Import Floodlight v0.90
diff --git a/src/ext/floodlight/Makefile b/src/ext/floodlight/Makefile
new file mode 100644
index 0000000..63c4dd0
--- /dev/null
+++ b/src/ext/floodlight/Makefile
@@ -0,0 +1,36 @@
+# Because I am old and crotchety and my fingers can't stop from running 
+#	`make` commands
+
+.PHONY: docs doc all test tests count install clean
+
+all:
+	ant
+
+init:
+	ant init
+
+docs:
+	ant javadoc
+
+doc:
+	ant javadoc
+
+javadoc:
+	ant javadoc
+
+check: tests
+test: tests
+
+tests: all unit-tests
+
+unit-tests:
+	ant tests
+
+regression-tests:
+	make -C regress tests
+
+count: 
+	@find src -name \*.java | xargs wc -l | sort -n
+
+clean:
+	ant clean
diff --git a/src/ext/floodlight/apps/circuitpusher/circuitpusher.py b/src/ext/floodlight/apps/circuitpusher/circuitpusher.py
new file mode 100755
index 0000000..68c5df5
--- /dev/null
+++ b/src/ext/floodlight/apps/circuitpusher/circuitpusher.py
@@ -0,0 +1,201 @@
+#! /usr/bin/python
+"""
+circuitpusher utilizes floodlight rest APIs to create a bidirectional circuit, 
+i.e., permanent flow entry, on all switches in route between two devices based 
+on IP addresses with specified priority.
+ 
+Notes:
+ 1. The circuit pusher currently only creates circuit with two IP end points 
+ 2. Prior to sending restAPI requests to the circuit pusher, the specified end
+    points must already been known to the controller (i.e., already have sent
+    packets on the network, easy way to assure this is to do a ping (to any
+    target) from the two hosts.
+ 3. The current supported command syntax format is:
+    a) circuitpusher.py --controller={IP}:{rest port} --type ip --src {IP} --dst {IP} --add --name {circuit-name}
+ 
+       adds a new circuit between src and dst devices Currently ip circuit is supported. ARP is automatically supported.
+    
+       Currently a simple circuit record storage is provided in a text file circuits.json in the working directory.
+       The file is not protected and does not clean itself between controller restarts.  The file is needed for correct operation
+       and the user should make sure deleting the file when floodlight controller is restarted.
+
+    b) circuitpusher.py --controller={IP}:{rest port} --delete --name {circuit-name}
+
+       deletes a created circuit (as recorded in circuits.json) using the previously given name
+
+@author kcwang
+"""
+
+import os
+import sys
+import subprocess
+import json
+import argparse
+import io
+import time
+
+# parse circuit options.  Currently supports add and delete actions.
+# Syntax:
+#   circuitpusher --controller {IP:REST_PORT} --add --name {CIRCUIT_NAME} --type ip --src {IP} --dst {IP} 
+#   circuitpusher --controller {IP:REST_PORT} --delete --name {CIRCUIT_NAME}
+
+parser = argparse.ArgumentParser(description='Circuit Pusher')
+parser.add_argument('--controller', dest='controllerRestIp', action='store', default='localhost:8080', help='controller IP:RESTport, e.g., localhost:8080 or A.B.C.D:8080')
+parser.add_argument('--add', dest='action', action='store_const', const='add', default='add', help='action: add, delete')
+parser.add_argument('--delete', dest='action', action='store_const', const='delete', default='add', help='action: add, delete')
+parser.add_argument('--type', dest='type', action='store', default='ip', help='valid types: ip')
+parser.add_argument('--src', dest='srcAddress', action='store', default='0.0.0.0', help='source address: if type=ip, A.B.C.D')
+parser.add_argument('--dst', dest='dstAddress', action='store', default='0.0.0.0', help='destination address: if type=ip, A.B.C.D')
+parser.add_argument('--name', dest='circuitName', action='store', default='circuit-1', help='name for circuit, e.g., circuit-1')
+
+args = parser.parse_args()
+print args
+
+controllerRestIp = args.controllerRestIp
+
+# first check if a local file exists, which needs to be updated after add/delete
+if os.path.exists('./circuits.json'):
+    circuitDb = open('./circuits.json','r')
+    lines = circuitDb.readlines()
+    circuitDb.close()
+else:
+    lines={}
+
+if args.action=='add':
+
+    circuitDb = open('./circuits.json','a')
+    
+    for line in lines:
+        data = json.loads(line)
+        if data['name']==(args.circuitName):
+            print "Circuit %s exists already. Use new name to create." % args.circuitName
+            sys.exit()
+        else:
+            circuitExists = False
+    
+    # retrieve source and destination device attachment points
+    # using DeviceManager rest API 
+    
+    command = "curl -s http://%s/wm/device/?ipv4=%s" % (args.controllerRestIp, args.srcAddress)
+    result = os.popen(command).read()
+    parsedResult = json.loads(result)
+    print command+"\n"
+    sourceSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID']
+    sourcePort = parsedResult[0]['attachmentPoint'][0]['port']
+    
+    command = "curl -s http://%s/wm/device/?ipv4=%s" % (args.controllerRestIp, args.dstAddress)
+    result = os.popen(command).read()
+    parsedResult = json.loads(result)
+    print command+"\n"
+    destSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID']
+    destPort = parsedResult[0]['attachmentPoint'][0]['port']
+    
+    print "Creating circuit:"
+    print "from source device at switch %s port %s" % (sourceSwitch,sourcePort)
+    print "to destination device at switch %s port %s"% (destSwitch,destPort)
+    
+    # retrieving route from source to destination
+    # using Routing rest API
+    
+    command = "curl -s http://%s/wm/topology/route/%s/%s/%s/%s/json" % (controllerRestIp, sourceSwitch, sourcePort, destSwitch, destPort)
+    
+    result = os.popen(command).read()
+    parsedResult = json.loads(result)
+
+    print command+"\n"
+    print result+"\n"
+
+    for i in range(len(parsedResult)):
+        if i % 2 == 0:
+            ap1Dpid = parsedResult[i]['switch']
+            ap1Port = parsedResult[i]['port']
+            print ap1Dpid, ap1Port
+            
+        else:
+            ap2Dpid = parsedResult[i]['switch']
+            ap2Port = parsedResult[i]['port']
+            print ap2Dpid, ap2Port
+            
+            # send one flow mod per pair of APs in route
+            # using StaticFlowPusher rest API
+
+            # IMPORTANT NOTE: current Floodlight StaticflowEntryPusher
+            # assumes all flow entries to have unique name across all switches
+            # this will most possibly be relaxed later, but for now we
+            # encode each flow entry's name with both switch dpid, user
+            # specified name, and flow type (f: forward, r: reverse, farp/rarp: arp)
+
+            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"src-ip\":\"%s\", \"dst-ip\":\"%s\", \"ether-type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress-port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".f", args.srcAddress, args.dstAddress, "0x800", ap1Port, ap2Port, controllerRestIp)
+            result = os.popen(command).read()
+            print command
+
+            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"ether-type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress-port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".farp", "0x806", ap1Port, ap2Port, controllerRestIp)
+            result = os.popen(command).read()
+            print command
+
+
+            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"src-ip\":\"%s\", \"dst-ip\":\"%s\", \"ether-type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress-port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".r", args.dstAddress, args.srcAddress, "0x800", ap2Port, ap1Port, controllerRestIp)
+            result = os.popen(command).read()
+            print command
+
+            command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"ether-type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress-port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".rarp", "0x806", ap2Port, ap1Port, controllerRestIp)
+            result = os.popen(command).read()
+            print command
+            
+            # store created circuit attributes in local ./circuits.json
+            datetime = time.asctime()
+            circuitParams = {'name':args.circuitName, 'Dpid':ap1Dpid, 'inPort':ap1Port, 'outPort':ap2Port, 'datetime':datetime}
+            str = json.dumps(circuitParams)
+            circuitDb.write(str+"\n")
+
+        # confirm successful circuit creation
+        # using controller rest API
+            
+        command="curl -s http://%s/wm/core/switch/all/flow/json| python -mjson.tool" % (controllerRestIp)
+        result = os.popen(command).read()
+        print command + "\n" + result
+
+elif args.action=='delete':
+    
+    circuitDb = open('./circuits.json','w')
+
+    # removing previously created flow from switches
+    # using StaticFlowPusher rest API       
+    # currently, circuitpusher records created circuits in local file ./circuits.db 
+    # with circuit name and list of switches                                  
+
+    circuitExists = False
+
+    for line in lines:
+        data = json.loads(line)
+        if data['name']==(args.circuitName):
+            circuitExists = True
+
+            sw = data['Dpid']
+            print data, sw
+
+            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".f", sw, controllerRestIp)
+            result = os.popen(command).read()
+            print command, result
+
+            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".farp", sw, controllerRestIp)
+            result = os.popen(command).read()
+            print command, result
+
+            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".r", sw, controllerRestIp)
+            result = os.popen(command).read()
+            print command, result
+
+            command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".rarp", sw, controllerRestIp)
+            result = os.popen(command).read()
+            print command, result            
+            
+        else:
+            circuitDb.write(line)
+
+    circuitDb.close()
+
+    if not circuitExists:
+        print "specified circuit does not exist"
+        sys.exit()
+
diff --git a/src/ext/floodlight/build.xml b/src/ext/floodlight/build.xml
new file mode 100644
index 0000000..b141553
--- /dev/null
+++ b/src/ext/floodlight/build.xml
@@ -0,0 +1,278 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+ <!--
+   Copyright 2011, Big Switch Networks, Inc.
+   
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!--
+    The build uses pregenerated Thrift code by default to reduce build
+    dependencies. To generate it locally run the gen-thrift target.
+    If you change the Thrift files be sure to also commit the updated
+    generated code.
+-->
+
+<project default="dist" name="Floodlight">
+    <property name="target" location="target"/>
+    <property name="build" location="${target}/bin"/>
+    <property name="build-test" location="${target}/bin-test"/>
+    <property name="build-coverage" location="${target}/bin-coverage"/>
+    <property name="test-output" location="${target}/test"/>
+    <property name="coverage-output" location="${target}/coverage"/>
+    <property name="source" location="src/main/java"/>
+    <property name="resources" location="src/main/resources/"/>
+    <property name="source-test" location="src/test/java"/>
+    <property name="python-src" location="src/main/python"/>
+    <property name="docs" location="${target}/docs"/>
+    <property name="main-class" value="net.floodlightcontroller.core.Main"/>
+    <property name="floodlight-jar" location="${target}/floodlight.jar"/>
+    <property name="floodlight-test-jar" location="${target}/floodlight-test.jar"/>
+    <property name="thrift.dir" value="${basedir}/src/main/thrift"/>
+    <property name="thrift.out.dir" value="lib/gen-java"/>
+    <property name="thrift.package" value="net/floodlightcontroller/packetstreamer/thrift"/>
+    <property name="ant.build.javac.source" value="1.6"/>
+    <property name="ant.build.javac.target" value="1.6"/>
+    <property name="lib" location="lib"/>
+
+    <patternset id="lib">
+        <include name="logback-classic-1.0.0.jar"/>
+        <include name="logback-core-1.0.0.jar"/>
+        <include name="jackson-core-asl-1.8.6.jar"/>
+        <include name="jackson-mapper-asl-1.8.6.jar"/>
+        <include name="slf4j-api-1.6.4.jar"/>
+        <include name="org.restlet-2.1-RC1.jar"/>
+        <include name="org.restlet.ext.jackson-2.1-RC1.jar"/>
+        <include name="org.restlet.ext.simple-2.1-RC1.jar"/>
+        <include name="org.restlet.ext.slf4j-2.1-RC1.jar"/>
+        <include name="simple-4.1.21.jar"/>
+        <include name="netty-3.2.6.Final.jar"/>
+        <include name="args4j-2.0.16.jar"/>
+        <include name="concurrentlinkedhashmap-lru-1.2.jar"/>
+        <include name="jython-2.5.2.jar"/>
+        <include name="libthrift-0.7.0.jar"/>
+    </patternset>
+
+    <path id="classpath">
+        <fileset dir="${lib}">
+            <patternset refid="lib"/>
+        </fileset>
+    </path>
+
+    <patternset id="lib-cobertura">
+        <include name="cobertura-1.9.4.1.jar"/>
+        <include name="asm-3.0.jar"/>
+        <include name="asm-tree-3.0.jar"/>
+        <include name="oro/jakarta-oro-2.0.8.jar"/>
+        <include name="log4j-1.2.9.jar"/>
+    </patternset>
+    <path id="classpath-cobertura">
+        <fileset dir="${lib}">
+            <patternset refid="lib-cobertura"/>
+    </fileset>
+    </path>
+
+    <patternset id="lib-test">
+        <include name="junit-4.8.2.jar"/>
+        <include name="org.easymock-3.1.jar"/>
+        <include name="objenesis-1.2.jar"/>  <!-- required by easymock to mock classes -->
+        <include name="cglib-nodep-2.2.2.jar"/>    <!-- required by easymock to mock classes -->
+    </patternset>
+    <path id="classpath-test">
+        <fileset dir="${lib}">
+            <patternset refid="lib-test"/>
+            <patternset refid="lib-cobertura"/>
+            <patternset refid="lib"/>
+        </fileset>
+    </path>
+
+    <target name="init">
+        <mkdir dir="${build}"/>
+        <mkdir dir="${build-test}"/>
+        <mkdir dir="${target}/lib"/>
+        <mkdir dir="${thrift.out.dir}"/>
+        <mkdir dir="${test-output}"/>
+    </target>
+
+    <target name="compile" depends="init">
+        <javac includeAntRuntime="false" 
+           classpathref="classpath" 
+           debug="true" 
+           srcdir="${source}:${thrift.out.dir}"
+           destdir="${build}">
+        </javac>
+    </target>
+
+    <target name="compile-tests" depends="compile-test"/>
+    <target name="compile-test" depends="compile">
+        <fileset dir="${resources}"/>
+        <javac includeAntRuntime="false" debug="true" 
+           srcdir="${source-test}"
+           classpath="${build}"
+           classpathref="classpath-test"
+           destdir="${build-test}"/>
+    </target>
+
+    <!-- Thrift build based on http://www.flester.com/blog/2009/04/26/using-thrift-from-ant -->
+    <fileset id="thrift.files" dir="${thrift.dir}">
+        <include name="**/*.thrift"/>
+    </fileset>
+
+    <target name="gen-thrift" depends="init">
+        <pathconvert property="thrift.file.list" refid="thrift.files"
+            pathsep=" " dirsep="/">
+        </pathconvert>
+        <echo message="Running thrift generator on ${thrift.file.list}"/>
+        <exec executable="thrift" dir="${basedir}" failonerror="true">
+            <arg line="--strict -v --gen java -o ${thrift.out.dir}/.. ${thrift.file.list}"/>
+        </exec>
+        <!-- Get rid of annoying warnings in thrift java: at annotations -->
+        <echo message="Adding @SuppressWarning annotations"/>
+        <replaceregexp byline="true">
+            <regexp pattern="^public "/>
+            <substitution expression='@SuppressWarnings("all") public '/>
+            <fileset id="thrift.output.files" dir="${thrift.out.dir}/..">
+                <include name="**/*.java"/>
+            </fileset>
+        </replaceregexp>
+    </target>
+
+    <target name="clean">
+        <delete dir="${target}"/>
+    </target>
+
+    <target name="run" depends="dist">
+        <java fork="true" jar="${floodlight-jar}" classpathref="classpath">
+            <jvmarg value="-server"/>
+            <jvmarg value="-Xms1024M"/>
+            <jvmarg value="-Xmx1024M"/>
+        </java>
+    </target>
+
+    <target name="tests" depends="test"/>
+    <target name="test" depends="compile-test">
+        <junit fork="true" forkmode="once"
+           failureproperty="junit.failure"
+           printsummary="on">
+        <sysproperty key="net.sourceforge.cobertura.datafile"
+             file="${target}/cobertura.ser" />
+            <classpath>
+                <pathelement location="${build-coverage}"/>
+                <pathelement location="${build}"/>
+                <pathelement location="${build-test}"/>
+                <pathelement location="${floodlight-jar}"/>
+                <path refid="classpath-test"/>
+            </classpath>
+            <formatter type="brief" usefile="true" />
+            <batchtest todir="${test-output}">
+                <fileset dir="${source-test}">
+                    <exclude name="**/storage/tests/StorageTest.java"/>
+				    <include name="**/*Test*.java"/>
+                    <exclude name="**/core/test/**"/>
+                    <exclude name="**/core/module/**"/>
+                </fileset>
+            </batchtest>
+        </junit>
+        <fail if="junit.failure" message="Unit test(s) failed.  See reports!"/>
+    </target>
+
+    <taskdef classpathref="classpath-cobertura" resource="tasks.properties"/>
+    <target name="clean-instrument">
+        <delete file="${target}/cobertura.ser"/>
+        <delete dir="${build-coverage}"/>
+    </target>
+    <target name="instrument" depends="compile,compile-test,clean-instrument">
+      <cobertura-instrument datafile="${target}/cobertura.ser"
+                todir="${build-coverage}"
+                classpathref="classpath-cobertura">
+    <fileset dir="${build}">
+      <include name="**/*.class"/>
+    </fileset>
+      </cobertura-instrument>
+    </target>
+    <target name="coverage-report">
+        <cobertura-report format="html"
+              datafile="${target}/cobertura.ser"
+              destdir="${coverage-output}"
+              srcdir="${source}"/>
+        <cobertura-report format="xml"
+              datafile="${target}/cobertura.ser"
+              destdir="${coverage-output}"
+              srcdir="${source}"/>
+    </target>
+    <target name="coverage" depends="instrument,test,coverage-report"/>
+
+    <target name="dist" depends="compile,compile-test">
+        <jar destfile="${floodlight-jar}" filesetmanifest="mergewithoutmain">
+            <manifest>
+                <attribute name="Main-Class" value="${main-class}"/>
+                <attribute name="Class-Path" value="."/>
+            </manifest>
+            <fileset dir="${build}"/>
+            <fileset dir="${resources}"/>
+            <fileset dir="${python-src}">
+                <include name="**/*.py"/>
+            </fileset>
+            <zipgroupfileset dir="${lib}">
+                <patternset refid="lib"/>
+            </zipgroupfileset>
+        </jar>
+        <jar destfile="${floodlight-test-jar}" filesetmanifest="mergewithoutmain">
+            <manifest>
+                <attribute name="Class-Path" value="."/>
+            </manifest>
+            <fileset dir="${build-test}"/>
+            <fileset dir="${resources}"/>
+            <zipgroupfileset dir="${lib}">
+                <patternset refid="lib-test"/>
+                <patternset refid="lib-cobertura"/>
+            </zipgroupfileset>
+        </jar>
+    </target>
+
+    <target name="javadoc">
+        <javadoc access="protected"
+            author="true"
+            classpathref="classpath"
+            destdir="${docs}"
+            doctitle="Floodlight"
+            nodeprecated="false"
+            nodeprecatedlist="false"
+            noindex="false"
+            nonavbar="false"
+            notree="false"
+            source="1.6"
+            sourcepath="${source}"
+            splitindex="true"
+            use="true"
+            version="true"/>
+    </target>
+
+    <target name="eclipse" depends="init">
+        <pathconvert property="eclipse-lib">
+            <map from="${basedir}/" to=""/>
+            <fileset dir="${lib}">
+                <patternset refid="lib"/>
+                <patternset refid="lib-test"/>
+            </fileset>
+        </pathconvert>
+        <exec executable="${basedir}/setup-eclipse.sh">
+            <arg value="${main-class}"/>
+            <arg value="${eclipse-lib}"/>
+        </exec>
+    </target>
+
+</project>
diff --git a/src/ext/floodlight/example/README b/src/ext/floodlight/example/README
new file mode 100644
index 0000000..e3e8179
--- /dev/null
+++ b/src/ext/floodlight/example/README
@@ -0,0 +1,28 @@
+One of Floodlight's main goals is extensibility and flexibility.
+
+To prove that point, this directory includes a number of useful
+utilities as examples of what can do with this extensibility.
+
+UTILITIES:
+--------------------------
+
+graphDeps.py and graphTopo.py
+
+    Read the module dependencies (graphDeps.py) or the topology
+    from the REST API and output it in the 'dot' format used by the
+    popular graphviz (www.graphviz.org) package so that they can
+    be visualized.
+
+    Example usage:
+        ./graphTopo.py $hostname                    # generate .dot file
+        dot -Tpdf -o $hostname.pdf $hostname.dot    # convert to PDF
+        open $hostname.pdf                          # open to view topology
+        
+
+
+packetStreamerClientExample.py
+
+    Example client for the packet streamer server in floodlight.
+    Allows you to intercept packets from floodlight's packet_in
+    processing chain and read them.
+
diff --git a/src/ext/floodlight/example/cli.py b/src/ext/floodlight/example/cli.py
new file mode 100755
index 0000000..ca8b443
--- /dev/null
+++ b/src/ext/floodlight/example/cli.py
@@ -0,0 +1,115 @@
+#!/usr/bin/python
+
+import sys
+import argparse
+import json
+import httplib
+import urllib2
+
+class RestApi(object):
+
+    def __init__(self, server,port):
+        self.server = server
+        self.port = port
+
+    def get(self, path):
+        #ret = self.rest_call(path, {}, 'GET')
+        #return ret[2]
+        f = urllib2.urlopen('http://'+self.server+':'+str(self.port)+path)
+        ret = f.read()
+        return json.loads(ret)
+
+    def set(self, path, data):
+        ret = self.rest_call(path, data, 'POST')
+        return ret[0] == 200
+
+    def remove(self, objtype, data):
+        #ret = self.rest_call(data, 'DELETE')
+        return ret[0] == 200
+
+    def rest_call(self, path, data, action):
+        headers = {
+            'Content-type': 'application/json',
+            'Accept': 'application/json',
+            }
+        body = json.dumps(data)
+        conn = httplib.HTTPConnection(self.server, self.port)
+        conn.request(action, path, body, headers)
+        response = conn.getresponse()
+        ret = (response.status, response.reason, response.read())
+        conn.close()
+        print str(ret[2])
+        return ret
+
+
+    
+usage_desc =  """
+Command descriptions: 
+
+    host [debug]
+    link [tunnellinks]
+    port <blocked | broadcast>
+    memory
+    switch
+    switchclusters
+    counter [DPID] <name>
+    switch_stats [DPID] <port | queue | flow | aggregate | desc | table | features | host>
+"""
+
+def lookupPath(cmd):
+    path = ''
+
+    numargs = len(args.otherargs)
+
+    if args.cmd == 'switch_stats':
+        if numargs == 1:
+            path = '/wm/core/switch/all/'+args.otherargs[0]+'/json'
+        elif numargs == 2:
+            path = '/wm/core/switch/'+args.otherargs[0]+'/'+args.otherargs[1]+'/json'
+    elif args.cmd == 'switch':
+        path = '/wm/core/controller/switches/json'
+    elif args.cmd == 'counter':
+        if numargs == 1:
+            path = '/wm/core/counter/'+args.otherargs[0]+'/json'
+        elif numargs == 2:
+            path = '/wm/core/counter/'+args.otherargs[0]+'/'+args.otherargs[1]+'/json'
+    elif args.cmd == 'memory':
+        path = '/wm/core/memory/json'
+    elif args.cmd == 'link':
+        if numargs == 0:
+            path = '/wm/topology/links/json'
+        elif numargs == 1:
+            path = '/wm/topology/'+args.otherargs[0]+'/json'
+    elif args.cmd == 'port' and numargs == 1:
+        if args.otherargs[0] == "blocked":
+            path = '/wm/topology/blockedports/json'
+        elif args.otherargs[0] == "broadcast":
+            path = '/wm/topology/broadcastdomainports/json'
+    elif args.cmd == 'switchclusters':
+        path = '/wm/topology/switchclusters/json'
+    elif args.cmd == 'host':
+        path = '/wm/device/'
+        if len(args.otherargs) == 1 and args.otherargs[0] == 'debug':
+            path = '/wm/device/debug'
+    else:
+        print usage_desc
+        path = ''
+        exit(0)
+    return path
+
+parser = argparse.ArgumentParser(description='process args', usage=usage_desc, epilog='foo bar help')
+parser.add_argument('--ip', default='localhost')
+parser.add_argument('--port', default=8080)
+parser.add_argument('cmd')
+parser.add_argument('otherargs', nargs='*')
+args = parser.parse_args()
+
+#print args
+
+rest = RestApi(args.ip, args.port)
+path = lookupPath(args.cmd)
+
+out = rest.get(path)
+print json.dumps(out,sort_keys=True, indent=4)
+print "Number of items: " + str(len(out))
+
diff --git a/src/ext/floodlight/example/graphDeps.py b/src/ext/floodlight/example/graphDeps.py
new file mode 100755
index 0000000..505d516
--- /dev/null
+++ b/src/ext/floodlight/example/graphDeps.py
@@ -0,0 +1,72 @@
+#!/usr/bin/python
+
+import urllib2
+import json
+import sys
+
+
+def simple_json_get(url):
+    return json.loads(urllib2.urlopen(url).read())
+
+
+def shorten(s):
+    return  s.replace('net.floodlightcontroller','n.f'
+            ).replace('com.bigswitch','c.b')
+
+def usage(s):
+    sys.stderr.write("Usage:\ngrahDeps.py hostname [port]\n%s" % s)
+    sys.stderr.write("\n\n\n\n    writes data to 'hostname.dot' for use with graphviz\n")
+    sys.exit(1)
+
+
+if __name__ == '__main__':
+
+    host='localhost'
+    port=8080
+
+    if len(sys.argv) == 1 or sys.argv[1] == '-h' or sys.argv[1] == '--help':
+        usage("need to specify hostname")
+
+    host = sys.argv[1]
+    if len(sys.argv) > 2:
+        port = int(sys.argv[2])
+
+    sys.stderr.write("Connecting to %s:%d ..." % (host,port))
+    URL="http://%s:%d/wm/core/module/loaded/json" % (host,port)
+
+    deps = simple_json_get(URL)
+    serviceMap = {}
+    nodeMap = {}
+    nodeCount = 0
+
+    sys.stderr.write("Writing to %s.dot ..." % (host))
+    f = open("%s.dot" % host, 'w')
+
+    f.write( "digraph Deps {\n")
+
+    for mod, info in deps.iteritems():
+        # sys.stderr.write("Discovered module %s\n" % mod)
+        nodeMap[mod] = "n%d" % nodeCount
+        nodeCount += 1
+        label = shorten(mod) + "\\n"
+        for service, serviceImpl in info['provides'].iteritems():
+            # sys.stderr.write("     Discovered service %s implemented with %s\n" % (service,serviceImpl))
+            label += "\\nService=%s" % shorten(service)
+            serviceMap[serviceImpl] = mod
+        f.write("     %s [ label=\"%s\", color=\"blue\"];\n" % (nodeMap[mod], label))
+
+    f.write("\n")      # for readability
+
+    for mod, info in deps.iteritems():
+        for dep, serviceImpl in info['depends'].iteritems():
+            f.write("     %s -> %s [ label=\"%s\"];\n" % (
+                    nodeMap[mod],
+                    shorten(nodeMap[serviceMap[serviceImpl]]),
+                    shorten(dep)))
+        
+
+    f.write("}\n")
+    f.close();
+    sys.stderr.write("Now type\ndot -Tpdf -o %s.pdf %s.dot\n" % (
+        host, host))
+        
diff --git a/src/ext/floodlight/example/graphTopo.py b/src/ext/floodlight/example/graphTopo.py
new file mode 100755
index 0000000..b89a763
--- /dev/null
+++ b/src/ext/floodlight/example/graphTopo.py
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+
+import urllib2
+import json
+import sys
+
+
+def simple_json_get(url):
+    return json.loads(urllib2.urlopen(url).read())
+
+
+def shorten(s):
+    return  s.replace('net.floodlightcontroller','n.f'
+            ).replace('com.bigswitch','c.b')
+
+def usage(s):
+    sys.stderr.write("Usage:\ngrahTopo.py hostname [port]\n%s" % s)
+    sys.stderr.write("\n\n\n\n    writes data to 'hostname.dot' for use with graphviz\n")
+    sys.exit(1)
+
+
+if __name__ == '__main__':
+
+    host='localhost'
+    port=8080
+
+    if len(sys.argv) == 1 or sys.argv[1] == '-h' or sys.argv[1] == '--help':
+        usage("need to specify hostname")
+
+    host = sys.argv[1]
+    if len(sys.argv) > 2:
+        port = int(sys.argv[2])
+
+    sys.stderr.write("Connecting to %s:%d ..." % (host,port))
+    URL="http://%s:%d/wm/topology/links/json" % (host,port)
+
+    # {
+    # "dst-port": 2, 
+    # "dst-switch": "00:00:00:00:00:00:00:0a", 
+    # "src-port": 3, 
+    # "src-switch": "00:00:00:00:00:00:00:0c"
+    # }
+
+    links = simple_json_get(URL)
+    nodeMap = {}
+
+    sys.stderr.write("Writing to %s.dot ..." % (host))
+    f = open("%s.dot" % host, 'w')
+
+    f.write( "digraph Deps {\n")
+
+    for link in links:
+        # sys.stderr.write("Discovered module %s\n" % mod)
+        if not link['dst-switch'] in nodeMap:
+            sw = link['dst-switch']
+            nodeMap[sw] = "n%d" % len(nodeMap)
+            f.write("     %s [ label=\"dpid=%s\", color=\"blue\"];\n" % (nodeMap[sw], sw))
+
+        if not link['src-switch'] in nodeMap:
+            sw = link['src-switch']
+            nodeMap[sw] = "n%d" % len(nodeMap)
+            f.write("     %s [ label=\"dpid=%s\", color=\"blue\"];\n" % (nodeMap[sw], sw))
+
+
+        f.write("     %s -> %s [ label=\"%s\"];\n" % (
+                nodeMap[link['dst-switch']],
+                nodeMap[link['src-switch']],
+                "src_port %d --> dst_port %d" % (link['src-port'],link['dst-port'])
+                )
+                )
+        
+
+    f.write("}\n")
+    f.close();
+    sys.stderr.write("Now type\ndot -Tpdf -o %s.pdf %s.dot\n" % (
+        host, host))
+        
diff --git a/src/ext/floodlight/example/packetStreamerClientExample.py b/src/ext/floodlight/example/packetStreamerClientExample.py
new file mode 100755
index 0000000..4510374
--- /dev/null
+++ b/src/ext/floodlight/example/packetStreamerClientExample.py
@@ -0,0 +1,138 @@
+#!/usr/bin/python
+
+import urllib2
+import json
+import re
+import sys
+
+from optparse import OptionParser
+
+sys.path.append('~/floodlight/target/gen-py')
+sys.path.append('~/floodlight/thrift/lib/py')
+
+from packetstreamer import PacketStreamer
+from packetstreamer.ttypes import *
+
+from thrift import Thrift
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+
+SESSIONID = 'sessionId'
+usage = "usage: %prog [options]"
+parser = OptionParser(usage=usage, version="%prog 1.0")
+parser.add_option("-c", "--controller", dest="controller", metavar="CONTROLLER_IP",
+                  default="127.0.0.1", help="controller's IP address")
+parser.add_option("-m", "--mac", dest="mac", metavar="HOST_MAC",
+                  help="The host mac address to trace the OF packets")
+
+(options, args) = parser.parse_args()
+
+def validateIp(ip):
+    ipReg = ("(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" 
+             "\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
+             "\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
+             "\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)")
+    m = re.compile(ipReg).match(ip)
+    if m:
+        return True
+    else :
+        return False
+
+def validateMac(mac):
+    macReg = '([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}' # same regex as above
+    m = re.compile(macReg).match(mac)
+    if m:
+        return True
+    else :
+        return False
+
+if not validateIp(options.controller):
+    parser.error("Invalid format for ip address.")
+
+if not options.mac:
+    parser.error("-m or --mac option is required.")
+
+if not validateMac(options.mac):
+    parser.error("Invalid format for mac address. Format: xx:xx:xx:xx:xx:xx")
+
+controller = options.controller
+host = options.mac
+
+url = 'http://%s:8080/wm/core/packettrace/json' % controller
+filter = {'mac':host, 'direction':'both', 'period':1000}
+post_data = json.dumps(filter)
+request = urllib2.Request(url, post_data, {'Content-Type':'application/json'})
+response_text = None
+
+def terminateTrace(sid):
+    global controller
+
+    filter = {SESSIONID:sid, 'period':-1}
+    post_data = json.dumps(filter)
+    url = 'http://%s:8080/wm/core/packettrace/json' % controller
+    request = urllib2.Request(url, post_data, {'Content-Type':'application/json'})
+    try:
+        response = urllib2.urlopen(request)
+        response_text = response.read()
+    except Exception, e:
+        # Floodlight may not be running, but we don't want that to be a fatal
+        # error, so we just ignore the exception in that case.
+        print "Exception:", e
+
+try: 
+    response = urllib2.urlopen(request)
+    response_text = response.read()
+except Exception, e:
+    # Floodlight may not be running, but we don't want that to be a fatal
+    # error, so we just ignore the exception in that case.
+    print "Exception:", e
+    exit
+
+if not response_text:
+    print "Failed to start a packet trace session"
+    sys.exit()
+
+response_text = json.loads(response_text)
+
+sessionId = None
+if SESSIONID in response_text:
+    sessionId = response_text[SESSIONID]
+else:
+    print "Failed to start a packet trace session"
+    sys.exit()
+
+try:
+
+    # Make socket
+    transport = TSocket.TSocket('localhost', 9090)
+
+    # Buffering is critical. Raw sockets are very slow
+    transport = TTransport.TFramedTransport(transport)
+
+    # Wrap in a protocol
+    protocol = TBinaryProtocol.TBinaryProtocol(transport)
+
+    # Create a client to use the protocol encoder
+    client = PacketStreamer.Client(protocol)
+
+    # Connect!
+    transport.open()
+
+    while 1:
+        packets = client.getPackets(sessionId)
+        for packet in packets:
+            print "Packet: %s"% packet
+            if "FilterTimeout" in packet:
+                sys.exit()
+
+except Thrift.TException, e:
+    print '%s' % (e.message)
+    terminateTrace(sessionId)
+
+except KeyboardInterrupt, e:
+    terminateTrace(sessionId)
+
+# Close!
+transport.close()
+
diff --git a/src/ext/floodlight/floodlight.sh b/src/ext/floodlight/floodlight.sh
new file mode 100755
index 0000000..5bc8564
--- /dev/null
+++ b/src/ext/floodlight/floodlight.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# Set paths
+FL_HOME=`dirname $0`
+FL_JAR="${FL_HOME}/target/floodlight.jar"
+FL_LOGBACK="${FL_HOME}/logback.xml"
+
+# Set JVM options
+JVM_OPTS=""
+JVM_OPTS="$JVM_OPTS -server -d64"
+JVM_OPTS="$JVM_OPTS -Xmx2g -Xms2g -Xmn800m"
+JVM_OPTS="$JVM_OPTS -XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods"
+JVM_OPTS="$JVM_OPTS -XX:MaxInlineSize=8192 -XX:FreqInlineSize=8192"
+JVM_OPTS="$JVM_OPTS -XX:CompileThreshold=1500 -XX:PreBlockSpin=8"
+JVM_OPTS="$JVM_OPTS -Dpython.security.respectJavaAccessibility=false"
+
+# Create a logback file if required
+[ -f ${FL_LOGBACK} ] || cat <<EOF_LOGBACK >${FL_LOGBACK}
+<configuration scan="true">
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%level [%logger:%thread] %msg%n</pattern>
+        </encoder>
+    </appender>
+    <root level="INFO">
+        <appender-ref ref="STDOUT" />
+    </root>
+    <logger name="org" level="WARN"/>
+    <logger name="LogService" level="WARN"/> <!-- Restlet access logging -->
+    <logger name="net.floodlightcontroller" level="INFO"/>
+    <logger name="net.floodlightcontroller.logging" level="ERROR"/>
+</configuration>
+EOF_LOGBACK
+
+echo "Starting floodlight server ..."
+java ${JVM_OPTS} -Dlogback.configurationFile=${FL_LOGBACK} -jar ${FL_JAR}
diff --git a/src/ext/floodlight/floodlight_style_settings.xml b/src/ext/floodlight/floodlight_style_settings.xml
new file mode 100644
index 0000000..51ff232
--- /dev/null
+++ b/src/ext/floodlight/floodlight_style_settings.xml
@@ -0,0 +1,291 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<profiles version="12">
+<profile kind="CodeFormatterProfile" name="Floodlight" version="12">
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="82"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+<setting id="org.eclipse.jdt.core.compiler.source" value="1.7"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="77"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="20"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="2"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="20"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.7"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="20"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.7"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="82"/>
+<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="82"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+</profile>
+</profiles>
diff --git a/src/ext/floodlight/logback.xml b/src/ext/floodlight/logback.xml
new file mode 100644
index 0000000..b85b463
--- /dev/null
+++ b/src/ext/floodlight/logback.xml
@@ -0,0 +1,14 @@
+<configuration scan="true">
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%level [%logger:%thread] %msg%n</pattern>
+    </encoder>
+  </appender>
+  <root level="INFO">
+    <appender-ref ref="STDOUT" />
+  </root>
+  <logger name="org" level="WARN"/>
+  <logger name="LogService" level="WARN"/> <!-- Restlet access logging -->
+  <logger name="net.floodlightcontroller" level="INFO"/>
+  <logger name="net.floodlightcontroller.logging" level="WARN"/>
+</configuration>
diff --git a/src/ext/floodlight/setup-eclipse.sh b/src/ext/floodlight/setup-eclipse.sh
new file mode 100755
index 0000000..a39dc62
--- /dev/null
+++ b/src/ext/floodlight/setup-eclipse.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+d=$(dirname $0)
+MAIN_CLASS=$1
+LIBRARIES=$2
+[ "${MAIN_CLASS}" ] || { echo "Run 'ant eclipse' to generate Eclipse project files"; exit 1; }
+
+
+cat >$d/.project <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>floodlight</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
+EOF
+
+
+cat >$d/.classpath <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src/main/java" output="target/bin"/>
+	<classpathentry kind="src" path="src/main/resources"/>
+        <classpathentry kind="src" path="src/test/java" output="target/bin-test"/>
+        <classpathentry kind="src" path="lib/gen-java" output="target/bin"/>
+EOF
+(
+IFS=":"
+for l in ${LIBRARIES}; do
+cat >>$d/.classpath <<EOF
+	<classpathentry exported="true" kind="lib" path="$l"/>
+EOF
+done
+)
+cat >>$d/.classpath <<EOF
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="output" path="target/bin"/>
+</classpath>
+EOF
diff --git a/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/Data b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/Data
new file mode 100644
index 0000000..0fbceab
--- /dev/null
+++ b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/Data
Binary files differ
diff --git a/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Preview.png b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Preview.png
new file mode 100644
index 0000000..4a36e7c
--- /dev/null
+++ b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Preview.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Thumbnail.png b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Thumbnail.png
new file mode 100644
index 0000000..50ebe22
--- /dev/null
+++ b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Thumbnail.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/images/README b/src/ext/floodlight/src/main/images/README
new file mode 100644
index 0000000..46da010
--- /dev/null
+++ b/src/ext/floodlight/src/main/images/README
@@ -0,0 +1 @@
+These icons were created with Sketch, available at http://www.bohemiancoding.com/sketch/
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContext.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContext.java
new file mode 100644
index 0000000..aa4fe6b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContext.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This is a context object where floodlight listeners can register 
+ * and later retrieve context information associated with an
+ * event
+ * @author readams
+ */
+public class FloodlightContext {
+    protected ConcurrentHashMap<String, Object> storage =
+            new ConcurrentHashMap<String, Object>();
+
+    public ConcurrentHashMap<String, Object> getStorage() {
+        return storage;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContextStore.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContextStore.java
new file mode 100644
index 0000000..5455284
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContextStore.java
@@ -0,0 +1,34 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core;
+
+public class FloodlightContextStore<V> {
+    
+    @SuppressWarnings("unchecked")
+    public V get(FloodlightContext bc, String key) {
+        return (V)bc.storage.get(key);
+    }
+    
+    public void put(FloodlightContext bc, String key, V value) {
+        bc.storage.put(key, value);
+    }
+    
+    public void remove(FloodlightContext bc, String key) {
+        bc.storage.remove(key);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightProvider.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightProvider.java
new file mode 100644
index 0000000..347bf5b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightProvider.java
@@ -0,0 +1,74 @@
+package net.floodlightcontroller.core;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.core.internal.Controller;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.perfmon.IPktInProcessingTimeService;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+public class FloodlightProvider implements IFloodlightModule {
+    Controller controller;
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> services =
+                new ArrayList<Class<? extends IFloodlightService>>(1);
+        services.add(IFloodlightProviderService.class);
+        return services;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>,
+               IFloodlightService> getServiceImpls() {
+        controller = new Controller();
+        
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                            IFloodlightService>();
+        m.put(IFloodlightProviderService.class, controller);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> dependencies =
+            new ArrayList<Class<? extends IFloodlightService>>(4);
+        dependencies.add(IStorageSourceService.class);
+        dependencies.add(IPktInProcessingTimeService.class);
+        dependencies.add(IRestApiService.class);
+        dependencies.add(ICounterStoreService.class);
+        dependencies.add(IThreadPoolService.class);
+        return dependencies;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context) throws FloodlightModuleException {
+       controller.setStorageSourceService(
+           context.getServiceImpl(IStorageSourceService.class));
+       controller.setPktInProcessingService(
+           context.getServiceImpl(IPktInProcessingTimeService.class));
+       controller.setCounterStore(
+           context.getServiceImpl(ICounterStoreService.class));
+       controller.setRestApiService(
+           context.getServiceImpl(IRestApiService.class));
+       controller.setThreadPoolService(
+           context.getServiceImpl(IThreadPoolService.class));
+       controller.init(context.getConfigParams(this));
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        controller.startupComponents();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java
new file mode 100644
index 0000000..1e3ec6f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java
@@ -0,0 +1,210 @@
+/**
+ *    Copyright 2011, Big Switch Networks, Inc. 
+ *    Originally created by David Erickson, Stanford University
+ * 
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.core;
+
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.packet.Ethernet;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.factory.BasicFactory;
+
+/**
+ * The interface exposed by the core bundle that allows you to interact
+ * with connected switches.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IFloodlightProviderService extends IFloodlightService {
+
+    /**
+     * A value stored in the floodlight context containing a parsed packet
+     * representation of the payload of a packet-in message. 
+     */
+    public static final String CONTEXT_PI_PAYLOAD = 
+            "net.floodlightcontroller.core.IFloodlightProvider.piPayload";
+
+    /**
+     * The role of the controller as used by the OF 1.2 and OVS failover and
+     * load-balancing mechanism.
+     */
+    public static enum Role { EQUAL, MASTER, SLAVE };
+    
+    /**
+     * A FloodlightContextStore object that can be used to retrieve the 
+     * packet-in payload
+     */
+    public static final FloodlightContextStore<Ethernet> bcStore = 
+            new FloodlightContextStore<Ethernet>();
+
+    /**
+     * Adds an OpenFlow message listener
+     * @param type The OFType the component wants to listen for
+     * @param listener The component that wants to listen for the message
+     */
+    public void addOFMessageListener(OFType type, IOFMessageListener listener);
+
+    /**
+     * Removes an OpenFlow message listener
+     * @param type The OFType the component no long wants to listen for
+     * @param listener The component that no longer wants to receive the message
+     */
+    public void removeOFMessageListener(OFType type, IOFMessageListener listener);
+    
+    /**
+     * Return a non-modifiable list of all current listeners
+     * @return listeners
+     */
+    public Map<OFType, List<IOFMessageListener>> getListeners();
+
+    /**
+     * Returns an unmodifiable map of all actively connected OpenFlow switches. This doesn't
+     * contain switches that are connected but the controller's in the slave role.
+     * @return the set of actively connected switches
+     */
+    public Map<Long, IOFSwitch> getSwitches();
+    
+    /**
+     * Get the current role of the controller
+     */
+    public Role getRole();
+    
+    /**
+     * Get the current mapping of controller IDs to their IP addresses
+     * Returns a copy of the current mapping. 
+     * @see IHAListener
+     */
+    public Map<String,String> getControllerNodeIPs();
+    
+    /**
+     * Gets the ID of the controller
+     */
+    public String getControllerId();
+    
+    /**
+     * Set the role of the controller
+     */
+    public void setRole(Role role);
+    
+    /**
+     * Add a switch listener
+     * @param listener The module that wants to listen for events
+     */
+    public void addOFSwitchListener(IOFSwitchListener listener);
+
+    /**
+     * Remove a switch listener
+     * @param listener The The module that no longer wants to listen for events
+     */
+    public void removeOFSwitchListener(IOFSwitchListener listener);
+    
+    /**
+     * Adds a listener for HA role events
+     * @param listener The module that wants to listen for events
+     */
+    public void addHAListener(IHAListener listener);
+    
+    /**
+     * Removes a listener for HA role events
+     * @param listener The module that no longer wants to listen for events
+     */
+    public void removeHAListener(IHAListener listener);
+
+    /**
+     * Terminate the process
+     */
+    public void terminate();
+
+    /**
+     * Re-injects an OFMessage back into the packet processing chain
+     * @param sw The switch to use for the message
+     * @param msg the message to inject
+     * @return True if successfully re-injected, false otherwise
+     */
+    public boolean injectOfMessage(IOFSwitch sw, OFMessage msg);
+
+    /**
+     * Re-injects an OFMessage back into the packet processing chain
+     * @param sw The switch to use for the message
+     * @param msg the message to inject
+     * @param bContext a floodlight context to use if required
+     * @return True if successfully re-injected, false otherwise
+     */
+    public boolean injectOfMessage(IOFSwitch sw, OFMessage msg, 
+            FloodlightContext bContext);
+
+    /**
+     * Process written messages through the message listeners for the controller
+     * @param sw The switch being written to
+     * @param m the message 
+     * @param bc any accompanying context object
+     */
+    public void handleOutgoingMessage(IOFSwitch sw, OFMessage m, 
+            FloodlightContext bc);
+
+    /**
+     * Gets the BasicFactory
+     * @return an OpenFlow message factory
+     */
+    public BasicFactory getOFMessageFactory();
+
+    /**
+     * Run the main I/O loop of the Controller.
+     */
+    public void run();
+
+    /**
+     * Add an info provider of a particular type
+     * @param type
+     * @param provider
+     */
+    public void addInfoProvider(String type, IInfoProvider provider);
+
+   /**
+    * Remove an info provider of a particular type
+    * @param type
+    * @param provider
+    */
+   public void removeInfoProvider(String type, IInfoProvider provider);
+   
+   /**
+    * Return information of a particular type (for rest services)
+    * @param type
+    * @return
+    */
+   public Map<String, Object> getControllerInfo(String type);
+   
+   
+   /**
+    * Return the controller start time in  milliseconds
+    * @return
+    */
+   public long getSystemStartTime();
+   
+   /**
+    * Configure controller to always clear the flow table on the switch,
+    * when it connects to controller. This will be true for first time switch
+    * reconnect, as well as a switch re-attaching to Controller after HA
+    * switch over to ACTIVE role
+    */
+   public void setAlwaysClearFlowsOnSwAdd(boolean value);
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IHAListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IHAListener.java
new file mode 100644
index 0000000..c76f46a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IHAListener.java
@@ -0,0 +1,30 @@
+package net.floodlightcontroller.core;
+
+import java.util.Map;
+
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+
+public interface IHAListener {
+    /**
+     * Gets called when the controller changes role (i.e. Master -> Slave).
+     * Note that oldRole CAN be null.
+     * @param oldRole The controller's old role
+     * @param newRole The controller's new role
+     */
+    public void roleChanged(Role oldRole, Role newRole);
+    
+    /**
+     * Gets called when the IP addresses of the controller nodes in the 
+     * controller cluster change. All parameters map controller ID to
+     * the controller's IP.
+     *  
+     * @param curControllerNodeIPs The current mapping of controller IDs to IP
+     * @param addedControllerNodeIPs These IPs were added since the last update
+     * @param removedControllerNodeIPs These IPs were removed since the last update
+     */
+    public void controllerNodeIPsChanged(
+    		Map<String, String> curControllerNodeIPs,  
+    		Map<String, String> addedControllerNodeIPs,  
+    		Map<String, String> removedControllerNodeIPs
+    		);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IInfoProvider.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IInfoProvider.java
new file mode 100644
index 0000000..8bfae0d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IInfoProvider.java
@@ -0,0 +1,34 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core;
+
+import java.util.Map;
+
+/**
+ *
+ *
+ * @author Shudong Zhou
+ */
+public interface IInfoProvider {
+
+    /**
+     * Called when rest API requests information of a particular type
+     * @param type
+     * @return
+     */
+    public Map<String, Object> getInfo(String type);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IListener.java
new file mode 100644
index 0000000..1bd6560
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IListener.java
@@ -0,0 +1,52 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core;
+
+public interface IListener<T> {
+    public enum Command {
+        CONTINUE, STOP
+    }
+    
+    /**
+     * The name assigned to this listener
+     * @return
+     */
+    public String getName();
+
+    /**
+     * Check if the module called name is a callback ordering prerequisite
+     * for this module.  In other words, if this function returns true for 
+     * the given name, then this message listener will be called after that
+     * message listener.
+     * @param type the message type to which this applies
+     * @param name the name of the module
+     * @return whether name is a prerequisite.
+     */
+    public boolean isCallbackOrderingPrereq(T type, String name);
+
+    /**
+     * Check if the module called name is a callback ordering post-requisite
+     * for this module.  In other words, if this function returns true for 
+     * the given name, then this message listener will be called before that
+     * message listener.
+     * @param type the message type to which this applies
+     * @param name the name of the module
+     * @return whether name is a post-requisite.
+     */
+    public boolean isCallbackOrderingPostreq(T type, String name);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageFilterManagerService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageFilterManagerService.java
new file mode 100644
index 0000000..36b5be3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageFilterManagerService.java
@@ -0,0 +1,7 @@
+package net.floodlightcontroller.core;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IOFMessageFilterManagerService extends IFloodlightService {
+    // empty for now
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java
new file mode 100644
index 0000000..00fdac1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java
@@ -0,0 +1,38 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+
+/**
+ *
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IOFMessageListener extends IListener<OFType> {
+  /**
+   * This is the method Floodlight uses to call listeners with OpenFlow messages
+   * @param sw the OpenFlow switch that sent this message
+   * @param msg the message
+   * @param cntx a Floodlight message context object you can use to pass 
+   * information between listeners
+   * @return the command to continue or stop the execution
+   */
+  public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitch.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitch.java
new file mode 100644
index 0000000..d63624c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitch.java
@@ -0,0 +1,379 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+
+import org.jboss.netty.channel.Channel;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFStatistics;
+
+/**
+ *
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IOFSwitch {
+    // Attribute keys
+    public static final String SWITCH_DESCRIPTION_FUTURE = "DescriptionFuture";
+    public static final String SWITCH_DESCRIPTION_DATA = "DescriptionData";
+    public static final String SWITCH_SUPPORTS_NX_ROLE = "supportsNxRole";
+    public static final String SWITCH_IS_CORE_SWITCH = "isCoreSwitch";
+    public static final String PROP_FASTWILDCARDS = "FastWildcards";
+    public static final String PROP_REQUIRES_L3_MATCH = "requiresL3Match";
+    public static final String PROP_SUPPORTS_OFPP_TABLE = "supportsOfppTable";
+    public static final String PROP_SUPPORTS_OFPP_FLOOD = "supportsOfppFlood";
+    public static final String PROP_SUPPORTS_NETMASK_TBL = "supportsNetmaskTbl";
+    
+    /**
+     * Writes to the OFMessage to the output stream.
+     * The message will be handed to the floodlightProvider for possible filtering
+     * and processing by message listeners
+     * @param m   
+     * @param bc  
+     * @throws IOException  
+     */
+    public void write(OFMessage m, FloodlightContext bc) throws IOException; 
+    
+    /**
+     * Writes the list of messages to the output stream
+     * The message will be handed to the floodlightProvider for possible filtering
+     * and processing by message listeners.
+     * @param msglist
+     * @param bc
+     * @throws IOException
+     */
+    public void write(List<OFMessage> msglist, FloodlightContext bc) throws IOException;
+    
+    /**
+     * 
+     * @throws IOException
+     */
+    public void disconnectOutputStream();
+
+    /**
+     * FIXME: remove getChannel(). All access to the channel should be through
+     *        wrapper functions in IOFSwitch
+     * @return
+     */
+    public Channel getChannel();
+
+    /**
+     * Returns switch features from features Reply
+     * @return
+     */
+    public int getBuffers();
+    
+    public int getActions();
+    
+    public int getCapabilities();
+    
+    public byte getTables();
+
+    /**
+     * Set the OFFeaturesReply message returned by the switch during initial
+     * handshake.
+     * @param featuresReply
+     */
+    public void setFeaturesReply(OFFeaturesReply featuresReply);
+    
+    /**
+     * Set the SwitchProperties based on it's description
+     * @param description
+     */
+    public void setSwitchProperties(OFDescriptionStatistics description);    
+
+    /**
+     * Get list of all enabled ports. This will typically be different from
+     * the list of ports in the OFFeaturesReply, since that one is a static
+     * snapshot of the ports at the time the switch connected to the controller
+     * whereas this port list also reflects the port status messages that have
+     * been received.
+     * @return Unmodifiable list of ports not backed by the underlying collection
+     */
+    public Collection<OFPhysicalPort> getEnabledPorts();
+    
+    /**
+     * Get list of the port numbers of all enabled ports. This will typically
+     * be different from the list of ports in the OFFeaturesReply, since that
+     * one is a static snapshot of the ports at the time the switch connected 
+     * to the controller whereas this port list also reflects the port status
+     * messages that have been received.
+     * @return Unmodifiable list of ports not backed by the underlying collection
+     */
+    public Collection<Short> getEnabledPortNumbers();
+
+    /**
+     * Retrieve the port object by the port number. The port object
+     * is the one that reflects the port status updates that have been
+     * received, not the one from the features reply.
+     * @param portNumber
+     * @return port object
+     */
+    public OFPhysicalPort getPort(short portNumber);
+    
+    /**
+     * Retrieve the port object by the port name. The port object
+     * is the one that reflects the port status updates that have been
+     * received, not the one from the features reply.
+     * @param portName
+     * @return port object
+     */
+    public OFPhysicalPort getPort(String portName);
+    
+    /**
+     * Add or modify a switch port. This is called by the core controller
+     * code in response to a OFPortStatus message. It should not typically be
+     * called by other floodlight applications.
+     * @param port
+     */
+    public void setPort(OFPhysicalPort port);
+
+    /**
+     * Delete a port for the switch. This is called by the core controller
+     * code in response to a OFPortStatus message. It should not typically be
+     * called by other floodlight applications.
+     * @param portNumber
+     */
+    public void deletePort(short portNumber);
+    
+    /**
+     * Delete a port for the switch. This is called by the core controller
+     * code in response to a OFPortStatus message. It should not typically be
+     * called by other floodlight applications.
+     * @param portName
+     */
+    public void deletePort(String portName);
+    
+    /**
+     * Get list of all ports. This will typically be different from
+     * the list of ports in the OFFeaturesReply, since that one is a static
+     * snapshot of the ports at the time the switch connected to the controller
+     * whereas this port list also reflects the port status messages that have
+     * been received.
+     * @return Unmodifiable list of ports 
+     */
+    public Collection<OFPhysicalPort> getPorts();
+
+    /**
+     * @param portName
+     * @return Whether a port is enabled per latest port status message
+     * (not configured down nor link down nor in spanning tree blocking state)
+     */
+    public boolean portEnabled(short portName);
+    
+    /**
+     * @param portNumber
+     * @return Whether a port is enabled per latest port status message
+     * (not configured down nor link down nor in spanning tree blocking state)
+     */
+    public boolean portEnabled(String portName);
+
+    /**
+     * @param port
+     * @return Whether a port is enabled per latest port status message
+     * (not configured down nor link down nor in spanning tree blocking state)
+     */
+    public boolean portEnabled(OFPhysicalPort port);
+
+    /**
+     * Get the datapathId of the switch
+     * @return
+     */
+    public long getId();
+
+    /**
+     * Get a string version of the ID for this switch
+     * @return
+     */
+    public String getStringId();
+    
+    /**
+     * Retrieves attributes of this switch
+     * @return
+     */
+    public Map<Object, Object> getAttributes();
+
+    /**
+     * Retrieves the date the switch connected to this controller
+     * @return the date
+     */
+    public Date getConnectedSince();
+
+    /**
+     * Returns the next available transaction id
+     * @return
+     */
+    public int getNextTransactionId();
+
+    /**
+     * Returns a Future object that can be used to retrieve the asynchronous
+     * OFStatisticsReply when it is available.
+     *
+     * @param request statistics request
+     * @return Future object wrapping OFStatisticsReply
+     * @throws IOException 
+     */
+    public Future<List<OFStatistics>> getStatistics(OFStatisticsRequest request)
+            throws IOException;
+    
+    /**
+     * Returns a Future object that can be used to retrieve the asynchronous
+     * OFStatisticsReply when it is available.
+     *
+     * @param request statistics request
+     * @return Future object wrapping OFStatisticsReply
+     * @throws IOException 
+     */
+    public Future<OFFeaturesReply> getFeaturesReplyFromSwitch()
+            throws IOException;
+
+    /**
+     * Deliver the featuresReply future reply
+     * @param reply the reply to deliver
+     */
+    void deliverOFFeaturesReply(OFMessage reply);
+
+    /*
+     * Cancel features reply with a specific transction ID
+     * @param transactionId the transaction ID
+     */
+    public void cancelFeaturesReply(int transactionId);
+
+    /**
+     * Check if the switch is still connected;
+     * Only call while holding processMessageLock
+     * @return whether the switch is still disconnected
+     */
+    public boolean isConnected();
+    
+    /**
+     * Set whether the switch is connected
+     * Only call while holding modifySwitchLock
+     * @param connected whether the switch is connected
+     */
+    public void setConnected(boolean connected);
+    
+    /**
+     * Get the current role of the controller for the switch
+     * @return the role of the controller
+     */
+    public Role getRole();
+    
+    /**
+     * Check if the controller is an active controller for the switch.
+     * The controller is active if its role is MASTER or EQUAL.
+     * @return whether the controller is active
+     */
+    public boolean isActive();
+    
+    /**
+     * Deliver the statistics future reply
+     * @param reply the reply to deliver
+     */
+    public void deliverStatisticsReply(OFMessage reply);
+    
+    /**
+     * Cancel the statistics reply with the given transaction ID
+     * @param transactionId the transaction ID
+     */
+    public void cancelStatisticsReply(int transactionId);
+    
+    /**
+     * Cancel all statistics replies
+     */
+    public void cancelAllStatisticsReplies();
+
+    /**
+     * Checks if a specific switch property exists for this switch
+     * @param name name of property
+     * @return value for name
+     */
+    boolean hasAttribute(String name);
+
+    /**
+     * Set properties for switch specific behavior
+     * @param name name of property
+     * @return value for name
+     */
+    Object getAttribute(String name);
+
+    /**
+     * Set properties for switch specific behavior
+     * @param name name of property
+     * @param value value for name
+     */
+    void setAttribute(String name, Object value);
+
+    /**
+     * Set properties for switch specific behavior
+     * @param name name of property
+     * @return current value for name or null (if not present)
+     */
+    Object removeAttribute(String name);
+
+    /**
+     * Clear all flowmods on this switch
+     */
+    public void clearAllFlowMods();
+
+    /**
+     * Update broadcast cache
+     * @param data
+     * @return true if there is a cache hit
+     *         false if there is no cache hit.
+     */
+    public boolean updateBroadcastCache(Long entry, Short port);
+    
+    /**
+     * Get the portBroadcastCacheHits
+     * @return
+     */
+    public Map<Short, Long> getPortBroadcastHits();
+
+    /**
+     * Send a flow statistics request to the switch. This call returns after
+     * sending the stats. request to the switch.
+     * @param request flow statistics request message
+     * @param xid transaction id, must be obtained by using the getXid() API.
+     * @param caller the caller of the API. receive() callback of this 
+     * caller would be called when the reply from the switch is received.
+     * @return the transaction id for the message sent to the switch. The 
+     * transaction id can be used to match the response with the request. Note
+     * that the transaction id is unique only within the scope of this switch.
+     * @throws IOException
+     */
+    public void sendStatsQuery(OFStatisticsRequest request, int xid,
+                            IOFMessageListener caller) throws IOException;
+
+    /**
+     * Flush all flows queued for this switch in the current thread.
+     * NOTE: The contract is limited to the current thread
+     */
+     public void flush();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchFilter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchFilter.java
new file mode 100644
index 0000000..134ba98
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchFilter.java
@@ -0,0 +1,38 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core;
+
+/**
+ * Used in conjunction with {@link IOFMessageListener} to allow a listener to
+ * filter an incoming message based on the {@link IOFSwitch} it originated from.
+ * Implementations wanting to use this interface should implement both
+ * IOFMessageListener and IOFSwitchFilter.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IOFSwitchFilter {
+
+    /**
+     * The result of this method call determines whether the
+     * IOFMessageListener's receive method is called or not.
+     *
+     * @param sw switch to filter on
+     * @return true to receive the message, false to ignore
+     */
+    public boolean isInterested(IOFSwitch sw);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java
new file mode 100644
index 0000000..1bc258b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java
@@ -0,0 +1,51 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core;
+
+/**
+ *
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IOFSwitchListener {
+
+    /**
+     * Fired when a switch is connected to the controller, and has sent
+     * a features reply.
+     * @param sw
+     */
+    public void addedSwitch(IOFSwitch sw);
+
+    /**
+     * Fired when a switch is disconnected from the controller.
+     * @param sw
+     */
+    public void removedSwitch(IOFSwitch sw);
+    
+    /**
+     * Fired when ports on a switch change (any change to the collection
+     * of OFPhysicalPorts and/or to a particular port)
+     */
+    public void switchPortChanged(Long switchId);
+    
+    /**
+     * The name assigned to this listener
+     * @return
+     */
+    public String getName();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/Main.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/Main.java
new file mode 100644
index 0000000..91b317a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/Main.java
@@ -0,0 +1,49 @@
+package net.floodlightcontroller.core;
+
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+
+import net.floodlightcontroller.core.internal.CmdLineSettings;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.FloodlightModuleLoader;
+import net.floodlightcontroller.core.module.IFloodlightModuleContext;
+import net.floodlightcontroller.restserver.IRestApiService;
+
+/**
+ * Host for the Floodlight main method
+ * @author alexreimers
+ */
+public class Main {
+
+    /**
+     * Main method to load configuration and modules
+     * @param args
+     * @throws FloodlightModuleException 
+     */
+    public static void main(String[] args) throws FloodlightModuleException {
+        // Setup logger
+        System.setProperty("org.restlet.engine.loggerFacadeClass", 
+                "org.restlet.ext.slf4j.Slf4jLoggerFacade");
+        
+        CmdLineSettings settings = new CmdLineSettings();
+        CmdLineParser parser = new CmdLineParser(settings);
+        try {
+            parser.parseArgument(args);
+        } catch (CmdLineException e) {
+            parser.printUsage(System.out);
+            System.exit(1);
+        }
+        
+        // Load modules
+        FloodlightModuleLoader fml = new FloodlightModuleLoader();
+        IFloodlightModuleContext moduleContext = fml.loadModulesFromConfig(settings.getModuleFile());
+        // Run REST server
+        IRestApiService restApi = moduleContext.getServiceImpl(IRestApiService.class);
+        restApi.run();
+        // Run the main floodlight module
+        IFloodlightProviderService controller =
+                moduleContext.getServiceImpl(IFloodlightProviderService.class);
+        // This call blocks, it has to be the last line in the main
+        controller.run();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/OFMessageFilterManager.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/OFMessageFilterManager.java
new file mode 100644
index 0000000..391c002
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/OFMessageFilterManager.java
@@ -0,0 +1,529 @@
+/**
+ *    Copyright 2011, Big Switch Networks, Inc.
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.core;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFType;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.ArrayList;
+import org.apache.thrift.TException;
+import org.apache.thrift.transport.TFramedTransport;
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TSocket;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TProtocol;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packetstreamer.thrift.*;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+@LogMessageCategory("OpenFlow Message Tracing")
+public class OFMessageFilterManager 
+        implements IOFMessageListener, IFloodlightModule, IOFMessageFilterManagerService {
+
+    /**
+     * @author Srini
+     */
+    protected static Logger log = LoggerFactory.getLogger(OFMessageFilterManager.class);
+
+    // The port and client reference for packet streaming
+    protected int serverPort = 9090;
+    protected final int MaxRetry = 1;
+    protected static TTransport transport = null;
+    protected static PacketStreamer.Client packetClient = null;
+
+    protected IFloodlightProviderService floodlightProvider = null;
+    protected IThreadPoolService threadPool = null;
+    // filter List is a key value pair.  Key is the session id, 
+    // value is the filter rules.
+    protected ConcurrentHashMap<String, 
+                                ConcurrentHashMap<String,
+                                                  String>> filterMap = null;
+    protected ConcurrentHashMap<String, Long> filterTimeoutMap = null;
+    protected Timer timer = null;
+
+    protected int MAX_FILTERS=5;
+    protected long MAX_FILTER_TIME= 300000; // maximum filter time is 5 minutes.
+    protected int TIMER_INTERVAL = 1000;  // 1 second time interval.
+
+    public static final String SUCCESS                     = "0";
+    public static final String FILTER_SETUP_FAILED         = "-1001"; 
+    public static final String FILTER_NOT_FOUND            = "-1002";
+    public static final String FILTER_LIMIT_REACHED        = "-1003";
+    public static final String FILTER_SESSION_ID_NOT_FOUND = "-1004";
+    public static final String SERVICE_UNAVAILABLE         = "-1005";
+
+    public enum FilterResult {
+        /*
+         * FILTER_NOT_DEFINED: Filter is not defined
+         * FILTER_NO_MATCH:    Filter is defined and the packet doesn't 
+         *                     match the filter
+         * FILTER_MATCH:       Filter is defined and the packet matches
+         *                     the filter
+         */
+        FILTER_NOT_DEFINED, FILTER_NO_MATCH, FILTER_MATCH
+    }
+
+    protected String addFilter(ConcurrentHashMap<String,String> f, long delta) {
+
+        // Create unique session ID.  
+        int prime = 33791;
+        String s = null;
+        int i;
+
+        if ((filterMap == null) || (filterTimeoutMap == null))
+            return  String.format("%d", FILTER_SETUP_FAILED);
+
+        for (i=0; i<MAX_FILTERS; ++i) {
+            Integer x = prime + i;
+            s = String.format("%d", x.hashCode());
+            // implies you can use this key for session id.    
+            if (!filterMap.containsKey(s)) break; 
+        }
+
+        if (i==MAX_FILTERS) {
+            return FILTER_LIMIT_REACHED;
+        }
+
+        filterMap.put(s, f);
+        if (filterTimeoutMap.containsKey(s))  filterTimeoutMap.remove(s);
+        filterTimeoutMap.put(s, delta);
+
+        // set the timer as there will be no existing timers. 
+        if (filterMap.size() == 1) { 
+            TimeoutFilterTask task = new TimeoutFilterTask(this);
+            Timer timer = new Timer();
+            timer.schedule (task, TIMER_INTERVAL);                
+            // Keep the listeners to avoid race condition
+            //startListening();
+        }   
+        return s;  // the return string is the session ID.
+    }
+
+    public String setupFilter(String sid, 
+                              ConcurrentHashMap<String,String> f, 
+                              int deltaInMilliSeconds) {
+
+        if (sid == null) {
+            // Delta in filter needs to be milliseconds
+            log.debug("Adding new filter: {} for {} ms", f, deltaInMilliSeconds);
+            return addFilter(f, deltaInMilliSeconds);
+        } else {// this is the session id.
+            // we will ignore the hash map features.
+            if (deltaInMilliSeconds > 0)  
+                return refreshFilter(sid, deltaInMilliSeconds);
+            else 
+                return deleteFilter(sid);
+        }
+    }
+
+    public int timeoutFilters() {                
+        Iterator<String> i = filterTimeoutMap.keySet().iterator();
+
+        while(i.hasNext()) {
+            String s = i.next();
+
+            Long t = filterTimeoutMap.get(s);
+            if (t != null) {
+                i.remove();
+                t -= TIMER_INTERVAL;
+                if (t > 0) {
+                    filterTimeoutMap.put(s, t);
+                } else deleteFilter(s);
+            } else deleteFilter(s);
+        }
+        return filterMap.size();
+    }
+
+    protected String refreshFilter(String s, int delta) {
+        Long t = filterTimeoutMap.get(s);
+        if (t != null) {
+            filterTimeoutMap.remove(s);
+            t += delta;  // time is in milliseconds
+            if (t > MAX_FILTER_TIME) t = MAX_FILTER_TIME;
+            filterTimeoutMap.put(s, t);
+            return SUCCESS;
+        } else return FILTER_SESSION_ID_NOT_FOUND;
+    }
+
+    @LogMessageDoc(level="ERROR",
+                   message="Error while terminating packet " +
+                           "filter session",
+                   explanation="An unknown error occurred while terminating " +
+                   		"a packet filter session.",
+                   recommendation=LogMessageDoc.GENERIC_ACTION)
+    protected String deleteFilter(String sessionId) {
+
+        if (filterMap.containsKey(sessionId)) {
+            filterMap.remove(sessionId);
+            try {
+                if (packetClient != null)
+                    packetClient.terminateSession(sessionId);
+            } catch (TException e) {
+                log.error("Error while terminating packet " +
+                		  "filter session", e);
+            }
+            log.debug("Deleted Filter {}.  # of filters" +
+            		 " remaining: {}", sessionId, filterMap.size());
+            return SUCCESS;
+        } else return FILTER_SESSION_ID_NOT_FOUND;
+    }
+
+    public HashSet<String> getMatchedFilters(OFMessage m, FloodlightContext cntx) {  
+
+        HashSet<String> matchedFilters = new HashSet<String>();
+
+        // This default function is written to match on packet ins and 
+        // packet outs.
+        Ethernet eth = null;
+
+        if (m.getType() == OFType.PACKET_IN) {
+            eth = IFloodlightProviderService.bcStore.get(cntx, 
+                    IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+        } else if (m.getType() == OFType.PACKET_OUT) {
+            eth = new Ethernet();
+            OFPacketOut p = (OFPacketOut) m;
+            
+            // No MAC match if packetOut doesn't have the packet.
+            if (p.getPacketData() == null) return null;
+            
+            eth.deserialize(p.getPacketData(), 0, p.getPacketData().length);
+        } else if (m.getType() == OFType.FLOW_MOD) {
+            // flow-mod can't be matched by mac.
+            return null;
+        }
+
+        if (eth == null) return null;
+
+        Iterator<String> filterIt = filterMap.keySet().iterator();
+        while (filterIt.hasNext()) {   // for every filter
+            boolean filterMatch = false;
+            String filterSessionId = filterIt.next();
+            Map<String,String> filter = filterMap.get(filterSessionId);
+
+            // If the filter has empty fields, then it is not considered as a match.
+            if (filter == null || filter.isEmpty()) continue;                  
+            Iterator<String> fieldIt = filter.keySet().iterator();
+            while (fieldIt.hasNext()) {   
+                String filterFieldType = fieldIt.next();
+                String filterFieldValue = filter.get(filterFieldType);
+                if (filterFieldType.equals("mac")) {
+
+                    String srcMac = HexString.toHexString(eth.getSourceMACAddress());
+                    String dstMac = HexString.toHexString(eth.getDestinationMACAddress());
+                    log.debug("srcMac: {}, dstMac: {}", srcMac, dstMac);
+
+                    if (filterFieldValue.equals(srcMac) || 
+                            filterFieldValue.equals(dstMac)){
+                        filterMatch = true; 
+                    } else {
+                        filterMatch = false;
+                        break;
+                    }
+                }
+            }
+            if (filterMatch) {
+                matchedFilters.add(filterSessionId);
+            }
+        }
+
+        if (matchedFilters.isEmpty())
+            return null;    
+        else 
+            return matchedFilters;
+    }
+    
+    @LogMessageDoc(level="ERROR",
+                   message="Failed to establish connection with the " +
+                           "packetstreamer server.",
+                   explanation="The message tracing server is not running " +
+                   		"or otherwise unavailable.",
+                   recommendation=LogMessageDoc.CHECK_CONTROLLER)
+    public boolean connectToPSServer() {
+        int numRetries = 0;
+        if (transport != null && transport.isOpen()) {
+            return true;
+        }
+
+        while (numRetries++ < MaxRetry) {
+            try {
+                transport = new TFramedTransport(new TSocket("localhost", 
+                                                             serverPort));
+                transport.open();
+
+                TProtocol protocol = new  TBinaryProtocol(transport);
+                packetClient = new PacketStreamer.Client(protocol);
+
+                log.debug("Have a connection to packetstreamer server " +
+                		  "localhost:{}", serverPort);
+                break;
+            } catch (TException x) {
+                try {
+                    // Wait for 1 second before retry
+                    if (numRetries < MaxRetry) {
+                        Thread.sleep(1000);
+                    }
+                } catch (Exception e) {}
+            } 
+        }
+
+        if (numRetries > MaxRetry) {
+            log.error("Failed to establish connection with the " +
+            		  "packetstreamer server.");
+            return false;
+        }
+        return true;
+    }
+
+    public void disconnectFromPSServer() {
+        if (transport != null && transport.isOpen()) {
+            log.debug("Close the connection to packetstreamer server" +
+            		  " localhost:{}", serverPort);
+            transport.close();
+        }
+    }
+
+    @Override
+    public String getName() {
+        return "messageFilterManager";
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        return (type == OFType.PACKET_IN && name.equals("devicemanager"));
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return (type == OFType.PACKET_IN && name.equals("learningswitch"));
+    }
+
+    @Override
+    @LogMessageDoc(level="ERROR",
+                   message="Error while sending packet",
+                   explanation="Failed to send a message to the message " +
+                   		"tracing server",
+                   recommendation=LogMessageDoc.CHECK_CONTROLLER)
+    public Command receive(IOFSwitch sw, OFMessage msg, 
+                           FloodlightContext cntx) {
+
+        if (filterMap == null || filterMap.isEmpty()) return Command.CONTINUE;
+
+        HashSet<String> matchedFilters = null;
+        if (log.isDebugEnabled()) {
+            log.debug("Received packet {} from switch {}", 
+                      msg, sw.getStringId());
+        }
+
+        matchedFilters = getMatchedFilters(msg, cntx);
+        if (matchedFilters == null) {
+            return Command.CONTINUE;
+        } else {
+            try {
+                sendPacket(matchedFilters, sw, msg, cntx, true);
+            } catch (Exception e) {
+                log.error("Error while sending packet", e);
+            }
+        }
+        
+        return Command.CONTINUE;
+    }
+
+
+    public class TimeoutFilterTask extends TimerTask {
+
+        OFMessageFilterManager filterManager;
+        ScheduledExecutorService ses = threadPool.getScheduledExecutor();
+
+        public TimeoutFilterTask(OFMessageFilterManager manager) {
+            filterManager = manager;
+        }
+
+        public void run() {
+            int x = filterManager.timeoutFilters();
+
+            if (x > 0) {  // there's at least one filter still active.
+                Timer timer = new Timer();
+                timer.schedule(new TimeoutFilterTask(filterManager), 
+                               TIMER_INTERVAL);
+            } else {
+                // Don't stop the listener to avoid race condition
+                //stopListening();
+            }
+        }
+    }
+
+    public int getNumberOfFilters() {
+        return filterMap.size();
+    }
+
+    public int getMaxFilterSize() {
+        return MAX_FILTERS;
+    }
+
+    protected void sendPacket(HashSet<String> matchedFilters, IOFSwitch sw, 
+            OFMessage msg, FloodlightContext cntx, boolean sync) 
+                    throws TException {
+        Message sendMsg = new Message();
+        Packet packet = new Packet();
+        ChannelBuffer bb;
+        sendMsg.setPacket(packet);
+
+        List<String> sids = new ArrayList<String>(matchedFilters);
+
+        sendMsg.setSessionIDs(sids);
+        packet.setMessageType(OFMessageType.findByValue((msg.getType().ordinal())));
+
+        switch (msg.getType()) {
+            case PACKET_IN:
+                OFPacketIn pktIn = (OFPacketIn)msg;
+                packet.setSwPortTuple(new SwitchPortTuple(sw.getId(), 
+                                                          pktIn.getInPort()));
+                bb = ChannelBuffers.buffer(pktIn.getLength());
+                pktIn.writeTo(bb);
+                packet.setData(OFMessage.getData(sw, msg, cntx));
+                break;
+            case PACKET_OUT:
+                OFPacketOut pktOut = (OFPacketOut)msg;
+                packet.setSwPortTuple(new SwitchPortTuple(sw.getId(), 
+                                                          pktOut.getInPort()));
+                bb = ChannelBuffers.buffer(pktOut.getLength());
+                pktOut.writeTo(bb);
+                packet.setData(OFMessage.getData(sw, msg, cntx));
+                break;
+            case FLOW_MOD:
+                OFFlowMod offlowMod = (OFFlowMod)msg;
+                packet.setSwPortTuple(new SwitchPortTuple(sw.getId(), 
+                                                          offlowMod.
+                                                          getOutPort()));
+                bb = ChannelBuffers.buffer(offlowMod.getLength());
+                offlowMod.writeTo(bb);
+                packet.setData(OFMessage.getData(sw, msg, cntx));
+                break;
+            default:
+                packet.setSwPortTuple(new SwitchPortTuple(sw.getId(), 
+                                                          (short)0));
+                String strData = "Unknown packet";
+                packet.setData(strData.getBytes());
+                break;
+        }
+
+        try {
+            if (transport == null || 
+                !transport.isOpen() || 
+                packetClient == null) {
+                if (!connectToPSServer()) {
+                    // No need to sendPacket if can't make connection to 
+                    // the server
+                    return;
+                }
+            }
+            if (sync) {
+                log.debug("Send packet sync: {}", packet.toString());
+                packetClient.pushMessageSync(sendMsg);
+            } else {
+                log.debug("Send packet sync: ", packet.toString());
+                packetClient.pushMessageAsync(sendMsg);
+            }
+        } catch (Exception e) {
+            log.error("Error while sending packet", e);
+            disconnectFromPSServer();
+            connectToPSServer();
+        }
+    }
+
+    // IFloodlightModule methods
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IOFMessageFilterManagerService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m = 
+            new HashMap<Class<? extends IFloodlightService>,
+                        IFloodlightService>();
+        // We are the class that implements the service
+        m.put(IOFMessageFilterManagerService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        l.add(IThreadPoolService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context) 
+            throws FloodlightModuleException {
+        this.floodlightProvider = 
+                context.getServiceImpl(IFloodlightProviderService.class);
+        this.threadPool =
+                context.getServiceImpl(IThreadPoolService.class);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // This is our 'constructor'
+        
+        filterMap = new ConcurrentHashMap<String, ConcurrentHashMap<String,String>>();
+        filterTimeoutMap = new ConcurrentHashMap<String, Long>();
+        serverPort = 
+                Integer.parseInt(System.getProperty("net.floodlightcontroller." +
+                		"packetstreamer.port", "9090"));
+        
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+        floodlightProvider.addOFMessageListener(OFType.PACKET_OUT, this);
+        floodlightProvider.addOFMessageListener(OFType.FLOW_MOD, this);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageCategory.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageCategory.java
new file mode 100644
index 0000000..e9abf02
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageCategory.java
@@ -0,0 +1,34 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to set the category for log messages for a class
+ * @author readams
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface LogMessageCategory {
+    /**
+     * The category for the log messages for this class
+     * @return
+     */
+    String value() default "Core";
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java
new file mode 100644
index 0000000..80af1a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java
@@ -0,0 +1,71 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to document log messages.  This can be used to generate
+ * documentation on syslog output.
+ * @author readams
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface LogMessageDoc {
+    public static final String NO_ACTION = "No action is required.";
+    public static final String UNKNOWN_ERROR = "An unknown error occured";
+    public static final String GENERIC_ACTION = 
+            "Examine the returned error or exception and take " +
+            "appropriate action.";
+    public static final String CHECK_SWITCH = 
+            "Check the health of the indicated switch.  " + 
+            "Test and troubleshoot IP connectivity.";
+    public static final String CHECK_CONTROLLER = 
+            "Verify controller system health, CPU usage, and memory.  " + 
+            "Rebooting the controller node may help if the controller " +
+            "node is in a distressed state.";
+    public static final String REPORT_CONTROLLER_BUG =
+            "This is likely a defect in the controller.  Please report this " +
+            "issue.  Restarting the controller or switch may help to " +
+            "alleviate.";
+    public static final String REPORT_SWITCH_BUG =
+            "This is likely a defect in the switch.  Please report this " +
+            "issue.  Restarting the controller or switch may help to " +
+            "alleviate.";
+
+    /**
+     * The log level for the log message
+     * @return the log level as a tring
+     */
+    String level() default "INFO";
+    /**
+     * The message that will be printed
+     * @return the message
+     */
+    String message() default UNKNOWN_ERROR;
+    /**
+     * An explanation of the meaning of the log message
+     * @return the explanation
+     */
+    String explanation() default UNKNOWN_ERROR;
+    /**
+     * The recommendated action associated with the log message
+     * @return the recommendation
+     */
+    String recommendation() default NO_ACTION;
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDocs.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDocs.java
new file mode 100644
index 0000000..663baf0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDocs.java
@@ -0,0 +1,36 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to document log messages.  This can be used to generate
+ * documentation on syslog output.  This version allows multiple log messages
+ * to be documentated on an interface.
+ * @author readams
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface LogMessageDocs {
+    /**
+     * A list of {@link LogMessageDoc} elements
+     * @return the list of log message doc
+     */
+    LogMessageDoc[] value();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java
new file mode 100644
index 0000000..7641a7c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java
@@ -0,0 +1,17 @@
+package net.floodlightcontroller.core.internal;
+
+import org.kohsuke.args4j.Option;
+
+/**
+ * Expresses the port settings of OpenFlow controller.
+ */
+public class CmdLineSettings {
+    public static final String DEFAULT_CONFIG_FILE = "config/floodlight.properties";
+
+    @Option(name="-cf", aliases="--configFile", metaVar="FILE", usage="Floodlight configuration file")
+    private String configFile = DEFAULT_CONFIG_FILE;
+    
+    public String getModuleFile() {
+    	return configFile;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/Controller.java
new file mode 100644
index 0000000..90eff6f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/Controller.java
@@ -0,0 +1,2239 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.nio.channels.ClosedChannelException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.Stack;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IInfoProvider;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IListener.Command;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchFilter;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.internal.OFChannelState.HandshakeState;
+import net.floodlightcontroller.core.util.ListenerDispatcher;
+import net.floodlightcontroller.core.web.CoreWebRoutable;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.perfmon.IPktInProcessingTimeService;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceListener;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.OperatorPredicate;
+import net.floodlightcontroller.storage.StorageException;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.ChannelUpstreamHandler;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.jboss.netty.handler.timeout.IdleStateAwareChannelUpstreamHandler;
+import org.jboss.netty.handler.timeout.IdleStateEvent;
+import org.jboss.netty.handler.timeout.ReadTimeoutException;
+import org.openflow.protocol.OFEchoReply;
+import org.openflow.protocol.OFError;
+import org.openflow.protocol.OFError.OFBadActionCode;
+import org.openflow.protocol.OFError.OFBadRequestCode;
+import org.openflow.protocol.OFError.OFErrorType;
+import org.openflow.protocol.OFError.OFFlowModFailedCode;
+import org.openflow.protocol.OFError.OFHelloFailedCode;
+import org.openflow.protocol.OFError.OFPortModFailedCode;
+import org.openflow.protocol.OFError.OFQueueOpFailedCode;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFGetConfigReply;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFPortStatus;
+import org.openflow.protocol.OFPortStatus.OFPortReason;
+import org.openflow.protocol.OFSetConfig;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.OFSwitchConfig;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFVendor;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.MessageParseException;
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.protocol.vendor.OFBasicVendorDataType;
+import org.openflow.protocol.vendor.OFBasicVendorId;
+import org.openflow.protocol.vendor.OFVendorId;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.openflow.util.U32;
+import org.openflow.vendor.nicira.OFNiciraVendorData;
+import org.openflow.vendor.nicira.OFRoleReplyVendorData;
+import org.openflow.vendor.nicira.OFRoleRequestVendorData;
+import org.openflow.vendor.nicira.OFRoleVendorData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * The main controller class.  Handles all setup and network listeners
+ */
+public class Controller implements IFloodlightProviderService, 
+            IStorageSourceListener {
+    
+    protected static Logger log = LoggerFactory.getLogger(Controller.class);
+
+    private static final String ERROR_DATABASE = 
+            "The controller could not communicate with the system database.";
+    
+    protected BasicFactory factory;
+    protected ConcurrentMap<OFType,
+                            ListenerDispatcher<OFType,IOFMessageListener>> 
+                                messageListeners;
+    // The activeSwitches map contains only those switches that are actively
+    // being controlled by us -- it doesn't contain switches that are
+    // in the slave role
+    protected ConcurrentHashMap<Long, IOFSwitch> activeSwitches;
+    // connectedSwitches contains all connected switches, including ones where
+    // we're a slave controller. We need to keep track of them so that we can
+    // send role request messages to switches when our role changes to master
+    // We add a switch to this set after it successfully completes the
+    // handshake. Access to this Set needs to be synchronized with roleChanger
+    protected HashSet<OFSwitchImpl> connectedSwitches;
+    
+    // The controllerNodeIPsCache maps Controller IDs to their IP address. 
+    // It's only used by handleControllerNodeIPsChanged
+    protected HashMap<String, String> controllerNodeIPsCache;
+    
+    protected Set<IOFSwitchListener> switchListeners;
+    protected Set<IHAListener> haListeners;
+    protected Map<String, List<IInfoProvider>> providerMap;
+    protected BlockingQueue<IUpdate> updates;
+    
+    // Module dependencies
+    protected IRestApiService restApi;
+    protected ICounterStoreService counterStore = null;
+    protected IStorageSourceService storageSource;
+    protected IPktInProcessingTimeService pktinProcTime;
+    protected IThreadPoolService threadPool;
+    
+    // Configuration options
+    protected int openFlowPort = 6633;
+    protected int workerThreads = 0;
+    // The id for this controller node. Should be unique for each controller
+    // node in a controller cluster.
+    protected String controllerId = "localhost";
+    
+    // The current role of the controller.
+    // If the controller isn't configured to support roles, then this is null.
+    protected Role role;
+    // A helper that handles sending and timeout handling for role requests
+    protected RoleChanger roleChanger;
+    
+    // Start time of the controller
+    protected long systemStartTime;
+    
+    // Flag to always flush flow table on switch reconnect (HA or otherwise)
+    protected boolean alwaysClearFlowsOnSwAdd = false;
+    
+    // Storage table names
+    protected static final String CONTROLLER_TABLE_NAME = "controller_controller";
+    protected static final String CONTROLLER_ID = "id";
+    
+    protected static final String SWITCH_TABLE_NAME = "controller_switch";
+    protected static final String SWITCH_DATAPATH_ID = "dpid";
+    protected static final String SWITCH_SOCKET_ADDRESS = "socket_address";
+    protected static final String SWITCH_IP = "ip";
+    protected static final String SWITCH_CONTROLLER_ID = "controller_id";
+    protected static final String SWITCH_ACTIVE = "active";
+    protected static final String SWITCH_CONNECTED_SINCE = "connected_since";
+    protected static final String SWITCH_CAPABILITIES = "capabilities";
+    protected static final String SWITCH_BUFFERS = "buffers";
+    protected static final String SWITCH_TABLES = "tables";
+    protected static final String SWITCH_ACTIONS = "actions";
+
+    protected static final String SWITCH_CONFIG_TABLE_NAME = "controller_switchconfig";
+    protected static final String SWITCH_CONFIG_CORE_SWITCH = "core_switch";
+    
+    protected static final String PORT_TABLE_NAME = "controller_port";
+    protected static final String PORT_ID = "id";
+    protected static final String PORT_SWITCH = "switch_id";
+    protected static final String PORT_NUMBER = "number";
+    protected static final String PORT_HARDWARE_ADDRESS = "hardware_address";
+    protected static final String PORT_NAME = "name";
+    protected static final String PORT_CONFIG = "config";
+    protected static final String PORT_STATE = "state";
+    protected static final String PORT_CURRENT_FEATURES = "current_features";
+    protected static final String PORT_ADVERTISED_FEATURES = "advertised_features";
+    protected static final String PORT_SUPPORTED_FEATURES = "supported_features";
+    protected static final String PORT_PEER_FEATURES = "peer_features";
+    
+    protected static final String CONTROLLER_INTERFACE_TABLE_NAME = "controller_controllerinterface";
+    protected static final String CONTROLLER_INTERFACE_ID = "id";
+    protected static final String CONTROLLER_INTERFACE_CONTROLLER_ID = "controller_id";
+    protected static final String CONTROLLER_INTERFACE_TYPE = "type";
+    protected static final String CONTROLLER_INTERFACE_NUMBER = "number";
+    protected static final String CONTROLLER_INTERFACE_DISCOVERED_IP = "discovered_ip";
+    
+    
+    
+    // Perf. related configuration
+    protected static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024;
+    protected static final int BATCH_MAX_SIZE = 100;
+    protected static final boolean ALWAYS_DECODE_ETH = true;
+
+    /**
+     *  Updates handled by the main loop 
+     */
+    protected interface IUpdate {
+        /** 
+         * Calls the appropriate listeners
+         */
+        public void dispatch();
+    }
+    public enum SwitchUpdateType {
+        ADDED,
+        REMOVED,
+        PORTCHANGED
+    }
+    /**
+     * Update message indicating a switch was added or removed 
+     */
+    protected class SwitchUpdate implements IUpdate {
+        public IOFSwitch sw;
+        public SwitchUpdateType switchUpdateType;
+        public SwitchUpdate(IOFSwitch sw, SwitchUpdateType switchUpdateType) {
+            this.sw = sw;
+            this.switchUpdateType = switchUpdateType;
+        }
+        public void dispatch() {
+            if (log.isTraceEnabled()) {
+                log.trace("Dispatching switch update {} {}",
+                        sw, switchUpdateType);
+            }
+            if (switchListeners != null) {
+                for (IOFSwitchListener listener : switchListeners) {
+                    switch(switchUpdateType) {
+                        case ADDED:
+                            listener.addedSwitch(sw);
+                            break;
+                        case REMOVED:
+                            listener.removedSwitch(sw);
+                            break;
+                        case PORTCHANGED:
+                            listener.switchPortChanged(sw.getId());
+                            break;
+                    }
+                }
+            }
+        }
+    }
+    
+    /**
+     * Update message indicating controller's role has changed
+     */
+    protected class HARoleUpdate implements IUpdate {
+        public Role oldRole;
+        public Role newRole;
+        public HARoleUpdate(Role newRole, Role oldRole) {
+            this.oldRole = oldRole;
+            this.newRole = newRole;
+        }
+        public void dispatch() {
+            // Make sure that old and new roles are different.
+            if (oldRole == newRole) {
+                if (log.isTraceEnabled()) {
+                    log.trace("HA role update ignored as the old and " +
+                              "new roles are the same. newRole = {}" +
+                              "oldRole = {}", newRole, oldRole);
+                }
+                return;
+            }
+            if (log.isTraceEnabled()) {
+                log.trace("Dispatching HA Role update newRole = {}, oldRole = {}",
+                          newRole, oldRole);
+            }
+            if (haListeners != null) {
+                for (IHAListener listener : haListeners) {
+                        listener.roleChanged(oldRole, newRole);
+                }
+            }
+        }
+    }
+    
+    /**
+     * Update message indicating
+     * IPs of controllers in controller cluster have changed.
+     */
+    protected class HAControllerNodeIPUpdate implements IUpdate {
+        public Map<String,String> curControllerNodeIPs;
+        public Map<String,String> addedControllerNodeIPs;
+        public Map<String,String> removedControllerNodeIPs;
+        public HAControllerNodeIPUpdate(
+                HashMap<String,String> curControllerNodeIPs,  
+                HashMap<String,String> addedControllerNodeIPs,  
+                HashMap<String,String> removedControllerNodeIPs) {
+            this.curControllerNodeIPs = curControllerNodeIPs;
+            this.addedControllerNodeIPs = addedControllerNodeIPs;
+            this.removedControllerNodeIPs = removedControllerNodeIPs;
+        }
+        public void dispatch() {
+            if (log.isTraceEnabled()) {
+                log.trace("Dispatching HA Controller Node IP update "
+                        + "curIPs = {}, addedIPs = {}, removedIPs = {}",
+                        new Object[] { curControllerNodeIPs, addedControllerNodeIPs,
+                            removedControllerNodeIPs }
+                        );
+            }
+            if (haListeners != null) {
+                for (IHAListener listener: haListeners) {
+                    listener.controllerNodeIPsChanged(curControllerNodeIPs,
+                            addedControllerNodeIPs, removedControllerNodeIPs);
+                }
+            }
+        }
+    }
+    
+    // ***************
+    // Getters/Setters
+    // ***************
+    
+    public void setStorageSourceService(IStorageSourceService storageSource) {
+        this.storageSource = storageSource;
+    }
+    
+    public void setCounterStore(ICounterStoreService counterStore) {
+        this.counterStore = counterStore;
+    }
+    
+    public void setPktInProcessingService(IPktInProcessingTimeService pits) {
+        this.pktinProcTime = pits;
+    }
+    
+    public void setRestApiService(IRestApiService restApi) {
+        this.restApi = restApi;
+    }
+    
+    public void setThreadPoolService(IThreadPoolService tp) {
+        this.threadPool = tp;
+    }
+
+    @Override
+    public Role getRole() {
+        synchronized(roleChanger) {
+            return role;
+        }
+    }
+    
+    @Override
+    public void setRole(Role role) {
+        if (role == null) throw new NullPointerException("Role can not be null.");
+        if (role == Role.MASTER && this.role == Role.SLAVE) {
+            // Reset db state to Inactive for all switches. 
+            updateAllInactiveSwitchInfo();
+        }
+        
+        // Need to synchronize to ensure a reliable ordering on role request
+        // messages send and to ensure the list of connected switches is stable
+        // RoleChanger will handle the actual sending of the message and 
+        // timeout handling
+        // @see RoleChanger
+        synchronized(roleChanger) {
+            if (role.equals(this.role)) {
+                log.debug("Ignoring role change: role is already {}", role);
+                return;
+            }
+
+            Role oldRole = this.role;
+            this.role = role;
+            
+            log.debug("Submitting role change request to role {}", role);
+            roleChanger.submitRequest(connectedSwitches, role);
+            
+            // Enqueue an update for our listeners.
+            try {
+                this.updates.put(new HARoleUpdate(role, oldRole));
+            } catch (InterruptedException e) {
+                log.error("Failure adding update to queue", e);
+            }
+        }
+    }
+    
+    
+    
+    // **********************
+    // ChannelUpstreamHandler
+    // **********************
+    
+    /**
+     * Return a new channel handler for processing a switch connections
+     * @param state The channel state object for the connection
+     * @return the new channel handler
+     */
+    protected ChannelUpstreamHandler getChannelHandler(OFChannelState state) {
+        return new OFChannelHandler(state);
+    }
+    
+    /**
+     * Channel handler deals with the switch connection and dispatches
+     * switch messages to the appropriate locations.
+     * @author readams
+     */
+    protected class OFChannelHandler 
+        extends IdleStateAwareChannelUpstreamHandler {
+        protected OFSwitchImpl sw;
+        protected OFChannelState state;
+        
+        public OFChannelHandler(OFChannelState state) {
+            this.state = state;
+        }
+
+        @Override
+        @LogMessageDoc(message="New switch connection from {ip address}",
+                       explanation="A new switch has connected from the " + 
+                                "specified IP address")
+        public void channelConnected(ChannelHandlerContext ctx,
+                                     ChannelStateEvent e) throws Exception {
+            log.info("New switch connection from {}",
+                     e.getChannel().getRemoteAddress());
+            
+            sw = new OFSwitchImpl();
+            sw.setChannel(e.getChannel());
+            sw.setFloodlightProvider(Controller.this);
+            sw.setThreadPoolService(threadPool);
+            
+            List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+            msglist.add(factory.getMessage(OFType.HELLO));
+            e.getChannel().write(msglist);
+
+        }
+
+        @Override
+        @LogMessageDoc(message="Disconnected switch {switch information}",
+                       explanation="The specified switch has disconnected.")
+        public void channelDisconnected(ChannelHandlerContext ctx,
+                                        ChannelStateEvent e) throws Exception {
+            if (sw != null && state.hsState == HandshakeState.READY) {
+                if (activeSwitches.containsKey(sw.getId())) {
+                    // It's safe to call removeSwitch even though the map might
+                    // not contain this particular switch but another with the 
+                    // same DPID
+                    removeSwitch(sw);
+                }
+                synchronized(roleChanger) {
+                    connectedSwitches.remove(sw);
+                }
+                sw.setConnected(false);
+            }
+            log.info("Disconnected switch {}", sw);
+        }
+
+        @Override
+        @LogMessageDocs({
+            @LogMessageDoc(level="ERROR",
+                    message="Disconnecting switch {switch} due to read timeout",
+                    explanation="The connected switch has failed to send any " + 
+                                "messages or respond to echo requests",
+                    recommendation=LogMessageDoc.CHECK_SWITCH),
+            @LogMessageDoc(level="ERROR",
+                    message="Disconnecting switch {switch}: failed to " + 
+                            "complete handshake",
+                    explanation="The switch did not respond correctly " + 
+                                "to handshake messages",
+                    recommendation=LogMessageDoc.CHECK_SWITCH),
+            @LogMessageDoc(level="ERROR",
+                    message="Disconnecting switch {switch} due to IO Error: {}",
+                    explanation="There was an error communicating with the switch",
+                    recommendation=LogMessageDoc.CHECK_SWITCH),
+            @LogMessageDoc(level="ERROR",
+                    message="Disconnecting switch {switch} due to switch " + 
+                            "state error: {error}",
+                    explanation="The switch sent an unexpected message",
+                    recommendation=LogMessageDoc.CHECK_SWITCH),
+            @LogMessageDoc(level="ERROR",
+                    message="Disconnecting switch {switch} due to " +
+                            "message parse failure",
+                    explanation="Could not parse a message from the switch",
+                    recommendation=LogMessageDoc.CHECK_SWITCH),
+            @LogMessageDoc(level="ERROR",
+                    message="Terminating controller due to storage exception",
+                    explanation=ERROR_DATABASE,
+                    recommendation=LogMessageDoc.CHECK_CONTROLLER),
+            @LogMessageDoc(level="ERROR",
+                    message="Could not process message: queue full",
+                    explanation="OpenFlow messages are arriving faster than " +
+                                " the controller can process them.",
+                    recommendation=LogMessageDoc.CHECK_CONTROLLER),
+            @LogMessageDoc(level="ERROR",
+                    message="Error while processing message " +
+                            "from switch {switch} {cause}",
+                    explanation="An error occurred processing the switch message",
+                    recommendation=LogMessageDoc.GENERIC_ACTION)
+        })
+        public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
+                throws Exception {
+            if (e.getCause() instanceof ReadTimeoutException) {
+                // switch timeout
+                log.error("Disconnecting switch {} due to read timeout", sw);
+                ctx.getChannel().close();
+            } else if (e.getCause() instanceof HandshakeTimeoutException) {
+                log.error("Disconnecting switch {}: failed to complete handshake", 
+                          sw);
+                ctx.getChannel().close();
+            } else if (e.getCause() instanceof ClosedChannelException) {
+                //log.warn("Channel for sw {} already closed", sw);
+            } else if (e.getCause() instanceof IOException) {
+                log.error("Disconnecting switch {} due to IO Error: {}",
+                          sw, e.getCause().getMessage());
+                ctx.getChannel().close();
+            } else if (e.getCause() instanceof SwitchStateException) {
+                log.error("Disconnecting switch {} due to switch state error: {}", 
+                          sw, e.getCause().getMessage());
+                ctx.getChannel().close();
+            } else if (e.getCause() instanceof MessageParseException) {
+                log.error("Disconnecting switch " + sw +
+                          " due to message parse failure", 
+                          e.getCause());
+                ctx.getChannel().close();
+            } else if (e.getCause() instanceof StorageException) {
+                log.error("Terminating controller due to storage exception", 
+                          e.getCause());
+                terminate();
+            } else if (e.getCause() instanceof RejectedExecutionException) {
+                log.warn("Could not process message: queue full");
+            } else {
+                log.error("Error while processing message from switch " + sw,
+                          e.getCause());
+                ctx.getChannel().close();
+            }
+        }
+
+        @Override
+        public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e)
+                throws Exception {
+            List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+            msglist.add(factory.getMessage(OFType.ECHO_REQUEST));
+            e.getChannel().write(msglist);
+        }
+
+        @Override
+        public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
+                throws Exception {
+            if (e.getMessage() instanceof List) {
+                @SuppressWarnings("unchecked")
+                List<OFMessage> msglist = (List<OFMessage>)e.getMessage();
+
+                for (OFMessage ofm : msglist) {
+                    try {
+                        processOFMessage(ofm);
+                    }
+                    catch (Exception ex) {
+                        // We are the last handler in the stream, so run the 
+                        // exception through the channel again by passing in 
+                        // ctx.getChannel().
+                        Channels.fireExceptionCaught(ctx.getChannel(), ex);
+                    }
+                }
+
+                // Flush all flow-mods/packet-out generated from this "train"
+                OFSwitchImpl.flush_all();
+            }
+        }
+        
+        /**
+         * Process the request for the switch description
+         */
+        @LogMessageDoc(level="ERROR",
+                message="Exception in reading description " + 
+                        " during handshake {exception}",
+                explanation="Could not process the switch description string",
+                recommendation=LogMessageDoc.CHECK_SWITCH)
+        void processSwitchDescReply() {
+            try {
+                // Read description, if it has been updated
+                @SuppressWarnings("unchecked")
+                Future<List<OFStatistics>> desc_future =
+                    (Future<List<OFStatistics>>)sw.
+                        getAttribute(IOFSwitch.SWITCH_DESCRIPTION_FUTURE);
+                List<OFStatistics> values = 
+                        desc_future.get(0, TimeUnit.MILLISECONDS);
+                if (values != null) {
+                    OFDescriptionStatistics description = 
+                            new OFDescriptionStatistics();
+                    ChannelBuffer data = 
+                            ChannelBuffers.buffer(description.getLength());
+                    for (OFStatistics f : values) {
+                        f.writeTo(data);
+                        description.readFrom(data);
+                        break; // SHOULD be a list of length 1
+                    }
+                    sw.setAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA, 
+                                    description);
+                    sw.setSwitchProperties(description);
+                    data = null;
+
+                    // At this time, also set other switch properties from storage
+                    boolean is_core_switch = false;
+                    IResultSet resultSet = null;
+                    try {
+                        String swid = sw.getStringId();
+                        resultSet = 
+                                storageSource.getRow(SWITCH_CONFIG_TABLE_NAME, swid);
+                        for (Iterator<IResultSet> it = 
+                                resultSet.iterator(); it.hasNext();) {
+                            // In case of multiple rows, use the status
+                            // in last row?
+                            Map<String, Object> row = it.next().getRow();
+                            if (row.containsKey(SWITCH_CONFIG_CORE_SWITCH)) {
+                                if (log.isDebugEnabled()) {
+                                    log.debug("Reading SWITCH_IS_CORE_SWITCH " + 
+                                              "config for switch={}, is-core={}",
+                                              sw, row.get(SWITCH_CONFIG_CORE_SWITCH));
+                                }
+                                String ics = 
+                                        (String)row.get(SWITCH_CONFIG_CORE_SWITCH);
+                                is_core_switch = ics.equals("true");
+                            }
+                        }
+                    }
+                    finally {
+                        if (resultSet != null)
+                            resultSet.close();
+                    }
+                    if (is_core_switch) {
+                        sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, 
+                                        new Boolean(true));
+                    }
+                }
+                sw.removeAttribute(IOFSwitch.SWITCH_DESCRIPTION_FUTURE);
+                state.hasDescription = true;
+                checkSwitchReady();
+            }
+            catch (InterruptedException ex) {
+                // Ignore
+            }
+            catch (TimeoutException ex) {
+                // Ignore
+            } catch (Exception ex) {
+                log.error("Exception in reading description " + 
+                          " during handshake", ex);
+            }
+        }
+
+        /**
+         * Send initial switch setup information that we need before adding
+         * the switch
+         * @throws IOException
+         */
+        void sendHelloConfiguration() throws IOException {
+            // Send initial Features Request
+            sw.write(factory.getMessage(OFType.FEATURES_REQUEST), null);
+        }
+        
+        /**
+         * Send the configuration requests we can only do after we have
+         * the features reply
+         * @throws IOException
+         */
+        void sendFeatureReplyConfiguration() throws IOException {
+            // Ensure we receive the full packet via PacketIn
+            OFSetConfig config = (OFSetConfig) factory
+                    .getMessage(OFType.SET_CONFIG);
+            config.setMissSendLength((short) 0xffff)
+            .setLengthU(OFSwitchConfig.MINIMUM_LENGTH);
+            sw.write(config, null);
+            sw.write(factory.getMessage(OFType.GET_CONFIG_REQUEST),
+                    null);
+
+            // Get Description to set switch-specific flags
+            OFStatisticsRequest req = new OFStatisticsRequest();
+            req.setStatisticType(OFStatisticsType.DESC);
+            req.setLengthU(req.getLengthU());
+            Future<List<OFStatistics>> dfuture = 
+                    sw.getStatistics(req);
+            sw.setAttribute(IOFSwitch.SWITCH_DESCRIPTION_FUTURE,
+                    dfuture);
+
+        }
+        
+        protected void checkSwitchReady() {
+            if (state.hsState == HandshakeState.FEATURES_REPLY &&
+                    state.hasDescription && state.hasGetConfigReply) {
+                
+                state.hsState = HandshakeState.READY;
+                
+                synchronized(roleChanger) {
+                    // We need to keep track of all of the switches that are connected
+                    // to the controller, in any role, so that we can later send the 
+                    // role request messages when the controller role changes.
+                    // We need to be synchronized while doing this: we must not 
+                    // send a another role request to the connectedSwitches until
+                    // we were able to add this new switch to connectedSwitches 
+                    // *and* send the current role to the new switch.
+                    connectedSwitches.add(sw);
+                    
+                    if (role != null) {
+                        // Send a role request if role support is enabled for the controller
+                        // This is a probe that we'll use to determine if the switch
+                        // actually supports the role request message. If it does we'll
+                        // get back a role reply message. If it doesn't we'll get back an
+                        // OFError message. 
+                        // If role is MASTER we will promote switch to active
+                        // list when we receive the switch's role reply messages
+                        log.debug("This controller's role is {}, " + 
+                                "sending initial role request msg to {}",
+                                role, sw);
+                        Collection<OFSwitchImpl> swList = new ArrayList<OFSwitchImpl>(1);
+                        swList.add(sw);
+                        roleChanger.submitRequest(swList, role);
+                    } 
+                    else {
+                        // Role supported not enabled on controller (for now)
+                        // automatically promote switch to active state. 
+                        log.debug("This controller's role is null, " + 
+                                "not sending role request msg to {}",
+                                role, sw);
+                        // Need to clear FlowMods before we add the switch
+                        // and dispatch updates otherwise we have a race condition.
+                        sw.clearAllFlowMods();
+                        addSwitch(sw);
+                        state.firstRoleReplyReceived = true;
+                    }
+                }
+            }
+        }
+                
+        /* Handle a role reply message we received from the switch. Since
+         * netty serializes message dispatch we don't need to synchronize 
+         * against other receive operations from the same switch, so no need
+         * to synchronize addSwitch(), removeSwitch() operations from the same
+         * connection. 
+         * FIXME: However, when a switch with the same DPID connects we do
+         * need some synchronization. However, handling switches with same
+         * DPID needs to be revisited anyways (get rid of r/w-lock and synchronous
+         * removedSwitch notification):1
+         * 
+         */
+        @LogMessageDoc(level="ERROR",
+                message="Invalid role value in role reply message",
+                explanation="Was unable to set the HA role (master or slave) " +
+                        "for the controller.",
+                recommendation=LogMessageDoc.CHECK_CONTROLLER)
+        protected void handleRoleReplyMessage(OFVendor vendorMessage,
+                                    OFRoleReplyVendorData roleReplyVendorData) {
+            // Map from the role code in the message to our role enum
+            int nxRole = roleReplyVendorData.getRole();
+            Role role = null;
+            switch (nxRole) {
+                case OFRoleVendorData.NX_ROLE_OTHER:
+                    role = Role.EQUAL;
+                    break;
+                case OFRoleVendorData.NX_ROLE_MASTER:
+                    role = Role.MASTER;
+                    break;
+                case OFRoleVendorData.NX_ROLE_SLAVE:
+                    role = Role.SLAVE;
+                    break;
+                default:
+                    log.error("Invalid role value in role reply message");
+                    sw.getChannel().close();
+                    return;
+            }
+            
+            log.debug("Handling role reply for role {} from {}. " +
+                      "Controller's role is {} ", 
+                      new Object[] { role, sw, Controller.this.role} 
+                      );
+            
+            sw.deliverRoleReply(vendorMessage.getXid(), role);
+            
+            boolean isActive = activeSwitches.containsKey(sw.getId());
+            if (!isActive && sw.isActive()) {
+                // Transition from SLAVE to MASTER.
+                
+                if (!state.firstRoleReplyReceived || 
+                    getAlwaysClearFlowsOnSwAdd()) {
+                    // This is the first role-reply message we receive from
+                    // this switch or roles were disabled when the switch
+                    // connected: 
+                    // Delete all pre-existing flows for new connections to 
+                    // the master
+                    //
+                    // FIXME: Need to think more about what the test should 
+                    // be for when we flush the flow-table? For example, 
+                    // if all the controllers are temporarily in the backup 
+                    // role (e.g. right after a failure of the master 
+                    // controller) at the point the switch connects, then 
+                    // all of the controllers will initially connect as 
+                    // backup controllers and not flush the flow-table. 
+                    // Then when one of them is promoted to master following
+                    // the master controller election the flow-table
+                    // will still not be flushed because that's treated as 
+                    // a failover event where we don't want to flush the 
+                    // flow-table. The end result would be that the flow 
+                    // table for a newly connected switch is never
+                    // flushed. Not sure how to handle that case though...
+                    sw.clearAllFlowMods();
+                    log.debug("First role reply from master switch {}, " +
+                              "clear FlowTable to active switch list",
+                             HexString.toHexString(sw.getId()));
+                }
+                
+                // Some switches don't seem to update us with port
+                // status messages while in slave role.
+                readSwitchPortStateFromStorage(sw);                
+                
+                // Only add the switch to the active switch list if 
+                // we're not in the slave role. Note that if the role 
+                // attribute is null, then that means that the switch 
+                // doesn't support the role request messages, so in that
+                // case we're effectively in the EQUAL role and the 
+                // switch should be included in the active switch list.
+                addSwitch(sw);
+                log.debug("Added master switch {} to active switch list",
+                         HexString.toHexString(sw.getId()));
+
+            } 
+            else if (isActive && !sw.isActive()) {
+                // Transition from MASTER to SLAVE: remove switch 
+                // from active switch list. 
+                log.debug("Removed slave switch {} from active switch" +
+                          " list", HexString.toHexString(sw.getId()));
+                removeSwitch(sw);
+            }
+            
+            // Indicate that we have received a role reply message. 
+            state.firstRoleReplyReceived = true;
+        }
+
+        protected boolean handleVendorMessage(OFVendor vendorMessage) {
+            boolean shouldHandleMessage = false;
+            int vendor = vendorMessage.getVendor();
+            switch (vendor) {
+                case OFNiciraVendorData.NX_VENDOR_ID:
+                    OFNiciraVendorData niciraVendorData =
+                        (OFNiciraVendorData)vendorMessage.getVendorData();
+                    int dataType = niciraVendorData.getDataType();
+                    switch (dataType) {
+                        case OFRoleReplyVendorData.NXT_ROLE_REPLY:
+                            OFRoleReplyVendorData roleReplyVendorData =
+                                    (OFRoleReplyVendorData) niciraVendorData;
+                            handleRoleReplyMessage(vendorMessage, 
+                                                   roleReplyVendorData);
+                            break;
+                        default:
+                            log.warn("Unhandled Nicira VENDOR message; " +
+                                     "data type = {}", dataType);
+                            break;
+                    }
+                    break;
+                default:
+                    log.warn("Unhandled VENDOR message; vendor id = {}", vendor);
+                    break;
+            }
+            
+            return shouldHandleMessage;
+        }
+
+        /**
+         * Dispatch an Openflow message from a switch to the appropriate
+         * handler.
+         * @param m The message to process
+         * @throws IOException
+         * @throws SwitchStateException 
+         */
+        @LogMessageDocs({
+            @LogMessageDoc(level="WARN",
+                    message="Config Reply from {switch} has " +
+                            "miss length set to {length}",
+                    explanation="The controller requires that the switch " +
+                            "use a miss length of 0xffff for correct " +
+                            "function",
+                    recommendation="Use a different switch to ensure " +
+                            "correct function"),
+            @LogMessageDoc(level="WARN",
+                    message="Received ERROR from sw {switch} that "
+                            +"indicates roles are not supported "
+                            +"but we have received a valid "
+                            +"role reply earlier",
+                    explanation="The switch sent a confusing message to the" +
+                            "controller")
+        })
+        protected void processOFMessage(OFMessage m)
+                throws IOException, SwitchStateException {
+            boolean shouldHandleMessage = false;
+            
+            switch (m.getType()) {
+                case HELLO:
+                    if (log.isTraceEnabled())
+                        log.trace("HELLO from {}", sw);
+                    
+                    if (state.hsState.equals(HandshakeState.START)) {
+                        state.hsState = HandshakeState.HELLO;
+                        sendHelloConfiguration();
+                    } else {
+                        throw new SwitchStateException("Unexpected HELLO from " 
+                                                       + sw);
+                    }
+                    break;
+                case ECHO_REQUEST:
+                    OFEchoReply reply =
+                        (OFEchoReply) factory.getMessage(OFType.ECHO_REPLY);
+                    reply.setXid(m.getXid());
+                    sw.write(reply, null);
+                    break;
+                case ECHO_REPLY:
+                    break;
+                case FEATURES_REPLY:
+                    if (log.isTraceEnabled())
+                        log.trace("Features Reply from {}", sw);
+                    
+                    sw.setFeaturesReply((OFFeaturesReply) m);
+                    if (state.hsState.equals(HandshakeState.HELLO)) {
+                        sendFeatureReplyConfiguration();
+                        state.hsState = HandshakeState.FEATURES_REPLY;
+                        // uncomment to enable "dumb" switches like cbench
+                        // state.hsState = HandshakeState.READY;
+                        // addSwitch(sw);
+                    } else {
+                        // return results to rest api caller
+                        sw.deliverOFFeaturesReply(m);
+                        // update database */
+                        updateActiveSwitchInfo(sw);
+                    }
+                    break;
+                case GET_CONFIG_REPLY:
+                    if (log.isTraceEnabled())
+                        log.trace("Get config reply from {}", sw);
+                    
+                    if (!state.hsState.equals(HandshakeState.FEATURES_REPLY)) {
+                        String em = "Unexpected GET_CONFIG_REPLY from " + sw;
+                        throw new SwitchStateException(em);
+                    }
+                    OFGetConfigReply cr = (OFGetConfigReply) m;
+                    if (cr.getMissSendLength() == (short)0xffff) {
+                        log.trace("Config Reply from {} confirms " + 
+                                  "miss length set to 0xffff", sw);
+                    } else {
+                        log.warn("Config Reply from {} has " +
+                                 "miss length set to {}", 
+                                 sw, cr.getMissSendLength() & 0xffff);
+                    }
+                    state.hasGetConfigReply = true;
+                    checkSwitchReady();
+                    break;
+                case VENDOR:
+                    shouldHandleMessage = handleVendorMessage((OFVendor)m);
+                    break;
+                case ERROR:
+                    // TODO: we need better error handling. Especially for 
+                    // request/reply style message (stats, roles) we should have
+                    // a unified way to lookup the xid in the error message. 
+                    // This will probable involve rewriting the way we handle
+                    // request/reply style messages.
+                    OFError error = (OFError) m;
+                    boolean shouldLogError = true;
+                    // TODO: should we check that firstRoleReplyReceived is false,
+                    // i.e., check only whether the first request fails?
+                    if (sw.checkFirstPendingRoleRequestXid(error.getXid())) {
+                        boolean isBadVendorError =
+                            (error.getErrorType() == OFError.OFErrorType.
+                                    OFPET_BAD_REQUEST.getValue());
+                        // We expect to receive a bad vendor error when 
+                        // we're connected to a switch that doesn't support 
+                        // the Nicira vendor extensions (i.e. not OVS or 
+                        // derived from OVS).  By protocol, it should also be
+                        // BAD_VENDOR, but too many switch implementations
+                        // get it wrong and we can already check the xid()
+                        // so we can ignore the type with confidence that this
+                        // is not a spurious error
+                        shouldLogError = !isBadVendorError;
+                        if (isBadVendorError) {
+                            if (state.firstRoleReplyReceived && (role != null)) {
+                                log.warn("Received ERROR from sw {} that "
+                                          +"indicates roles are not supported "
+                                          +"but we have received a valid "
+                                          +"role reply earlier", sw);
+                            }
+                            state.firstRoleReplyReceived = true;
+                            sw.deliverRoleRequestNotSupported(error.getXid());
+                            synchronized(roleChanger) {
+                                if (sw.role == null && Controller.this.role==Role.SLAVE) {
+                                    // the switch doesn't understand role request
+                                    // messages and the current controller role is
+                                    // slave. We need to disconnect the switch. 
+                                    // @see RoleChanger for rationale
+                                    sw.getChannel().close();
+                                }
+                                else if (sw.role == null) {
+                                    // Controller's role is master: add to
+                                    // active 
+                                    // TODO: check if clearing flow table is
+                                    // right choice here.
+                                    // Need to clear FlowMods before we add the switch
+                                    // and dispatch updates otherwise we have a race condition.
+                                    // TODO: switch update is async. Won't we still have a potential
+                                    //       race condition? 
+                                    sw.clearAllFlowMods();
+                                    addSwitch(sw);
+                                }
+                            }
+                        }
+                        else {
+                            // TODO: Is this the right thing to do if we receive
+                            // some other error besides a bad vendor error? 
+                            // Presumably that means the switch did actually
+                            // understand the role request message, but there 
+                            // was some other error from processing the message.
+                            // OF 1.2 specifies a OFPET_ROLE_REQUEST_FAILED
+                            // error code, but it doesn't look like the Nicira 
+                            // role request has that. Should check OVS source 
+                            // code to see if it's possible for any other errors
+                            // to be returned.
+                            // If we received an error the switch is not
+                            // in the correct role, so we need to disconnect it. 
+                            // We could also resend the request but then we need to
+                            // check if there are other pending request in which
+                            // case we shouldn't resend. If we do resend we need
+                            // to make sure that the switch eventually accepts one
+                            // of our requests or disconnect the switch. This feels
+                            // cumbersome. 
+                            sw.getChannel().close();
+                        }
+                    }
+                    // Once we support OF 1.2, we'd add code to handle it here.
+                    //if (error.getXid() == state.ofRoleRequestXid) {
+                    //}
+                    if (shouldLogError)
+                        logError(sw, error);
+                    break;
+                case STATS_REPLY:
+                    if (state.hsState.ordinal() < 
+                        HandshakeState.FEATURES_REPLY.ordinal()) {
+                        String em = "Unexpected STATS_REPLY from " + sw;
+                        throw new SwitchStateException(em);
+                    }
+                    sw.deliverStatisticsReply(m);
+                    if (sw.hasAttribute(IOFSwitch.SWITCH_DESCRIPTION_FUTURE)) {
+                        processSwitchDescReply();
+                    }
+                    break;
+                case PORT_STATUS:
+                    // We want to update our port state info even if we're in 
+                    // the slave role, but we only want to update storage if 
+                    // we're the master (or equal).
+                    boolean updateStorage = state.hsState.
+                                                equals(HandshakeState.READY) &&
+                                                (sw.getRole() != Role.SLAVE);
+                    handlePortStatusMessage(sw, (OFPortStatus)m, updateStorage);
+                    shouldHandleMessage = true;
+                    break;
+
+                default:
+                    shouldHandleMessage = true;
+                    break;
+            }
+            
+            if (shouldHandleMessage) {
+                sw.getListenerReadLock().lock();
+                try {
+                    if (sw.isConnected()) {
+                        if (!state.hsState.equals(HandshakeState.READY)) {
+                            log.debug("Ignoring message type {} received " + 
+                                      "from switch {} before switch is " + 
+                                      "fully configured.", m.getType(), sw);
+                        }
+                        // Check if the controller is in the slave role for the 
+                        // switch. If it is, then don't dispatch the message to 
+                        // the listeners.
+                        // TODO: Should we dispatch messages that we expect to 
+                        // receive when we're in the slave role, e.g. port 
+                        // status messages? Since we're "hiding" switches from 
+                        // the listeners when we're in the slave role, then it 
+                        // seems a little weird to dispatch port status messages
+                        // to them. On the other hand there might be special 
+                        // modules that care about all of the connected switches
+                        // and would like to receive port status notifications.
+                        else if (sw.getRole() == Role.SLAVE) {
+                            // Don't log message if it's a port status message 
+                            // since we expect to receive those from the switch 
+                            // and don't want to emit spurious messages.
+                            if (m.getType() != OFType.PORT_STATUS) {
+                                log.debug("Ignoring message type {} received " +
+                                        "from switch {} while in the slave role.",
+                                        m.getType(), sw);
+                            }
+                        } else {
+                            handleMessage(sw, m, null);
+                        }
+                    }
+                }
+                finally {
+                    sw.getListenerReadLock().unlock();
+                }
+            }
+        }
+    }
+
+    // ****************
+    // Message handlers
+    // ****************
+    
+    protected void handlePortStatusMessage(IOFSwitch sw,
+                                           OFPortStatus m,
+                                           boolean updateStorage) {
+        short portNumber = m.getDesc().getPortNumber();
+        OFPhysicalPort port = m.getDesc();
+        if (m.getReason() == (byte)OFPortReason.OFPPR_MODIFY.ordinal()) {
+            sw.setPort(port);
+            if (updateStorage)
+                updatePortInfo(sw, port);
+            log.debug("Port #{} modified for {}", portNumber, sw);
+        } else if (m.getReason() == (byte)OFPortReason.OFPPR_ADD.ordinal()) {
+            sw.setPort(port);
+            if (updateStorage)
+                updatePortInfo(sw, port);
+            log.debug("Port #{} added for {}", portNumber, sw);
+        } else if (m.getReason() == 
+                   (byte)OFPortReason.OFPPR_DELETE.ordinal()) {
+            sw.deletePort(portNumber);
+            if (updateStorage)
+                removePortInfo(sw, portNumber);
+            log.debug("Port #{} deleted for {}", portNumber, sw);
+        }
+        SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.PORTCHANGED);
+        try {
+            this.updates.put(update);
+        } catch (InterruptedException e) {
+            log.error("Failure adding update to queue", e);
+        }
+    }
+    
+    /**
+     * flcontext_cache - Keep a thread local stack of contexts
+     */
+    protected static final ThreadLocal<Stack<FloodlightContext>> flcontext_cache =
+        new ThreadLocal <Stack<FloodlightContext>> () {
+            @Override
+            protected Stack<FloodlightContext> initialValue() {
+                return new Stack<FloodlightContext>();
+            }
+        };
+
+    /**
+     * flcontext_alloc - pop a context off the stack, if required create a new one
+     * @return FloodlightContext
+     */
+    protected static FloodlightContext flcontext_alloc() {
+        FloodlightContext flcontext = null;
+
+        if (flcontext_cache.get().empty()) {
+            flcontext = new FloodlightContext();
+        }
+        else {
+            flcontext = flcontext_cache.get().pop();
+        }
+
+        return flcontext;
+    }
+
+    /**
+     * flcontext_free - Free the context to the current thread
+     * @param flcontext
+     */
+    protected void flcontext_free(FloodlightContext flcontext) {
+        flcontext.getStorage().clear();
+        flcontext_cache.get().push(flcontext);
+    }
+
+    /**
+     * Handle replies to certain OFMessages, and pass others off to listeners
+     * @param sw The switch for the message
+     * @param m The message
+     * @param bContext The floodlight context. If null then floodlight context would
+     * be allocated in this function
+     * @throws IOException
+     */
+    @LogMessageDocs({
+        @LogMessageDoc(level="ERROR",
+                message="Ignoring PacketIn (Xid = {xid}) because the data" +
+                        " field is empty.",
+                explanation="The switch sent an improperly-formatted PacketIn" +
+                        " message",
+                recommendation=LogMessageDoc.CHECK_SWITCH),
+        @LogMessageDoc(level="WARN",
+                message="Unhandled OF Message: {} from {}",
+                explanation="The switch sent a message not handled by " +
+                        "the controller")
+    })
+    protected void handleMessage(IOFSwitch sw, OFMessage m,
+                                 FloodlightContext bContext)
+            throws IOException {
+        Ethernet eth = null;
+
+        switch (m.getType()) {
+            case PACKET_IN:
+                OFPacketIn pi = (OFPacketIn)m;
+                
+                if (pi.getPacketData().length <= 0) {
+                    log.error("Ignoring PacketIn (Xid = " + pi.getXid() + 
+                              ") because the data field is empty.");
+                    return;
+                }
+                
+                if (Controller.ALWAYS_DECODE_ETH) {
+                    eth = new Ethernet();
+                    eth.deserialize(pi.getPacketData(), 0,
+                            pi.getPacketData().length);
+                    counterStore.updatePacketInCounters(sw, m, eth);
+                }
+                // fall through to default case...
+
+            default:
+                
+                List<IOFMessageListener> listeners = null;
+                if (messageListeners.containsKey(m.getType())) {
+                    listeners = messageListeners.get(m.getType()).
+                            getOrderedListeners();
+                }
+                        
+                FloodlightContext bc = null;
+                if (listeners != null) {
+                    // Check if floodlight context is passed from the calling 
+                    // function, if so use that floodlight context, otherwise 
+                    // allocate one
+                    if (bContext == null) {
+                        bc = flcontext_alloc();
+                    } else {
+                        bc = bContext;
+                    }
+                    if (eth != null) {
+                        IFloodlightProviderService.bcStore.put(bc, 
+                                IFloodlightProviderService.CONTEXT_PI_PAYLOAD, 
+                                eth);
+                    }
+                    
+                    // Get the starting time (overall and per-component) of 
+                    // the processing chain for this packet if performance
+                    // monitoring is turned on
+                    pktinProcTime.bootstrap(listeners);
+                    pktinProcTime.recordStartTimePktIn();                     
+                    Command cmd;
+                    for (IOFMessageListener listener : listeners) {
+                        if (listener instanceof IOFSwitchFilter) {
+                            if (!((IOFSwitchFilter)listener).isInterested(sw)) {
+                                continue;
+                            }
+                        }
+
+                        pktinProcTime.recordStartTimeComp(listener);
+                        cmd = listener.receive(sw, m, bc);
+                        pktinProcTime.recordEndTimeComp(listener);
+                        
+                        if (Command.STOP.equals(cmd)) {
+                            break;
+                        }
+                    }
+                    pktinProcTime.recordEndTimePktIn(sw, m, bc);
+                } else {
+                    log.warn("Unhandled OF Message: {} from {}", m, sw);
+                }
+                
+                if ((bContext == null) && (bc != null)) flcontext_free(bc);
+        }
+    }
+    
+    /**
+     * Log an OpenFlow error message from a switch
+     * @param sw The switch that sent the error
+     * @param error The error message
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Error {error type} {error code} from {switch}",
+            explanation="The switch responded with an unexpected error" +
+                    "to an OpenFlow message from the controller",
+            recommendation="This could indicate improper network operation. " +
+                    "If the problem persists restarting the switch and " +
+                    "controller may help."
+            )
+    protected void logError(IOFSwitch sw, OFError error) {
+        int etint = 0xffff & error.getErrorType();
+        if (etint < 0 || etint >= OFErrorType.values().length) {
+            log.error("Unknown error code {} from sw {}", etint, sw);
+        }
+        OFErrorType et = OFErrorType.values()[etint];
+        switch (et) {
+            case OFPET_HELLO_FAILED:
+                OFHelloFailedCode hfc = 
+                    OFHelloFailedCode.values()[0xffff & error.getErrorCode()];
+                log.error("Error {} {} from {}", new Object[] {et, hfc, sw});
+                break;
+            case OFPET_BAD_REQUEST:
+                OFBadRequestCode brc = 
+                    OFBadRequestCode.values()[0xffff & error.getErrorCode()];
+                log.error("Error {} {} from {}", new Object[] {et, brc, sw});
+                break;
+            case OFPET_BAD_ACTION:
+                OFBadActionCode bac =
+                    OFBadActionCode.values()[0xffff & error.getErrorCode()];
+                log.error("Error {} {} from {}", new Object[] {et, bac, sw});
+                break;
+            case OFPET_FLOW_MOD_FAILED:
+                OFFlowModFailedCode fmfc =
+                    OFFlowModFailedCode.values()[0xffff & error.getErrorCode()];
+                log.error("Error {} {} from {}", new Object[] {et, fmfc, sw});
+                break;
+            case OFPET_PORT_MOD_FAILED:
+                OFPortModFailedCode pmfc =
+                    OFPortModFailedCode.values()[0xffff & error.getErrorCode()];
+                log.error("Error {} {} from {}", new Object[] {et, pmfc, sw});
+                break;
+            case OFPET_QUEUE_OP_FAILED:
+                OFQueueOpFailedCode qofc =
+                    OFQueueOpFailedCode.values()[0xffff & error.getErrorCode()];
+                log.error("Error {} {} from {}", new Object[] {et, qofc, sw});
+                break;
+            default:
+                break;
+        }
+    }
+    
+    /**
+     * Add a switch to the active switch list and call the switch listeners.
+     * This happens either when a switch first connects (and the controller is
+     * not in the slave role) or when the role of the controller changes from
+     * slave to master.
+     * @param sw the switch that has been added
+     */
+    // TODO: need to rethink locking and the synchronous switch update.
+    //       We can / should also handle duplicate DPIDs in connectedSwitches
+    @LogMessageDoc(level="ERROR",
+            message="New switch added {switch} for already-added switch {switch}",
+            explanation="A switch with the same DPID as another switch " +
+                    "connected to the controller.  This can be caused by " +
+                    "multiple switches configured with the same DPID, or " +
+                    "by a switch reconnected very quickly after " +
+                    "disconnecting.",
+            recommendation="If this happens repeatedly, it is likely there " +
+                    "are switches with duplicate DPIDs on the network.  " +
+                    "Reconfigure the appropriate switches.  If it happens " +
+                    "very rarely, then it is likely this is a transient " +
+                    "network problem that can be ignored."
+            )
+    protected void addSwitch(IOFSwitch sw) {
+        // TODO: is it safe to modify the HashMap without holding 
+        // the old switch's lock?
+        OFSwitchImpl oldSw = (OFSwitchImpl) this.activeSwitches.put(sw.getId(), sw);
+        if (sw == oldSw) {
+            // Note == for object equality, not .equals for value
+            log.info("New add switch for pre-existing switch {}", sw);
+            return;
+        }
+        
+        if (oldSw != null) {
+            oldSw.getListenerWriteLock().lock();
+            try {
+                log.error("New switch added {} for already-added switch {}",
+                          sw, oldSw);
+                // Set the connected flag to false to suppress calling
+                // the listeners for this switch in processOFMessage
+                oldSw.setConnected(false);
+                
+                oldSw.cancelAllStatisticsReplies();
+                
+                updateInactiveSwitchInfo(oldSw);
+    
+                // we need to clean out old switch state definitively 
+                // before adding the new switch
+                // FIXME: It seems not completely kosher to call the
+                // switch listeners here. I thought one of the points of
+                // having the asynchronous switch update mechanism was so
+                // the addedSwitch and removedSwitch were always called
+                // from a single thread to simplify concurrency issues
+                // for the listener.
+                if (switchListeners != null) {
+                    for (IOFSwitchListener listener : switchListeners) {
+                        listener.removedSwitch(oldSw);
+                    }
+                }
+                // will eventually trigger a removeSwitch(), which will cause
+                // a "Not removing Switch ... already removed debug message.
+                // TODO: Figure out a way to handle this that avoids the
+                // spurious debug message.
+                oldSw.getChannel().close();
+            }
+            finally {
+                oldSw.getListenerWriteLock().unlock();
+            }
+        }
+        
+        updateActiveSwitchInfo(sw);
+        SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.ADDED);
+        try {
+            this.updates.put(update);
+        } catch (InterruptedException e) {
+            log.error("Failure adding update to queue", e);
+        }
+    }
+
+    /**
+     * Remove a switch from the active switch list and call the switch listeners.
+     * This happens either when the switch is disconnected or when the
+     * controller's role for the switch changes from master to slave.
+     * @param sw the switch that has been removed
+     */
+    protected void removeSwitch(IOFSwitch sw) {
+        // No need to acquire the listener lock, since
+        // this method is only called after netty has processed all
+        // pending messages
+        log.debug("removeSwitch: {}", sw);
+        if (!this.activeSwitches.remove(sw.getId(), sw) || !sw.isConnected()) {
+            log.debug("Not removing switch {}; already removed", sw);
+            return;
+        }
+        // We cancel all outstanding statistics replies if the switch transition
+        // from active. In the future we might allow statistics requests 
+        // from slave controllers. Then we need to move this cancelation
+        // to switch disconnect
+        sw.cancelAllStatisticsReplies();
+            
+        // FIXME: I think there's a race condition if we call updateInactiveSwitchInfo
+        // here if role support is enabled. In that case if the switch is being
+        // removed because we've been switched to being in the slave role, then I think
+        // it's possible that the new master may have already been promoted to master
+        // and written out the active switch state to storage. If we now execute
+        // updateInactiveSwitchInfo we may wipe out all of the state that was
+        // written out by the new master. Maybe need to revisit how we handle all
+        // of the switch state that's written to storage.
+        
+        updateInactiveSwitchInfo(sw);
+        SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.REMOVED);
+        try {
+            this.updates.put(update);
+        } catch (InterruptedException e) {
+            log.error("Failure adding update to queue", e);
+        }
+    }
+    
+    // ***************
+    // IFloodlightProvider
+    // ***************
+    
+    @Override
+    public synchronized void addOFMessageListener(OFType type, 
+                                                  IOFMessageListener listener) {
+        ListenerDispatcher<OFType, IOFMessageListener> ldd = 
+            messageListeners.get(type);
+        if (ldd == null) {
+            ldd = new ListenerDispatcher<OFType, IOFMessageListener>();
+            messageListeners.put(type, ldd);
+        }
+        ldd.addListener(type, listener);
+    }
+
+    @Override
+    public synchronized void removeOFMessageListener(OFType type,
+                                                     IOFMessageListener listener) {
+        ListenerDispatcher<OFType, IOFMessageListener> ldd = 
+            messageListeners.get(type);
+        if (ldd != null) {
+            ldd.removeListener(listener);
+        }
+    }
+    
+    private void logListeners() {
+        for (Map.Entry<OFType,
+                       ListenerDispatcher<OFType, 
+                                          IOFMessageListener>> entry
+             : messageListeners.entrySet()) {
+            
+            OFType type = entry.getKey();
+            ListenerDispatcher<OFType, IOFMessageListener> ldd = 
+                    entry.getValue();
+            
+            StringBuffer sb = new StringBuffer();
+            sb.append("OFListeners for ");
+            sb.append(type);
+            sb.append(": ");
+            for (IOFMessageListener l : ldd.getOrderedListeners()) {
+                sb.append(l.getName());
+                sb.append(",");
+            }
+            log.debug(sb.toString());            
+        }
+    }
+    
+    public void removeOFMessageListeners(OFType type) {
+        messageListeners.remove(type);
+    }
+
+    @Override
+    public Map<Long, IOFSwitch> getSwitches() {
+        return Collections.unmodifiableMap(this.activeSwitches);
+    }
+
+    @Override
+    public void addOFSwitchListener(IOFSwitchListener listener) {
+        this.switchListeners.add(listener);
+    }
+
+    @Override
+    public void removeOFSwitchListener(IOFSwitchListener listener) {
+        this.switchListeners.remove(listener);
+    }
+
+    @Override
+    public Map<OFType, List<IOFMessageListener>> getListeners() {
+        Map<OFType, List<IOFMessageListener>> lers = 
+            new HashMap<OFType, List<IOFMessageListener>>();
+        for(Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> e : 
+            messageListeners.entrySet()) {
+            lers.put(e.getKey(), e.getValue().getOrderedListeners());
+        }
+        return Collections.unmodifiableMap(lers);
+    }
+    
+    @Override
+    @LogMessageDocs({
+        @LogMessageDoc(message="Failed to inject OFMessage {message} onto " +
+                "a null switch",
+                explanation="Failed to process a message because the switch " +
+                " is no longer connected."),
+        @LogMessageDoc(level="ERROR",
+                message="Error reinjecting OFMessage on switch {switch}",
+                explanation="An I/O error occured while attempting to " +
+                        "process an OpenFlow message",
+                recommendation=LogMessageDoc.CHECK_SWITCH)
+    })
+    public boolean injectOfMessage(IOFSwitch sw, OFMessage msg,
+                                   FloodlightContext bc) {
+        if (sw == null) {
+            log.info("Failed to inject OFMessage {} onto a null switch", msg);
+            return false;
+        }
+        
+        // FIXME: Do we need to be able to inject messages to switches
+        // where we're the slave controller (i.e. they're connected but
+        // not active)?
+        // FIXME: Don't we need synchronization logic here so we're holding
+        // the listener read lock when we call handleMessage? After some
+        // discussions it sounds like the right thing to do here would be to
+        // inject the message as a netty upstream channel event so it goes
+        // through the normal netty event processing, including being
+        // handled 
+        if (!activeSwitches.containsKey(sw.getId())) return false;
+        
+        try {
+            // Pass Floodlight context to the handleMessages()
+            handleMessage(sw, msg, bc);
+        } catch (IOException e) {
+            log.error("Error reinjecting OFMessage on switch {}", 
+                      HexString.toHexString(sw.getId()));
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    @LogMessageDoc(message="Calling System.exit",
+                   explanation="The controller is terminating")
+    public synchronized void terminate() {
+        log.info("Calling System.exit");
+        System.exit(1);
+    }
+    
+    @Override
+    public boolean injectOfMessage(IOFSwitch sw, OFMessage msg) {
+        // call the overloaded version with floodlight context set to null    
+        return injectOfMessage(sw, msg, null);
+    }
+    
+    @Override
+    public void handleOutgoingMessage(IOFSwitch sw, OFMessage m,
+                                      FloodlightContext bc) {
+        if (log.isTraceEnabled()) {
+            String str = OFMessage.getDataAsString(sw, m, bc);
+            log.trace("{}", str);
+        }
+
+        List<IOFMessageListener> listeners = null;
+        if (messageListeners.containsKey(m.getType())) {
+            listeners = 
+                    messageListeners.get(m.getType()).getOrderedListeners();
+        }
+            
+        if (listeners != null) {                
+            for (IOFMessageListener listener : listeners) {
+                if (listener instanceof IOFSwitchFilter) {
+                    if (!((IOFSwitchFilter)listener).isInterested(sw)) {
+                        continue;
+                    }
+                }
+                if (Command.STOP.equals(listener.receive(sw, m, bc))) {
+                    break;
+                }
+            }
+        }
+    }
+
+    @Override
+    public BasicFactory getOFMessageFactory() {
+        return factory;
+    }
+    
+    @Override
+    public String getControllerId() {
+        return controllerId;
+    }
+    
+    // **************
+    // Initialization
+    // **************
+
+    protected void updateAllInactiveSwitchInfo() {
+        if (role == Role.SLAVE) {
+            return;
+        }
+        String controllerId = getControllerId();
+        String[] switchColumns = { SWITCH_DATAPATH_ID,
+                                   SWITCH_CONTROLLER_ID,
+                                   SWITCH_ACTIVE };
+        String[] portColumns = { PORT_ID, PORT_SWITCH };
+        IResultSet switchResultSet = null;
+        try {
+            OperatorPredicate op = 
+                    new OperatorPredicate(SWITCH_CONTROLLER_ID,
+                                          OperatorPredicate.Operator.EQ,
+                                          controllerId);
+            switchResultSet = 
+                    storageSource.executeQuery(SWITCH_TABLE_NAME,
+                                               switchColumns,
+                                               op, null);
+            while (switchResultSet.next()) {
+                IResultSet portResultSet = null;
+                try {
+                    String datapathId =
+                            switchResultSet.getString(SWITCH_DATAPATH_ID);
+                    switchResultSet.setBoolean(SWITCH_ACTIVE, Boolean.FALSE);
+                    op = new OperatorPredicate(PORT_SWITCH, 
+                                               OperatorPredicate.Operator.EQ,
+                                               datapathId);
+                    portResultSet = 
+                            storageSource.executeQuery(PORT_TABLE_NAME,
+                                                       portColumns,
+                                                       op, null);
+                    while (portResultSet.next()) {
+                        portResultSet.deleteRow();
+                    }
+                    portResultSet.save();
+                }
+                finally {
+                    if (portResultSet != null)
+                        portResultSet.close();
+                }
+            }
+            switchResultSet.save();
+        }
+        finally {
+            if (switchResultSet != null)
+                switchResultSet.close();
+        }
+    }
+    
+    protected void updateControllerInfo() {
+        updateAllInactiveSwitchInfo();
+        
+        // Write out the controller info to the storage source
+        Map<String, Object> controllerInfo = new HashMap<String, Object>();
+        String id = getControllerId();
+        controllerInfo.put(CONTROLLER_ID, id);
+        storageSource.updateRow(CONTROLLER_TABLE_NAME, controllerInfo);
+    }
+    
+    protected void updateActiveSwitchInfo(IOFSwitch sw) {
+        if (role == Role.SLAVE) {
+            return;
+        }
+        // Obtain the row info for the switch
+        Map<String, Object> switchInfo = new HashMap<String, Object>();
+        String datapathIdString = sw.getStringId();
+        switchInfo.put(SWITCH_DATAPATH_ID, datapathIdString);
+        String controllerId = getControllerId();
+        switchInfo.put(SWITCH_CONTROLLER_ID, controllerId);
+        Date connectedSince = sw.getConnectedSince();
+        switchInfo.put(SWITCH_CONNECTED_SINCE, connectedSince);
+        Channel channel = sw.getChannel();
+        SocketAddress socketAddress = channel.getRemoteAddress();
+        if (socketAddress != null) {
+            String socketAddressString = socketAddress.toString();
+            switchInfo.put(SWITCH_SOCKET_ADDRESS, socketAddressString);
+            if (socketAddress instanceof InetSocketAddress) {
+                InetSocketAddress inetSocketAddress =
+                        (InetSocketAddress)socketAddress;
+                InetAddress inetAddress = inetSocketAddress.getAddress();
+                String ip = inetAddress.getHostAddress();
+                switchInfo.put(SWITCH_IP, ip);
+            }
+        }
+        
+        // Write out the switch features info
+        long capabilities = U32.f(sw.getCapabilities());
+        switchInfo.put(SWITCH_CAPABILITIES, capabilities);
+        long buffers = U32.f(sw.getBuffers());
+        switchInfo.put(SWITCH_BUFFERS, buffers);
+        long tables = U32.f(sw.getTables());
+        switchInfo.put(SWITCH_TABLES, tables);
+        long actions = U32.f(sw.getActions());
+        switchInfo.put(SWITCH_ACTIONS, actions);
+        switchInfo.put(SWITCH_ACTIVE, Boolean.TRUE);
+        
+        // Update the switch
+        storageSource.updateRowAsync(SWITCH_TABLE_NAME, switchInfo);
+        
+        // Update the ports
+        for (OFPhysicalPort port: sw.getPorts()) {
+            updatePortInfo(sw, port);
+        }
+    }
+    
+    protected void updateInactiveSwitchInfo(IOFSwitch sw) {
+        if (role == Role.SLAVE) {
+            return;
+        }
+        log.debug("Update DB with inactiveSW {}", sw);
+        // Update the controller info in the storage source to be inactive
+        Map<String, Object> switchInfo = new HashMap<String, Object>();
+        String datapathIdString = sw.getStringId();
+        switchInfo.put(SWITCH_DATAPATH_ID, datapathIdString);
+        //switchInfo.put(SWITCH_CONNECTED_SINCE, null);
+        switchInfo.put(SWITCH_ACTIVE, Boolean.FALSE);
+        storageSource.updateRowAsync(SWITCH_TABLE_NAME, switchInfo);
+    }
+
+    protected void updatePortInfo(IOFSwitch sw, OFPhysicalPort port) {
+        if (role == Role.SLAVE) {
+            return;
+        }
+        String datapathIdString = sw.getStringId();
+        Map<String, Object> portInfo = new HashMap<String, Object>();
+        int portNumber = U16.f(port.getPortNumber());
+        String id = datapathIdString + "|" + portNumber;
+        portInfo.put(PORT_ID, id);
+        portInfo.put(PORT_SWITCH, datapathIdString);
+        portInfo.put(PORT_NUMBER, portNumber);
+        byte[] hardwareAddress = port.getHardwareAddress();
+        String hardwareAddressString = HexString.toHexString(hardwareAddress);
+        portInfo.put(PORT_HARDWARE_ADDRESS, hardwareAddressString);
+        String name = port.getName();
+        portInfo.put(PORT_NAME, name);
+        long config = U32.f(port.getConfig());
+        portInfo.put(PORT_CONFIG, config);
+        long state = U32.f(port.getState());
+        portInfo.put(PORT_STATE, state);
+        long currentFeatures = U32.f(port.getCurrentFeatures());
+        portInfo.put(PORT_CURRENT_FEATURES, currentFeatures);
+        long advertisedFeatures = U32.f(port.getAdvertisedFeatures());
+        portInfo.put(PORT_ADVERTISED_FEATURES, advertisedFeatures);
+        long supportedFeatures = U32.f(port.getSupportedFeatures());
+        portInfo.put(PORT_SUPPORTED_FEATURES, supportedFeatures);
+        long peerFeatures = U32.f(port.getPeerFeatures());
+        portInfo.put(PORT_PEER_FEATURES, peerFeatures);
+        storageSource.updateRowAsync(PORT_TABLE_NAME, portInfo);
+    }
+    
+    /**
+     * Read switch port data from storage and write it into a switch object
+     * @param sw the switch to update
+     */
+    protected void readSwitchPortStateFromStorage(OFSwitchImpl sw) {
+        OperatorPredicate op = 
+                new OperatorPredicate(PORT_SWITCH, 
+                                      OperatorPredicate.Operator.EQ,
+                                      sw.getStringId());
+        IResultSet portResultSet = 
+                storageSource.executeQuery(PORT_TABLE_NAME,
+                                           null, op, null);
+        //Map<Short, OFPhysicalPort> oldports = 
+        //        new HashMap<Short, OFPhysicalPort>();
+        //oldports.putAll(sw.getPorts());
+
+        while (portResultSet.next()) {
+            try {
+                OFPhysicalPort p = new OFPhysicalPort();
+                p.setPortNumber((short)portResultSet.getInt(PORT_NUMBER));
+                p.setName(portResultSet.getString(PORT_NAME));
+                p.setConfig((int)portResultSet.getLong(PORT_CONFIG));
+                p.setState((int)portResultSet.getLong(PORT_STATE));
+                String portMac = portResultSet.getString(PORT_HARDWARE_ADDRESS);
+                p.setHardwareAddress(HexString.fromHexString(portMac));
+                p.setCurrentFeatures((int)portResultSet.
+                                     getLong(PORT_CURRENT_FEATURES));
+                p.setAdvertisedFeatures((int)portResultSet.
+                                        getLong(PORT_ADVERTISED_FEATURES));
+                p.setSupportedFeatures((int)portResultSet.
+                                       getLong(PORT_SUPPORTED_FEATURES));
+                p.setPeerFeatures((int)portResultSet.
+                                  getLong(PORT_PEER_FEATURES));
+                //oldports.remove(Short.valueOf(p.getPortNumber()));
+                sw.setPort(p);
+            } catch (NullPointerException e) {
+                // ignore
+            }
+        }
+        SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.PORTCHANGED);
+        try {
+            this.updates.put(update);
+        } catch (InterruptedException e) {
+            log.error("Failure adding update to queue", e);
+        }
+    }
+    
+    protected void removePortInfo(IOFSwitch sw, short portNumber) {
+        if (role == Role.SLAVE) {
+            return;
+        }
+        String datapathIdString = sw.getStringId();
+        String id = datapathIdString + "|" + portNumber;
+        storageSource.deleteRowAsync(PORT_TABLE_NAME, id);
+    }
+
+    /**
+     * Sets the initial role based on properties in the config params.
+     * It looks for two different properties.
+     * If the "role" property is specified then the value should be
+     * either "EQUAL", "MASTER", or "SLAVE" and the role of the
+     * controller is set to the specified value. If the "role" property
+     * is not specified then it looks next for the "role.path" property.
+     * In this case the value should be the path to a property file in
+     * the file system that contains a property called "floodlight.role"
+     * which can be one of the values listed above for the "role" property.
+     * The idea behind the "role.path" mechanism is that you have some
+     * separate heartbeat and master controller election algorithm that
+     * determines the role of the controller. When a role transition happens,
+     * it updates the current role in the file specified by the "role.path"
+     * file. Then if floodlight restarts for some reason it can get the
+     * correct current role of the controller from the file.
+     * @param configParams The config params for the FloodlightProvider service
+     * @return A valid role if role information is specified in the
+     *         config params, otherwise null
+     */
+    @LogMessageDocs({
+        @LogMessageDoc(message="Controller role set to {role}",
+                explanation="Setting the initial HA role to "),
+        @LogMessageDoc(level="ERROR",
+                message="Invalid current role value: {role}",
+                explanation="An invalid HA role value was read from the " + 
+                            "properties file",
+                recommendation=LogMessageDoc.CHECK_CONTROLLER)
+    })
+    protected Role getInitialRole(Map<String, String> configParams) {
+        Role role = null;
+        String roleString = configParams.get("role");
+        if (roleString == null) {
+            String rolePath = configParams.get("rolepath");
+            if (rolePath != null) {
+                Properties properties = new Properties();
+                try {
+                    properties.load(new FileInputStream(rolePath));
+                    roleString = properties.getProperty("floodlight.role");
+                }
+                catch (IOException exc) {
+                    // Don't treat it as an error if the file specified by the
+                    // rolepath property doesn't exist. This lets us enable the
+                    // HA mechanism by just creating/setting the floodlight.role
+                    // property in that file without having to modify the
+                    // floodlight properties.
+                }
+            }
+        }
+        
+        if (roleString != null) {
+            // Canonicalize the string to the form used for the enum constants
+            roleString = roleString.trim().toUpperCase();
+            try {
+                role = Role.valueOf(roleString);
+            }
+            catch (IllegalArgumentException exc) {
+                log.error("Invalid current role value: {}", roleString);
+            }
+        }
+        
+        log.info("Controller role set to {}", role);
+        
+        return role;
+    }
+    
+    /**
+     * Tell controller that we're ready to accept switches loop
+     * @throws IOException 
+     */
+    @LogMessageDocs({
+        @LogMessageDoc(message="Listening for switch connections on {address}",
+                explanation="The controller is ready and listening for new" +
+                        " switch connections"),
+        @LogMessageDoc(message="Storage exception in controller " + 
+                        "updates loop; terminating process",
+                explanation=ERROR_DATABASE,
+                recommendation=LogMessageDoc.CHECK_CONTROLLER),
+        @LogMessageDoc(level="ERROR",
+                message="Exception in controller updates loop",
+                explanation="Failed to dispatch controller event",
+                recommendation=LogMessageDoc.GENERIC_ACTION)
+    })
+    public void run() {
+        if (log.isDebugEnabled()) {
+            logListeners();
+        }
+        
+        try {            
+           final ServerBootstrap bootstrap = createServerBootStrap();
+
+            bootstrap.setOption("reuseAddr", true);
+            bootstrap.setOption("child.keepAlive", true);
+            bootstrap.setOption("child.tcpNoDelay", true);
+            bootstrap.setOption("child.sendBufferSize", Controller.SEND_BUFFER_SIZE);
+
+            ChannelPipelineFactory pfact = 
+                    new OpenflowPipelineFactory(this, null);
+            bootstrap.setPipelineFactory(pfact);
+            InetSocketAddress sa = new InetSocketAddress(openFlowPort);
+            final ChannelGroup cg = new DefaultChannelGroup();
+            cg.add(bootstrap.bind(sa));
+            
+            log.info("Listening for switch connections on {}", sa);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        // main loop
+        while (true) {
+            try {
+                IUpdate update = updates.take();
+                update.dispatch();
+            } catch (InterruptedException e) {
+                return;
+            } catch (StorageException e) {
+                log.error("Storage exception in controller " + 
+                          "updates loop; terminating process", e);
+                return;
+            } catch (Exception e) {
+                log.error("Exception in controller updates loop", e);
+            }
+        }
+    }
+
+    private ServerBootstrap createServerBootStrap() {
+        if (workerThreads == 0) {
+            return new ServerBootstrap(
+                    new NioServerSocketChannelFactory(
+                            Executors.newCachedThreadPool(),
+                            Executors.newCachedThreadPool()));
+        } else {
+            return new ServerBootstrap(
+                    new NioServerSocketChannelFactory(
+                            Executors.newCachedThreadPool(),
+                            Executors.newCachedThreadPool(), workerThreads));
+        }
+    }
+    
+    public void setConfigParams(Map<String, String> configParams) {
+        String ofPort = configParams.get("openflowport");
+        if (ofPort != null) {
+            this.openFlowPort = Integer.parseInt(ofPort);
+        }
+        log.debug("OpenFlow port set to {}", this.openFlowPort);
+        String threads = configParams.get("workerthreads");
+        if (threads != null) {
+            this.workerThreads = Integer.parseInt(threads);
+        }
+        log.debug("Number of worker threads set to {}", this.workerThreads);
+        String controllerId = configParams.get("controllerid");
+        if (controllerId != null) {
+            this.controllerId = controllerId;
+        }
+        log.debug("ControllerId set to {}", this.controllerId);
+    }
+
+    private void initVendorMessages() {
+        // Configure openflowj to be able to parse the role request/reply
+        // vendor messages.
+        OFBasicVendorId niciraVendorId = new OFBasicVendorId(
+                OFNiciraVendorData.NX_VENDOR_ID, 4);
+        OFVendorId.registerVendorId(niciraVendorId);
+        OFBasicVendorDataType roleRequestVendorData =
+                new OFBasicVendorDataType(
+                        OFRoleRequestVendorData.NXT_ROLE_REQUEST,
+                        OFRoleRequestVendorData.getInstantiable());
+        niciraVendorId.registerVendorDataType(roleRequestVendorData);
+        OFBasicVendorDataType roleReplyVendorData =
+                new OFBasicVendorDataType(
+                        OFRoleReplyVendorData.NXT_ROLE_REPLY,
+                        OFRoleReplyVendorData.getInstantiable());
+         niciraVendorId.registerVendorDataType(roleReplyVendorData);
+    }
+    
+    /**
+     * Initialize internal data structures
+     */
+    public void init(Map<String, String> configParams) {
+        // These data structures are initialized here because other
+        // module's startUp() might be called before ours
+        this.messageListeners =
+                new ConcurrentHashMap<OFType, 
+                                      ListenerDispatcher<OFType, 
+                                                         IOFMessageListener>>();
+        this.switchListeners = new CopyOnWriteArraySet<IOFSwitchListener>();
+        this.haListeners = new CopyOnWriteArraySet<IHAListener>();
+        this.activeSwitches = new ConcurrentHashMap<Long, IOFSwitch>();
+        this.connectedSwitches = new HashSet<OFSwitchImpl>();
+        this.controllerNodeIPsCache = new HashMap<String, String>();
+        this.updates = new LinkedBlockingQueue<IUpdate>();
+        this.factory = new BasicFactory();
+        this.providerMap = new HashMap<String, List<IInfoProvider>>();
+        setConfigParams(configParams);
+        this.role = getInitialRole(configParams);
+        this.roleChanger = new RoleChanger();
+        initVendorMessages();
+        this.systemStartTime = System.currentTimeMillis();
+    }
+    
+    /**
+     * Startup all of the controller's components
+     */
+    @LogMessageDoc(message="Waiting for storage source",
+                explanation="The system database is not yet ready",
+                recommendation="If this message persists, this indicates " +
+                        "that the system database has failed to start. " +
+                        LogMessageDoc.CHECK_CONTROLLER)
+    public void startupComponents() {
+        // Create the table names we use
+        storageSource.createTable(CONTROLLER_TABLE_NAME, null);
+        storageSource.createTable(SWITCH_TABLE_NAME, null);
+        storageSource.createTable(PORT_TABLE_NAME, null);
+        storageSource.createTable(CONTROLLER_INTERFACE_TABLE_NAME, null);
+        storageSource.createTable(SWITCH_CONFIG_TABLE_NAME, null);
+        storageSource.setTablePrimaryKeyName(CONTROLLER_TABLE_NAME,
+                                             CONTROLLER_ID);
+        storageSource.setTablePrimaryKeyName(SWITCH_TABLE_NAME,
+                                             SWITCH_DATAPATH_ID);
+        storageSource.setTablePrimaryKeyName(PORT_TABLE_NAME, PORT_ID);
+        storageSource.setTablePrimaryKeyName(CONTROLLER_INTERFACE_TABLE_NAME, 
+                                             CONTROLLER_INTERFACE_ID);
+        storageSource.addListener(CONTROLLER_INTERFACE_TABLE_NAME, this);
+        
+        while (true) {
+            try {
+                updateControllerInfo();
+                break;
+            }
+            catch (StorageException e) {
+                log.info("Waiting for storage source");
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e1) {
+                }
+            }
+        }
+       
+        // Add our REST API
+        restApi.addRestletRoutable(new CoreWebRoutable());
+    }
+
+    @Override
+    public void addInfoProvider(String type, IInfoProvider provider) {
+        if (!providerMap.containsKey(type)) {
+            providerMap.put(type, new ArrayList<IInfoProvider>());
+        }
+        providerMap.get(type).add(provider);
+    }
+
+    @Override
+    public void removeInfoProvider(String type, IInfoProvider provider) {
+        if (!providerMap.containsKey(type)) {
+            log.debug("Provider type {} doesn't exist.", type);
+            return;
+        }
+        
+        providerMap.get(type).remove(provider);
+    }
+    
+    public Map<String, Object> getControllerInfo(String type) {
+        if (!providerMap.containsKey(type)) return null;
+        
+        Map<String, Object> result = new LinkedHashMap<String, Object>();
+        for (IInfoProvider provider : providerMap.get(type)) {
+            result.putAll(provider.getInfo(type));
+        }
+        
+        return result;
+    }
+
+    @Override
+    public void addHAListener(IHAListener listener) {
+        this.haListeners.add(listener);
+    }
+
+    @Override
+    public void removeHAListener(IHAListener listener) {
+        this.haListeners.remove(listener);
+    }
+    
+    
+    /**
+     * Handle changes to the controller nodes IPs and dispatch update. 
+     */
+    @SuppressWarnings("unchecked")
+    protected void handleControllerNodeIPChanges() {
+        HashMap<String,String> curControllerNodeIPs = new HashMap<String,String>();
+        HashMap<String,String> addedControllerNodeIPs = new HashMap<String,String>();
+        HashMap<String,String> removedControllerNodeIPs =new HashMap<String,String>();
+        String[] colNames = { CONTROLLER_INTERFACE_CONTROLLER_ID, 
+                           CONTROLLER_INTERFACE_TYPE, 
+                           CONTROLLER_INTERFACE_NUMBER, 
+                           CONTROLLER_INTERFACE_DISCOVERED_IP };
+        synchronized(controllerNodeIPsCache) {
+            // We currently assume that interface Ethernet0 is the relevant
+            // controller interface. Might change.
+            // We could (should?) implement this using 
+            // predicates, but creating the individual and compound predicate
+            // seems more overhead then just checking every row. Particularly, 
+            // since the number of rows is small and changes infrequent
+            IResultSet res = storageSource.executeQuery(CONTROLLER_INTERFACE_TABLE_NAME,
+                    colNames,null, null);
+            while (res.next()) {
+                if (res.getString(CONTROLLER_INTERFACE_TYPE).equals("Ethernet") &&
+                        res.getInt(CONTROLLER_INTERFACE_NUMBER) == 0) {
+                    String controllerID = res.getString(CONTROLLER_INTERFACE_CONTROLLER_ID);
+                    String discoveredIP = res.getString(CONTROLLER_INTERFACE_DISCOVERED_IP);
+                    String curIP = controllerNodeIPsCache.get(controllerID);
+                    
+                    curControllerNodeIPs.put(controllerID, discoveredIP);
+                    if (curIP == null) {
+                        // new controller node IP
+                        addedControllerNodeIPs.put(controllerID, discoveredIP);
+                    } 
+                    else if (curIP != discoveredIP) {
+                        // IP changed                    
+                        removedControllerNodeIPs.put(controllerID, curIP);
+                        addedControllerNodeIPs.put(controllerID, discoveredIP);
+                    }
+                }
+            }
+            // Now figure out if rows have been deleted. We can't use the
+            // rowKeys from rowsDeleted directly, since the tables primary
+            // key is a compound that we can't disassemble
+            Set<String> curEntries = curControllerNodeIPs.keySet();
+            Set<String> removedEntries = controllerNodeIPsCache.keySet();
+            removedEntries.removeAll(curEntries);
+            for (String removedControllerID : removedEntries)
+                removedControllerNodeIPs.put(removedControllerID, controllerNodeIPsCache.get(removedControllerID));
+            controllerNodeIPsCache = (HashMap<String, String>) curControllerNodeIPs.clone();
+            HAControllerNodeIPUpdate update = new HAControllerNodeIPUpdate(
+                                curControllerNodeIPs, addedControllerNodeIPs,
+                                removedControllerNodeIPs);
+            if (!removedControllerNodeIPs.isEmpty() || !addedControllerNodeIPs.isEmpty()) {
+                try {
+                    this.updates.put(update);
+                } catch (InterruptedException e) {
+                    log.error("Failure adding update to queue", e);
+                }
+            }
+        }
+    }
+    
+    @Override
+    public Map<String, String> getControllerNodeIPs() {
+        // We return a copy of the mapping so we can guarantee that
+        // the mapping return is the same as one that will be (or was)
+        // dispatched to IHAListeners
+        HashMap<String,String> retval = new HashMap<String,String>();
+        synchronized(controllerNodeIPsCache) {
+            retval.putAll(controllerNodeIPsCache);
+        }
+        return retval;
+    }
+
+    @Override
+    public void rowsModified(String tableName, Set<Object> rowKeys) {
+        if (tableName.equals(CONTROLLER_INTERFACE_TABLE_NAME)) {
+            handleControllerNodeIPChanges();
+        }
+        
+    }
+
+    @Override
+    public void rowsDeleted(String tableName, Set<Object> rowKeys) {
+        if (tableName.equals(CONTROLLER_INTERFACE_TABLE_NAME)) {
+            handleControllerNodeIPChanges();
+        }
+    }
+
+    @Override
+    public long getSystemStartTime() {
+        return (this.systemStartTime);
+    }
+
+    @Override
+    public void setAlwaysClearFlowsOnSwAdd(boolean value) {
+        this.alwaysClearFlowsOnSwAdd = value;
+    }
+    
+    public boolean getAlwaysClearFlowsOnSwAdd() {
+        return this.alwaysClearFlowsOnSwAdd;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutException.java
new file mode 100644
index 0000000..421ec1a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutException.java
@@ -0,0 +1,29 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+/**
+ * Exception is thrown when the handshake fails to complete 
+ * before a specified time
+ * @author readams
+ */
+public class HandshakeTimeoutException extends Exception {
+
+    private static final long serialVersionUID = 6859880268940337312L;
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java
new file mode 100644
index 0000000..6d3335f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java
@@ -0,0 +1,101 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.internal.OFChannelState.HandshakeState;
+
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+import org.jboss.netty.util.ExternalResourceReleasable;
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.Timer;
+import org.jboss.netty.util.TimerTask;
+
+/**
+ * Trigger a timeout if a switch fails to complete handshake soon enough
+ */
+public class HandshakeTimeoutHandler 
+    extends SimpleChannelUpstreamHandler
+    implements ExternalResourceReleasable {
+    static final HandshakeTimeoutException EXCEPTION = 
+            new HandshakeTimeoutException();
+    
+    final OFChannelState state;
+    final Timer timer;
+    final long timeoutNanos;
+    volatile Timeout timeout;
+    
+    public HandshakeTimeoutHandler(OFChannelState state, Timer timer,
+                                   long timeoutSeconds) {
+        super();
+        this.state = state;
+        this.timer = timer;
+        this.timeoutNanos = TimeUnit.SECONDS.toNanos(timeoutSeconds);
+
+    }
+    
+    @Override
+    public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
+            throws Exception {
+        if (timeoutNanos > 0) {
+            timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx), 
+                                       timeoutNanos, TimeUnit.NANOSECONDS);
+        }
+        ctx.sendUpstream(e);
+    }
+    
+    @Override
+    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
+            throws Exception {
+        if (timeout != null) {
+            timeout.cancel();
+            timeout = null;
+        }
+    }
+
+    @Override
+    public void releaseExternalResources() {
+        timer.stop();
+    }
+    
+    private final class HandshakeTimeoutTask implements TimerTask {
+
+        private final ChannelHandlerContext ctx;
+
+        HandshakeTimeoutTask(ChannelHandlerContext ctx) {
+            this.ctx = ctx;
+        }
+
+        @Override
+        public void run(Timeout timeout) throws Exception {
+            if (timeout.isCancelled()) {
+                return;
+            }
+
+            if (!ctx.getChannel().isOpen()) {
+                return;
+            }
+            if (!state.hsState.equals(HandshakeState.READY))
+                Channels.fireExceptionCaught(ctx, EXCEPTION);
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchFeatures.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchFeatures.java
new file mode 100644
index 0000000..ddbb0ef
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchFeatures.java
@@ -0,0 +1,9 @@
+package net.floodlightcontroller.core.internal;
+
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+
+import net.floodlightcontroller.core.IOFSwitch;
+
+public interface IOFSwitchFeatures {
+    public void setFromDescription(IOFSwitch sw, OFDescriptionStatistics description);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java
new file mode 100644
index 0000000..ad5a377
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java
@@ -0,0 +1,64 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+/**
+ * Wrapper class to hold state for the OpenFlow switch connection
+ * @author readams
+ */
+class OFChannelState {
+
+    /**
+     * State for handling the switch handshake
+     */
+    protected enum HandshakeState {
+        /**
+         * Beginning state
+         */
+        START,
+
+        /**
+         * Received HELLO from switch
+         */
+        HELLO,
+
+        /**
+         * We've received the features reply
+         * Waiting for Config and Description reply
+         */
+        FEATURES_REPLY,
+
+        /**
+         * Switch is ready for processing messages
+         */
+        READY
+
+    }
+
+    protected volatile HandshakeState hsState = HandshakeState.START;
+    protected boolean hasGetConfigReply = false;
+    protected boolean hasDescription = false;
+    
+    // The firstRoleReplyRecevied flag indicates if we have received the
+    // first role reply message on this connection (in response to the 
+    // role request sent after the handshake). If role support is disabled
+    // on the controller we also set this flag to true. 
+    // The flag is used to decide if the flow table should be wiped
+    // @see Controller.handleRoleReplyMessage()
+    protected boolean firstRoleReplyReceived = false;
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFFeaturesReplyFuture.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFFeaturesReplyFuture.java
new file mode 100644
index 0000000..eca67bd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFFeaturesReplyFuture.java
@@ -0,0 +1,72 @@
+/**
+ *    Copyright 2012, Big Switch Networks, Inc. 
+ * 
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.core.internal;
+
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+
+/**
+ * A concrete implementation that handles asynchronously receiving
+ * OFFeaturesReply
+ * 
+ * @author Shudong Zhou
+ */
+public class OFFeaturesReplyFuture extends
+        OFMessageFuture<OFFeaturesReply> {
+
+    protected volatile boolean finished;
+
+    public OFFeaturesReplyFuture(IThreadPoolService tp,
+            IOFSwitch sw, int transactionId) {
+        super(tp, sw, OFType.FEATURES_REPLY, transactionId);
+        init();
+    }
+
+    public OFFeaturesReplyFuture(IThreadPoolService tp,
+            IOFSwitch sw, int transactionId, long timeout, TimeUnit unit) {
+        super(tp, sw, OFType.FEATURES_REPLY, transactionId, timeout, unit);
+        init();
+    }
+
+    private void init() {
+        this.finished = false;
+        this.result = null;
+    }
+
+    @Override
+    protected void handleReply(IOFSwitch sw, OFMessage msg) {
+        this.result = (OFFeaturesReply) msg;
+        this.finished = true;
+    }
+
+    @Override
+    protected boolean isFinished() {
+        return finished;
+    }
+
+    @Override
+    protected void unRegister() {
+        super.unRegister();
+        sw.cancelFeaturesReply(transactionId);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java
new file mode 100644
index 0000000..295e967
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java
@@ -0,0 +1,60 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.OFMessageFactory;
+
+/**
+ * Decode an openflow message from a Channel, for use in a netty
+ * pipeline
+ * @author readams
+ */
+public class OFMessageDecoder extends FrameDecoder {
+
+    OFMessageFactory factory = new BasicFactory();
+    
+    @Override
+    protected Object decode(ChannelHandlerContext ctx, Channel channel,
+                            ChannelBuffer buffer) throws Exception {
+        if (!channel.isConnected()) {
+            // In testing, I see decode being called AFTER decode last.
+            // This check avoids that from reading curroupted frames
+            return null;
+        }
+
+        List<OFMessage> message = factory.parseMessage(buffer);
+        return message;
+    }
+
+    @Override
+    protected Object decodeLast(ChannelHandlerContext ctx, Channel channel,
+                            ChannelBuffer buffer) throws Exception {
+        // This is not strictly needed atthis time. It is used to detect
+        // connection reset detection from netty (for debug)
+        return null;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java
new file mode 100644
index 0000000..6be5f9a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java
@@ -0,0 +1,56 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
+import org.openflow.protocol.OFMessage;
+
+/**
+ * Encode an openflow message for output into a ChannelBuffer, for use in a
+ * netty pipeline
+ * @author readams
+ */
+public class OFMessageEncoder extends OneToOneEncoder {
+
+    @Override
+    protected Object encode(ChannelHandlerContext ctx, Channel channel,
+                            Object msg) throws Exception {
+        if (!(  msg instanceof List))
+            return msg;
+
+        @SuppressWarnings("unchecked")
+        List<OFMessage> msglist = (List<OFMessage>)msg;
+        int size = 0;
+        for (OFMessage ofm :  msglist) {
+                size += ofm.getLengthU();
+        }
+
+        ChannelBuffer buf = ChannelBuffers.buffer(size);;
+        for (OFMessage ofm :  msglist) {
+            ofm.writeTo(buf);
+        }
+        return buf;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageFuture.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageFuture.java
new file mode 100644
index 0000000..f01e179
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageFuture.java
@@ -0,0 +1,170 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+/**
+ * A Future object used to retrieve asynchronous OFMessage replies. Unregisters
+ * and cancels itself by default after 60 seconds. This class is meant to be
+ * sub-classed and proper behavior added to the handleReply method, and
+ * termination of the Future to be handled in the isFinished method.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public abstract class OFMessageFuture<V> implements Future<V> {
+
+    protected IThreadPoolService threadPool;
+    protected volatile boolean canceled;
+    protected CountDownLatch latch;
+    protected OFType responseType;
+    protected volatile V result;
+    protected IOFSwitch sw;
+    protected Runnable timeoutTimer;
+    protected int transactionId;
+    protected static final long DEFAULT_TIMEOUT = 60;
+    protected static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;
+
+    public OFMessageFuture(IThreadPoolService tp,
+            IOFSwitch sw, OFType responseType, int transactionId) {
+        this(tp, sw, responseType, transactionId, 
+                 DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT);
+    }
+
+    public OFMessageFuture(IThreadPoolService tp,
+            IOFSwitch sw, OFType responseType, int transactionId, long timeout, TimeUnit unit) {
+        this.threadPool = tp;
+        this.canceled = false;
+        this.latch = new CountDownLatch(1);
+        this.responseType = responseType;
+        this.sw = sw;
+        this.transactionId = transactionId;
+
+        final OFMessageFuture<V> future = this;
+        timeoutTimer = new Runnable() {
+            @Override
+            public void run() {
+                if (timeoutTimer == this)
+                    future.cancel(true);
+            }
+        };
+        threadPool.getScheduledExecutor().schedule(timeoutTimer, timeout, unit);
+    }
+
+    protected void unRegister() {
+        this.timeoutTimer = null;
+    }
+
+  
+    public void deliverFuture(IOFSwitch sw, OFMessage msg) {
+        if (transactionId == msg.getXid()) {
+            handleReply(sw, msg);
+            if (isFinished()) {
+                unRegister();
+                this.latch.countDown();
+            }
+        }
+    }
+
+    /**
+     * Used to handle the specific expected message this Future was reigstered
+     * for, the specified msg parameter is guaranteed to match the type and
+     * transaction id specified.
+     * @param sw
+     * @param msg
+     * @return
+     */
+    protected abstract void handleReply(IOFSwitch sw, OFMessage msg);
+
+    /**
+     * Called directly after handleReply, subclasses implement this method to
+     * indicate when the future can deregister itself from receiving future
+     * messages, and when it is safe to return the results to any waiting
+     * threads.
+     * @return when this Future has completed its work
+     */
+    protected abstract boolean isFinished();
+
+    /* (non-Javadoc)
+     * @see java.util.concurrent.Future#cancel(boolean)
+     */
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning) {
+        if (isDone()) {
+            return false;
+        } else {
+            unRegister();
+            canceled = true;
+            this.latch.countDown();
+            return !isDone();
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.concurrent.Future#isCancelled()
+     */
+    @Override
+    public boolean isCancelled() {
+        return canceled;
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.concurrent.Future#isDone()
+     */
+    @Override
+    public boolean isDone() {
+        return this.latch.getCount() == 0;
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.concurrent.Future#get()
+     */
+    @Override
+    public V get() throws InterruptedException, ExecutionException {
+        this.latch.await();
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
+     */
+    @Override
+    public V get(long timeout, TimeUnit unit) throws InterruptedException,
+            ExecutionException, TimeoutException {
+        this.latch.await(timeout, unit);
+        return result;
+    }
+
+    public int getTransactionId() {
+        return transactionId;
+    }
+
+    public void setTransactionId(int transactionId) {
+        this.transactionId = transactionId;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFStatisticsFuture.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFStatisticsFuture.java
new file mode 100644
index 0000000..4d3f733
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFStatisticsFuture.java
@@ -0,0 +1,80 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFStatisticsReply;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.statistics.OFStatistics;
+
+/**
+ * A concrete implementation that handles asynchronously receiving OFStatistics
+ * 
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFStatisticsFuture extends
+        OFMessageFuture<List<OFStatistics>> {
+
+    protected volatile boolean finished;
+
+    public OFStatisticsFuture(IThreadPoolService tp,
+            IOFSwitch sw, int transactionId) {
+        super(tp, sw, OFType.STATS_REPLY, transactionId);
+        init();
+    }
+
+    public OFStatisticsFuture(IThreadPoolService tp,
+            IOFSwitch sw, int transactionId, long timeout, TimeUnit unit) {
+        super(tp, sw, OFType.STATS_REPLY, transactionId, timeout, unit);
+        init();
+    }
+
+    private void init() {
+        this.finished = false;
+        this.result = new CopyOnWriteArrayList<OFStatistics>();
+    }
+
+    @Override
+    protected void handleReply(IOFSwitch sw, OFMessage msg) {
+        OFStatisticsReply sr = (OFStatisticsReply) msg;
+        synchronized (this.result) {
+            this.result.addAll(sr.getStatistics());
+            if ((sr.getFlags() & 0x1) == 0) {
+                this.finished = true;
+            }
+        }
+    }
+
+    @Override
+    protected boolean isFinished() {
+        return finished;
+    }
+    
+    @Override
+    protected void unRegister() {
+        super.unRegister();
+        sw.cancelStatisticsReply(transactionId);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java
new file mode 100644
index 0000000..dff00fd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java
@@ -0,0 +1,857 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.util.TimedCache;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.ser.ToStringSerializer;
+import org.jboss.netty.channel.Channel;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFFeaturesRequest;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFVendor;
+import org.openflow.protocol.OFPhysicalPort.OFPortConfig;
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.openflow.vendor.nicira.OFNiciraVendorData;
+import org.openflow.vendor.nicira.OFRoleRequestVendorData;
+import org.openflow.vendor.nicira.OFRoleVendorData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is the internal representation of an openflow switch.
+ */
+public class OFSwitchImpl implements IOFSwitch {
+    // TODO: should we really do logging in the class or should we throw
+    // exception that can then be handled by callers?
+    protected static Logger log = LoggerFactory.getLogger(OFSwitchImpl.class);
+
+    private static final String HA_CHECK_SWITCH = 
+            "Check the health of the indicated switch.  If the problem " +
+            "persists or occurs repeatedly, it likely indicates a defect " +
+            "in the switch HA implementation.";
+    
+    protected ConcurrentMap<Object, Object> attributes;
+    protected IFloodlightProviderService floodlightProvider;
+    protected IThreadPoolService threadPool;
+    protected Date connectedSince;
+    protected String stringId;
+    protected Channel channel;
+    protected AtomicInteger transactionIdSource;
+    // Lock to protect modification of the port maps. We only need to 
+    // synchronize on modifications. For read operations we are fine since
+    // we rely on ConcurrentMaps which works for our use case.
+    private Object portLock;
+    // Map port numbers to the appropriate OFPhysicalPort
+    protected ConcurrentHashMap<Short, OFPhysicalPort> portsByNumber;
+    // Map port names to the appropriate OFPhyiscalPort
+    // XXX: The OF spec doesn't specify if port names need to be unique but
+    //      according it's always the case in practice. 
+    protected ConcurrentHashMap<String, OFPhysicalPort> portsByName;
+    protected Map<Integer,OFStatisticsFuture> statsFutureMap;
+    protected Map<Integer, IOFMessageListener> iofMsgListenersMap;
+    protected Map<Integer,OFFeaturesReplyFuture> featuresFutureMap;
+    protected boolean connected;
+    protected Role role;
+    protected TimedCache<Long> timedCache;
+    protected ReentrantReadWriteLock listenerLock;
+    protected ConcurrentMap<Short, Long> portBroadcastCacheHitMap;
+    /**
+     * When sending a role request message, the role request is added
+     * to this queue. If a role reply is received this queue is checked to 
+     * verify that the reply matches the expected reply. We require in order
+     * delivery of replies. That's why we use a Queue. 
+     * The RoleChanger uses a timeout to ensure we receive a timely reply.
+     * 
+     * Need to synchronize on this instance if a request is sent, received, 
+     * checked. 
+     */
+    protected LinkedList<PendingRoleRequestEntry> pendingRoleRequests;
+    
+    /* Switch features from initial featuresReply */
+    protected int capabilities;
+    protected int buffers;
+    protected int actions;
+    protected byte tables;
+    protected long datapathId;
+
+    public static IOFSwitchFeatures switchFeatures;
+    protected static final ThreadLocal<Map<OFSwitchImpl,List<OFMessage>>> local_msg_buffer =
+            new ThreadLocal<Map<OFSwitchImpl,List<OFMessage>>>() {
+            @Override
+            protected Map<OFSwitchImpl,List<OFMessage>> initialValue() {
+                return new HashMap<OFSwitchImpl,List<OFMessage>>();
+            }
+    };
+    
+    // for managing our map sizes
+    protected static final int MAX_MACS_PER_SWITCH  = 1000;
+    
+    protected static class PendingRoleRequestEntry {
+        protected int xid;
+        protected Role role;
+        // cookie is used to identify the role "generation". roleChanger uses
+        protected long cookie;
+        public PendingRoleRequestEntry(int xid, Role role, long cookie) {
+            this.xid = xid;
+            this.role = role;
+            this.cookie = cookie;
+        }
+    }
+    
+    public OFSwitchImpl() {
+        this.stringId = null;
+        this.attributes = new ConcurrentHashMap<Object, Object>();
+        this.connectedSince = new Date();
+        this.transactionIdSource = new AtomicInteger();
+        this.portLock = new Object();
+        this.portsByNumber = new ConcurrentHashMap<Short, OFPhysicalPort>();
+        this.portsByName = new ConcurrentHashMap<String, OFPhysicalPort>();
+        this.connected = true;
+        this.statsFutureMap = new ConcurrentHashMap<Integer,OFStatisticsFuture>();
+        this.featuresFutureMap = new ConcurrentHashMap<Integer,OFFeaturesReplyFuture>();
+        this.iofMsgListenersMap = new ConcurrentHashMap<Integer,IOFMessageListener>();
+        this.role = null;
+        this.timedCache = new TimedCache<Long>(100, 5*1000 );  // 5 seconds interval
+        this.listenerLock = new ReentrantReadWriteLock();
+        this.portBroadcastCacheHitMap = new ConcurrentHashMap<Short, Long>();
+        this.pendingRoleRequests = new LinkedList<OFSwitchImpl.PendingRoleRequestEntry>();
+        
+        // Defaults properties for an ideal switch
+        this.setAttribute(PROP_FASTWILDCARDS, OFMatch.OFPFW_ALL);
+        this.setAttribute(PROP_SUPPORTS_OFPP_FLOOD, new Boolean(true));
+        this.setAttribute(PROP_SUPPORTS_OFPP_TABLE, new Boolean(true));
+    }
+    
+
+    @Override
+    public Object getAttribute(String name) {
+        if (this.attributes.containsKey(name)) {
+            return this.attributes.get(name);
+        }
+        return null;
+    }
+    
+    @Override
+    public void setAttribute(String name, Object value) {
+        this.attributes.put(name, value);
+        return;
+    }
+
+    @Override
+    public Object removeAttribute(String name) {
+        return this.attributes.remove(name);
+    }
+    
+    @Override
+    public boolean hasAttribute(String name) {
+        return this.attributes.containsKey(name);
+    }
+        
+    @Override
+    @JsonIgnore
+    public Channel getChannel() {
+        return this.channel;
+    }
+
+    @JsonIgnore
+    public void setChannel(Channel channel) {
+        this.channel = channel;
+    }
+    
+    @Override
+    public void write(OFMessage m, FloodlightContext bc) throws IOException {
+        Map<OFSwitchImpl,List<OFMessage>> msg_buffer_map = local_msg_buffer.get();
+        List<OFMessage> msg_buffer = msg_buffer_map.get(this);
+        if (msg_buffer == null) {
+            msg_buffer = new ArrayList<OFMessage>();
+            msg_buffer_map.put(this, msg_buffer);
+        }
+
+        this.floodlightProvider.handleOutgoingMessage(this, m, bc);
+        msg_buffer.add(m);
+
+        if ((msg_buffer.size() >= Controller.BATCH_MAX_SIZE) ||
+            ((m.getType() != OFType.PACKET_OUT) && (m.getType() != OFType.FLOW_MOD))) {
+            this.write(msg_buffer);
+            msg_buffer.clear();
+        }
+    }
+
+    @Override
+    @LogMessageDoc(level="WARN",
+                   message="Sending OF message that modifies switch " +
+                           "state while in the slave role: {switch}",
+                   explanation="An application has sent a message to a switch " +
+                   		"that is not valid when the switch is in a slave role",
+                   recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    public void write(List<OFMessage> msglist, 
+                      FloodlightContext bc) throws IOException {
+        for (OFMessage m : msglist) {
+            if (role == Role.SLAVE) {
+                switch (m.getType()) {
+                    case PACKET_OUT:
+                    case FLOW_MOD:
+                    case PORT_MOD:
+                        log.warn("Sending OF message that modifies switch " +
+                        		 "state while in the slave role: {}", 
+                        		 m.getType().name());
+                        break;
+                    default:
+                        break;
+                }
+            }
+            this.floodlightProvider.handleOutgoingMessage(this, m, bc);
+        }
+        this.write(msglist);
+    }
+
+    public void write(List<OFMessage> msglist) throws IOException {
+        this.channel.write(msglist);
+    }
+    
+    @Override
+    public void disconnectOutputStream() {
+        channel.close();
+    }
+
+    @Override
+    @JsonIgnore
+    public void setFeaturesReply(OFFeaturesReply featuresReply) {
+        synchronized(portLock) {
+            if (stringId == null) {
+                /* ports are updated via port status message, so we
+                 * only fill in ports on initial connection.
+                 */
+                for (OFPhysicalPort port : featuresReply.getPorts()) {
+                    setPort(port);
+                }
+            }
+            this.datapathId = featuresReply.getDatapathId();
+            this.capabilities = featuresReply.getCapabilities();
+            this.buffers = featuresReply.getBuffers();
+            this.actions = featuresReply.getActions();
+            this.tables = featuresReply.getTables();
+            this.stringId = HexString.toHexString(this.datapathId);
+        }
+    }
+
+    @Override
+    @JsonIgnore
+    public Collection<OFPhysicalPort> getEnabledPorts() {
+        List<OFPhysicalPort> result = new ArrayList<OFPhysicalPort>();
+        for (OFPhysicalPort port : portsByNumber.values()) {
+            if (portEnabled(port)) {
+                result.add(port);
+            }
+        }
+        return result;
+    }
+    
+    @Override
+    @JsonIgnore
+    public Collection<Short> getEnabledPortNumbers() {
+        List<Short> result = new ArrayList<Short>();
+        for (OFPhysicalPort port : portsByNumber.values()) {
+            if (portEnabled(port)) {
+                result.add(port.getPortNumber());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public OFPhysicalPort getPort(short portNumber) {
+        return portsByNumber.get(portNumber);
+    }
+    
+    @Override
+    public OFPhysicalPort getPort(String portName) {
+        return portsByName.get(portName);
+    }
+
+    @Override
+    @JsonIgnore
+    public void setPort(OFPhysicalPort port) {
+        synchronized(portLock) {
+            portsByNumber.put(port.getPortNumber(), port);
+            portsByName.put(port.getName(), port);
+        }
+    }
+    
+    @Override
+    @JsonProperty("ports")
+    public Collection<OFPhysicalPort> getPorts() {
+        return Collections.unmodifiableCollection(portsByNumber.values());
+    }
+    
+    @Override
+    public void deletePort(short portNumber) {
+        synchronized(portLock) {
+            portsByName.remove(portsByNumber.get(portNumber).getName());
+            portsByNumber.remove(portNumber);
+        }
+    }
+    
+    @Override
+    public void deletePort(String portName) {
+        synchronized(portLock) {
+            portsByNumber.remove(portsByName.get(portName).getPortNumber());
+            portsByName.remove(portName);
+        }
+    }
+
+    @Override
+    public boolean portEnabled(short portNumber) {
+        if (portsByNumber.get(portNumber) == null) return false;
+        return portEnabled(portsByNumber.get(portNumber));
+    }
+    
+    @Override
+    public boolean portEnabled(String portName) {
+        if (portsByName.get(portName) == null) return false;
+        return portEnabled(portsByName.get(portName));
+    }
+    
+    @Override
+    public boolean portEnabled(OFPhysicalPort port) {
+        if (port == null)
+            return false;
+        if ((port.getConfig() & OFPortConfig.OFPPC_PORT_DOWN.getValue()) > 0)
+            return false;
+        if ((port.getState() & OFPortState.OFPPS_LINK_DOWN.getValue()) > 0)
+            return false;
+        // Port STP state doesn't work with multiple VLANs, so ignore it for now
+        //if ((port.getState() & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue())
+        //    return false;
+        return true;
+    }
+    
+    @Override
+    @JsonSerialize(using=DPIDSerializer.class)
+    @JsonProperty("dpid")
+    public long getId() {
+        if (this.stringId == null)
+            throw new RuntimeException("Features reply has not yet been set");
+        return this.datapathId;
+    }
+
+    @JsonIgnore
+    @Override
+    public String getStringId() {
+        return stringId;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "OFSwitchImpl [" + channel.getRemoteAddress() + " DPID[" + ((stringId != null) ? stringId : "?") + "]]";
+    }
+
+    @Override
+    public ConcurrentMap<Object, Object> getAttributes() {
+        return this.attributes;
+    }
+
+    @Override
+    public Date getConnectedSince() {
+        return connectedSince;
+    }
+
+    @JsonIgnore
+    @Override
+    public int getNextTransactionId() {
+        return this.transactionIdSource.incrementAndGet();
+    }
+
+    @Override
+    public void sendStatsQuery(OFStatisticsRequest request, int xid,
+                                IOFMessageListener caller) throws IOException {
+        request.setXid(xid);
+        this.iofMsgListenersMap.put(xid, caller);
+        List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+        msglist.add(request);
+        this.channel.write(msglist);
+        return;
+    }
+
+    @Override
+    public Future<List<OFStatistics>> getStatistics(OFStatisticsRequest request) throws IOException {
+        request.setXid(getNextTransactionId());
+        OFStatisticsFuture future = new OFStatisticsFuture(threadPool, this, request.getXid());
+        this.statsFutureMap.put(request.getXid(), future);
+        List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+        msglist.add(request);
+        this.channel.write(msglist);
+        return future;
+    }
+
+    @Override
+    public void deliverStatisticsReply(OFMessage reply) {
+        OFStatisticsFuture future = this.statsFutureMap.get(reply.getXid());
+        if (future != null) {
+            future.deliverFuture(this, reply);
+            // The future will ultimately unregister itself and call
+            // cancelStatisticsReply
+            return;
+        }
+        /* Transaction id was not found in statsFutureMap.check the other map */
+        IOFMessageListener caller = this.iofMsgListenersMap.get(reply.getXid());
+        if (caller != null) {
+            caller.receive(this, reply, null);
+        }
+    }
+
+    @Override
+    public void cancelStatisticsReply(int transactionId) {
+        if (null ==  this.statsFutureMap.remove(transactionId)) {
+            this.iofMsgListenersMap.remove(transactionId);
+        }
+    }
+
+    @Override
+    public void cancelAllStatisticsReplies() {
+        /* we don't need to be synchronized here. Even if another thread
+         * modifies the map while we're cleaning up the future will eventuall
+         * timeout */
+        for (OFStatisticsFuture f : statsFutureMap.values()) {
+            f.cancel(true);
+        }
+        statsFutureMap.clear();
+        iofMsgListenersMap.clear();
+    }
+ 
+    
+    /**
+     * @param floodlightProvider the floodlightProvider to set
+     */
+    @JsonIgnore
+    public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
+        this.floodlightProvider = floodlightProvider;
+    }
+    
+    @JsonIgnore
+    public void setThreadPoolService(IThreadPoolService tp) {
+        this.threadPool = tp;
+    }
+
+    @JsonIgnore
+    @Override
+    public synchronized boolean isConnected() {
+        return connected;
+    }
+
+    @Override
+    @JsonIgnore
+    public synchronized void setConnected(boolean connected) {
+        this.connected = connected;
+    }
+    
+    @Override
+    public Role getRole() {
+        return role;
+    }
+    
+    @JsonIgnore
+    @Override
+    public boolean isActive() {
+        return (role != Role.SLAVE);
+    }
+    
+    @Override
+    @JsonIgnore
+    public void setSwitchProperties(OFDescriptionStatistics description) {
+        if (switchFeatures != null) {
+            switchFeatures.setFromDescription(this, description);
+        }
+    }
+
+    @Override
+    @LogMessageDoc(level="ERROR",
+                   message="Failed to clear all flows on switch {switch}",
+                   explanation="An I/O error occured while trying to clear " +
+                   		"flows on the switch.",
+                   recommendation=LogMessageDoc.CHECK_SWITCH)
+    public void clearAllFlowMods() {
+        // Delete all pre-existing flows
+        OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL);
+        OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory()
+            .getMessage(OFType.FLOW_MOD))
+                .setMatch(match)
+            .setCommand(OFFlowMod.OFPFC_DELETE)
+            .setOutPort(OFPort.OFPP_NONE)
+            .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH));
+        try {
+            List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+            msglist.add(fm);
+            channel.write(msglist);
+        } catch (Exception e) {
+            log.error("Failed to clear all flows on switch " + this, e);
+        }
+    }
+
+    @Override
+    public boolean updateBroadcastCache(Long entry, Short port) {
+        if (timedCache.update(entry)) {
+            Long count = portBroadcastCacheHitMap.putIfAbsent(port, new Long(1));
+            if (count != null) {
+                count++;
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    @JsonIgnore
+    public Map<Short, Long> getPortBroadcastHits() {
+    	return this.portBroadcastCacheHitMap;
+    }
+    
+
+    @Override
+    public void flush() {
+        Map<OFSwitchImpl,List<OFMessage>> msg_buffer_map = local_msg_buffer.get();
+        List<OFMessage> msglist = msg_buffer_map.get(this);
+        if ((msglist != null) && (msglist.size() > 0)) {
+            try {
+                this.write(msglist);
+            } catch (IOException e) {
+                // TODO: log exception
+                e.printStackTrace();
+            }
+            msglist.clear();
+        }
+    }
+
+    public static void flush_all() {
+        Map<OFSwitchImpl,List<OFMessage>> msg_buffer_map = local_msg_buffer.get();
+        for (OFSwitchImpl sw : msg_buffer_map.keySet()) {
+            sw.flush();
+        }
+    }
+
+    /**
+     * Return a read lock that must be held while calling the listeners for
+     * messages from the switch. Holding the read lock prevents the active
+     * switch list from being modified out from under the listeners.
+     * @return 
+     */
+    @JsonIgnore
+    public Lock getListenerReadLock() {
+        return listenerLock.readLock();
+    }
+
+    /**
+     * Return a write lock that must be held when the controllers modifies the
+     * list of active switches. This is to ensure that the active switch list
+     * doesn't change out from under the listeners as they are handling a
+     * message from the switch.
+     * @return
+     */
+    @JsonIgnore
+    public Lock getListenerWriteLock() {
+        return listenerLock.writeLock();
+    }
+
+    /**
+     * Get the IP Address for the switch
+     * @return the inet address
+     */
+    @JsonSerialize(using=ToStringSerializer.class)
+    public SocketAddress getInetAddress() {
+        return channel.getRemoteAddress();
+    }
+    
+    /**
+     * Send NX role request message to the switch requesting the specified role.
+     * 
+     * This method should ONLY be called by @see RoleChanger.submitRequest(). 
+     * 
+     * After sending the request add it to the queue of pending request. We
+     * use the queue to later verify that we indeed receive the correct reply.
+     * @param sw switch to send the role request message to
+     * @param role role to request
+     * @param cookie an opaque value that will be stored in the pending queue so
+     *        RoleChanger can check for timeouts.
+     * @return transaction id of the role request message that was sent
+     */
+    protected int sendNxRoleRequest(Role role, long cookie)
+            throws IOException {
+        synchronized(pendingRoleRequests) {
+            // Convert the role enum to the appropriate integer constant used
+            // in the NX role request message
+            int nxRole = 0;
+            switch (role) {
+                case EQUAL:
+                    nxRole = OFRoleVendorData.NX_ROLE_OTHER;
+                    break;
+                case MASTER:
+                    nxRole = OFRoleVendorData.NX_ROLE_MASTER;
+                    break;
+                case SLAVE:
+                    nxRole = OFRoleVendorData.NX_ROLE_SLAVE;
+                    break;
+                default:
+                    log.error("Invalid Role specified for switch {}."
+                              + " Disconnecting.", this);
+                    // TODO: should throw an error
+                    return 0;
+            }
+            
+            // Construct the role request message
+            OFVendor roleRequest = (OFVendor)floodlightProvider.
+                    getOFMessageFactory().getMessage(OFType.VENDOR);
+            int xid = this.getNextTransactionId();
+            roleRequest.setXid(xid);
+            roleRequest.setVendor(OFNiciraVendorData.NX_VENDOR_ID);
+            OFRoleRequestVendorData roleRequestData = new OFRoleRequestVendorData();
+            roleRequestData.setRole(nxRole);
+            roleRequest.setVendorData(roleRequestData);
+            roleRequest.setLengthU(OFVendor.MINIMUM_LENGTH + 
+                                   roleRequestData.getLength());
+            
+            // Send it to the switch
+            List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+            msglist.add(roleRequest);
+            // FIXME: should this use this.write() in order for messages to
+            // be processed by handleOutgoingMessage()
+            this.channel.write(msglist);
+            
+            pendingRoleRequests.add(new PendingRoleRequestEntry(xid, role, cookie));
+            return xid;
+        }
+    }
+    
+    /** 
+     * Deliver a RoleReply message to this switch. Checks if the reply 
+     * message matches the expected reply (head of the pending request queue). 
+     * We require in-order delivery of replies. If there's any deviation from
+     * our expectations we disconnect the switch. 
+     * 
+     * We must not check the received role against the controller's current
+     * role because there's no synchronization but that's fine @see RoleChanger
+     * 
+     * Will be called by the OFChannelHandler's receive loop
+     * 
+     * @param xid Xid of the reply message
+     * @param role The Role in the the reply message
+     */
+    @LogMessageDocs({
+        @LogMessageDoc(level="ERROR",
+                message="Switch {switch}: received unexpected role reply for " +
+                        "Role {role}" + 
+                        " Disconnecting switch",
+                explanation="The switch sent an unexpected HA role reply",
+                recommendation=HA_CHECK_SWITCH),                           
+        @LogMessageDoc(level="ERROR",
+                message="Switch {switch}: expected role reply with " +
+                        "Xid {xid}, got {xid}. Disconnecting switch",
+                explanation="The switch sent an unexpected HA role reply",
+                recommendation=HA_CHECK_SWITCH),                           
+        @LogMessageDoc(level="ERROR",
+                message="Switch {switch}: expected role reply with " +
+                        "Role {role}, got {role}. Disconnecting switch",
+                explanation="The switch sent an unexpected HA role reply",
+                recommendation=HA_CHECK_SWITCH)                           
+    })
+    protected void deliverRoleReply(int xid, Role role) {
+        synchronized(pendingRoleRequests) {
+            PendingRoleRequestEntry head = pendingRoleRequests.poll();
+            if (head == null) {
+                // Maybe don't disconnect if the role reply we received is 
+                // for the same role we are already in. 
+                log.error("Switch {}: received unexpected role reply for Role {}" + 
+                          " Disconnecting switch", this, role );
+                this.channel.close();
+            }
+            else if (head.xid != xid) {
+                // check xid before role!!
+                log.error("Switch {}: expected role reply with " +
+                       "Xid {}, got {}. Disconnecting switch",
+                       new Object[] { this, head.xid, xid } );
+                this.channel.close();
+            }
+            else if (head.role != role) {
+                log.error("Switch {}: expected role reply with " +
+                       "Role {}, got {}. Disconnecting switch",
+                       new Object[] { this, head.role, role } );
+                this.channel.close();
+            }
+            else {
+                log.debug("Received role reply message from {}, setting role to {}",
+                          this, role);
+                if (this.role == null && getAttribute(SWITCH_SUPPORTS_NX_ROLE) == null) {
+                    // The first role reply we received. Set the attribute
+                    // that the switch supports roles
+                    setAttribute(SWITCH_SUPPORTS_NX_ROLE, true);
+                }
+                this.role = role;
+            }
+        }
+    }
+    
+    /** 
+     * Checks whether the given xid matches the xid of the first pending
+     * role request. 
+     * @param xid
+     * @return 
+     */
+    protected boolean checkFirstPendingRoleRequestXid (int xid) {
+        synchronized(pendingRoleRequests) {
+            PendingRoleRequestEntry head = pendingRoleRequests.peek();
+            if (head == null)
+                return false;
+            else 
+                return head.xid == xid;
+        }
+    }
+    
+    /**
+     * Checks whether the given request cookie matches the cookie of the first 
+     * pending request
+     * @param cookie
+     * @return
+     */
+    protected boolean checkFirstPendingRoleRequestCookie(long cookie) {
+        synchronized(pendingRoleRequests) {
+            PendingRoleRequestEntry head = pendingRoleRequests.peek();
+            if (head == null)
+                return false;
+            else 
+                return head.cookie == cookie;
+        }
+    }
+    
+    /**
+     * Called if we receive a vendor error message indicating that roles
+     * are not supported by the switch. If the xid matches the first pending
+     * one, we'll mark the switch as not supporting roles and remove the head.
+     * Otherwise we ignore it.
+     * @param xid
+     */
+    protected void deliverRoleRequestNotSupported(int xid) {
+        synchronized(pendingRoleRequests) {
+            PendingRoleRequestEntry head = pendingRoleRequests.poll();
+            this.role = null;
+            if (head!=null && head.xid == xid) {
+                setAttribute(SWITCH_SUPPORTS_NX_ROLE, false);
+            }
+            else {
+                this.channel.close();
+            }
+        }
+    }
+
+    @Override
+    public Future<OFFeaturesReply> getFeaturesReplyFromSwitch()
+            throws IOException {
+        OFMessage request = new OFFeaturesRequest();
+        request.setXid(getNextTransactionId());
+        OFFeaturesReplyFuture future =
+                new OFFeaturesReplyFuture(threadPool, this, request.getXid());
+        this.featuresFutureMap.put(request.getXid(), future);
+        List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+        msglist.add(request);
+        this.channel.write(msglist);
+        return future;
+    }
+
+    @Override
+    public void deliverOFFeaturesReply(OFMessage reply) {
+        OFFeaturesReplyFuture future = this.featuresFutureMap.get(reply.getXid());
+        if (future != null) {
+            future.deliverFuture(this, reply);
+            // The future will ultimately unregister itself and call
+            // cancelFeaturesReply
+            return;
+        }
+        log.error("Switch {}: received unexpected featureReply", this);
+    }
+
+    @Override
+    public void cancelFeaturesReply(int transactionId) {
+        this.featuresFutureMap.remove(transactionId);
+    }
+
+
+    @Override
+    public int getBuffers() {
+        return buffers;
+    }
+
+
+    @Override
+    public int getActions() {
+        return actions;
+    }
+
+
+    @Override
+    public int getCapabilities() {
+        return capabilities;
+    }
+
+
+    @Override
+    public byte getTables() {
+        return tables;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java
new file mode 100644
index 0000000..5fb5c34
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java
@@ -0,0 +1,71 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.handler.execution.ExecutionHandler;
+import org.jboss.netty.handler.timeout.IdleStateHandler;
+import org.jboss.netty.handler.timeout.ReadTimeoutHandler;
+import org.jboss.netty.util.HashedWheelTimer;
+import org.jboss.netty.util.Timer;
+
+/**
+ * Creates a ChannelPipeline for a server-side openflow channel
+ * @author readams
+ */
+public class OpenflowPipelineFactory implements ChannelPipelineFactory {
+
+    protected Controller controller;
+    protected ThreadPoolExecutor pipelineExecutor;
+    protected Timer timer;
+    protected IdleStateHandler idleHandler;
+    protected ReadTimeoutHandler readTimeoutHandler;
+    
+    public OpenflowPipelineFactory(Controller controller,
+                                   ThreadPoolExecutor pipelineExecutor) {
+        super();
+        this.controller = controller;
+        this.pipelineExecutor = pipelineExecutor;
+        this.timer = new HashedWheelTimer();
+        this.idleHandler = new IdleStateHandler(timer, 20, 25, 0);
+        this.readTimeoutHandler = new ReadTimeoutHandler(timer, 30);
+    }
+ 
+    @Override
+    public ChannelPipeline getPipeline() throws Exception {
+        OFChannelState state = new OFChannelState();
+        
+        ChannelPipeline pipeline = Channels.pipeline();
+        pipeline.addLast("ofmessagedecoder", new OFMessageDecoder());
+        pipeline.addLast("ofmessageencoder", new OFMessageEncoder());
+        pipeline.addLast("idle", idleHandler);
+        pipeline.addLast("timeout", readTimeoutHandler);
+        pipeline.addLast("handshaketimeout",
+                         new HandshakeTimeoutHandler(state, timer, 15));
+        if (pipelineExecutor != null)
+            pipeline.addLast("pipelineExecutor",
+                             new ExecutionHandler(pipelineExecutor));
+        pipeline.addLast("handler", controller.getChannelHandler(state));
+        return pipeline;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java
new file mode 100644
index 0000000..6378136
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java
@@ -0,0 +1,321 @@
+package net.floodlightcontroller.core.internal;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+/** 
+ * This class handles sending of RoleRequest messages to all connected switches.
+ * 
+ * Handling Role Requests is tricky. Roles are hard state on the switch and
+ * we can't query it so we need to make sure that we have consistent states
+ * on the switches. Whenever we send a role request to the set of connected 
+ * switches we need to make sure that we've sent the request to all of them 
+ * before we process the next change request. If a new switch connects, we 
+ * need to send it the current role and need to make sure that the current
+ * role doesn't change while we are doing it. We achieve this by synchronizing
+ * all these actions on Controller.roleChanger
+ * On the receive side: we need to make sure that we receive a reply for each 
+ * request we send and that the reply is consistent with the request we sent. 
+ * We'd also like to send the role request to the switch asynchronously in a
+ * separate thread so we don't block the REST API or other callers.
+ * 
+ * There are potential ways to relax these synchronization requirements:
+ * - "Generation ID" for each role request. However, this would be most useful
+ *   if it were global for the whole cluster
+ * - Regularly resend the controller's current role. Don't know whether this
+ *   might have adverse effects on the switch. 
+ *   
+ * Caveats:
+ * - No way to know if another controller (not in our controller cluster) 
+ *   sends MASTER requests to connected switches. Then we would drop to
+ *   slave role without knowing it. Could regularly resend the current role. 
+ *   Ideally the switch would notify us if it demoted us. What happens if
+ *   the other controller also regularly resends the same role request? 
+ *   Or if the health check determines that
+ *   a controller is dead but the controller is still talking to switches (maybe
+ *   just its health check failed) and resending the master role request.... 
+ *   We could try to detect if a switch demoted us to slave even if we think
+ *   we are master (error messages on packet outs, e.g., when sending LLDPs)
+ * 
+ *
+ * The general model of Role Request handling is as follows:
+ * 
+ * - All role request messages are handled by this class. Class Controller 
+ *   submits a role change request and the request gets queued. submitRequest
+ *   takes a Collection of switches to which to send the request. We make a copy
+ *   of this list. 
+ * - A thread takes these change requests from the queue and sends them to 
+ *   all the switches (using our copy of the switch list). 
+ * - The OFSwitchImpl sends the request over the wire and puts the request
+ *   into a queue of pending request (storing xid and role). We start a timeout 
+ *   to make sure we eventually receive a reply from the switch. We use a single
+ *   timeout for each request submitted using submitRequest()
+ * - After the timeout triggers we go over the list of switches again and
+ *   check that a response has been received (by checking the head of the 
+ *   OFSwitchImpl's queue of pending requests)
+ * - We handle requests and timeouts in the same thread. We use a priority queue
+ *   to schedule them so we are guaranteed that they are processed in 
+ *   the same order as they are submitted. If a request times out we drop
+ *   the connection to this switch. 
+ * - Since we decouple submission of role change requests and actually sending
+ *   them we cannot check a received role reply against the controller's current 
+ *   role because the controller's current role could have changed again. 
+ * - Receiving Role Reply messages is handled by OFChannelHandler and
+ *   OFSwitchImpl directly. The OFSwitchImpl checks if the received request 
+ *   is as expected (xid and role match the head of the pending queue in 
+ *   OFSwitchImpl). If so
+ *   the switch updates its role. Otherwise the connection is dropped. If this
+ *   is the first reply, the SWITCH_SUPPORTS_NX_ROLE attribute is set.
+ *   Next, we call addSwitch(), removeSwitch() to update the list of active
+ *   switches if appropriate.
+ * - If we receive an Error indicating that roles are not supported by the 
+ *   switch, we set the SWITCH_SUPPORTS_NX_ROLE to false. We keep the 
+ *   switch connection alive while in MASTER and EQUAL role. 
+ *   (TODO: is this the right behavior for EQUAL??). If the role changes to
+ *   SLAVE the switch connection is dropped (remember: only if the switch
+ *   doesn't support role requests)  
+ *   The expected behavior is that the switch will probably try to reconnect
+ *   repeatedly (with some sort of exponential backoff), but after a  while 
+ *   will give-up and move on to the next controller-IP configured on the 
+ *   switch. This is the serial failover mechanism from OpenFlow spec v1.0.
+ *   
+ * New switch connection:
+ * - Switch handshake is done without sending any role request messages.
+ * - After handshake completes, switch is added to the list of connected switches
+ *   and we send the first role request message if role
+ *   requests are enabled. If roles are disabled automatically promote switch to
+ *   active switch list and clear FlowTable.
+ * - When we receive the first reply we proceed as above. In addition, if
+ *   the role request is for MASTER we wipe the flow table. We do not wipe
+ *   the flow table if the switch connected while role supported was disabled
+ *   on the controller. 
+ *
+ */
+public class RoleChanger {
+    // FIXME: Upon closer inspection DelayQueue seems to be somewhat broken. 
+    // We are required to implement a compareTo based on getDelay() and 
+    // getDelay() must return the remaining delay, thus it needs to use the 
+    // current time. So x1.compareTo(x1) can never return 0 as some time
+    // will have passed between evaluating both getDelays(). This is even worse
+    // if the thread happens to be preempted between calling the getDelay()
+    // For the time being we enforce a small delay between subsequent
+    // role request messages and hope that's long enough to not screw up
+    // ordering. In the long run we might want to use two threads and two queues
+    // (one for requests, one for timeouts)
+    // Sigh. 
+    protected DelayQueue<RoleChangeTask> pendingTasks;
+    protected long lastSubmitTime;
+    protected Thread workerThread;
+    protected long timeout;
+    protected static long DEFAULT_TIMEOUT = 15L*1000*1000*1000L; // 15s
+    protected static Logger log = LoggerFactory.getLogger(RoleChanger.class);
+    /** 
+     * A queued task to be handled by the Role changer thread. 
+     */
+    protected static class RoleChangeTask implements Delayed {
+        protected enum Type { 
+            /** This is a request. Dispatch the role update to switches */
+            REQUEST,
+            /** This is a timeout task. Check if all switches have 
+                correctly replied to the previously dispatched role request */
+            TIMEOUT
+        }
+        // The set of switches to work on
+        public Collection<OFSwitchImpl> switches;
+        public Role role;
+        public Type type;
+        // the time when the task should run as nanoTime() 
+        public long deadline;
+        public RoleChangeTask(Collection<OFSwitchImpl> switches, Role role, long deadline) {
+            this.switches = switches;
+            this.role = role;
+            this.type = Type.REQUEST;
+            this.deadline = deadline;
+        }
+        @Override
+        public int compareTo(Delayed o) {
+            Long timeRemaining = getDelay(TimeUnit.NANOSECONDS);
+            return timeRemaining.compareTo(o.getDelay(TimeUnit.NANOSECONDS));
+        }
+        @Override
+        public long getDelay(TimeUnit tu) {
+            long timeRemaining = deadline - System.nanoTime();
+            return tu.convert(timeRemaining, TimeUnit.NANOSECONDS);
+        }
+    }
+    
+    @LogMessageDoc(level="ERROR",
+                   message="RoleRequestWorker task had an uncaught exception.",
+                   explanation="An unknown occured while processing an HA " +
+                   		"role change event.",
+                   recommendation=LogMessageDoc.GENERIC_ACTION)                              
+    protected class RoleRequestWorker extends Thread  {
+        @Override
+        public void run() {
+            RoleChangeTask t;
+            boolean interrupted = false;
+            log.trace("RoleRequestWorker thread started");
+            try {
+                while (true) {
+                    try {
+                        t = pendingTasks.take();
+                    } catch (InterruptedException e) {
+                        // see http://www.ibm.com/developerworks/java/library/j-jtp05236/index.html
+                        interrupted = true;
+                        continue;
+                    }
+                    if (t.type == RoleChangeTask.Type.REQUEST) {
+                        sendRoleRequest(t.switches, t.role, t.deadline);
+                        // Queue the timeout
+                        t.type = RoleChangeTask.Type.TIMEOUT;
+                        t.deadline += timeout;
+                        pendingTasks.put(t);
+                    }
+                    else {
+                        verifyRoleReplyReceived(t.switches, t.deadline);
+                    }
+                }
+            }
+            catch (Exception e) {
+                // Should never get here
+                log.error("RoleRequestWorker task had an uncaught exception. ", 
+                          e);
+            }
+            finally {
+                // Be nice in case we earlier caught InterruptedExecution
+                if (interrupted)
+                    Thread.currentThread().interrupt();
+            }
+        } // end loop
+    }
+    
+    public RoleChanger() {
+        this.pendingTasks = new DelayQueue<RoleChangeTask>();
+        this.workerThread = new Thread(new RoleRequestWorker());
+        this.timeout = DEFAULT_TIMEOUT;
+        this.workerThread.start();
+    }
+    
+    
+    public synchronized void submitRequest(Collection<OFSwitchImpl> switches, Role role) {
+        long deadline = System.nanoTime();
+        // Grrr. stupid DelayQueue. Make sre we have at least 10ms between 
+        // role request messages.
+        if (deadline - lastSubmitTime < 10 * 1000*1000) 
+            deadline = lastSubmitTime + 10 * 1000*1000;
+        // make a copy of the list 
+        ArrayList<OFSwitchImpl> switches_copy = new ArrayList<OFSwitchImpl>(switches);
+        RoleChangeTask req = new RoleChangeTask(switches_copy, role, deadline);
+        pendingTasks.put(req);
+        lastSubmitTime = deadline;
+    }
+    
+    /**
+     * Send a role request message to switches. This checks the capabilities 
+     * of the switch for understanding role request messaging. Currently we only 
+     * support the OVS-style role request message, but once the controller 
+     * supports OF 1.2, this function will also handle sending out the 
+     * OF 1.2-style role request message.
+    * @param switches the collection of switches to send the request too
+     * @param role the role to request
+     */
+    @LogMessageDoc(level="WARN",
+            message="Failed to send role request message " + 
+                    "to switch {switch}: {message}. Disconnecting",
+            explanation="An I/O error occurred while attempting to change " +
+            		"the switch HA role.",
+            recommendation=LogMessageDoc.CHECK_SWITCH)                              
+    protected void sendRoleRequest(Collection<OFSwitchImpl> switches,
+                                   Role role, long cookie) {
+        // There are three cases to consider:
+        //
+        // 1) If the controller role at the point the switch connected was
+        //    null/disabled, then we never sent the role request probe to the
+        //    switch and therefore never set the SWITCH_SUPPORTS_NX_ROLE
+        //    attribute for the switch, so supportsNxRole is null. In that
+        //    case since we're now enabling role support for the controller
+        //    we should send out the role request probe/update to the switch.
+        //
+        // 2) If supportsNxRole == Boolean.TRUE then that means we've already
+        //    sent the role request probe to the switch and it replied with
+        //    a role reply message, so we know it supports role request
+        //    messages. Now we're changing the role and we want to send
+        //    it another role request message to inform it of the new role
+        //    for the controller.
+        //
+        // 3) If supportsNxRole == Boolean.FALSE, then that means we sent the
+        //    role request probe to the switch but it responded with an error
+        //    indicating that it didn't understand the role request message.
+        //    In that case we don't want to send it another role request that
+        //    it (still) doesn't understand. But if the new role of the
+        //    controller is SLAVE, then we don't want the switch to remain
+        //    connected to this controller. It might support the older serial
+        //    failover model for HA support, so we want to terminate the
+        //    connection and get it to initiate a connection with another
+        //    controller in its list of controllers. Eventually (hopefully, if
+        //    things are configured correctly) it will walk down its list of
+        //    controllers and connect to the current master controller.
+        Iterator<OFSwitchImpl> iter = switches.iterator();
+        while(iter.hasNext()) {
+            OFSwitchImpl sw = iter.next();
+            try {
+                Boolean supportsNxRole = (Boolean)
+                        sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE);
+                if ((supportsNxRole == null) || supportsNxRole) {
+                    // Handle cases #1 and #2
+                    sw.sendNxRoleRequest(role, cookie);
+                } else {
+                    // Handle case #3
+                    if (role == Role.SLAVE) {
+                        log.debug("Disconnecting switch {} that doesn't support " +
+                        "role request messages from a controller that went to SLAVE mode");
+                        // Closing the channel should result in a call to
+                        // channelDisconnect which updates all state 
+                        sw.getChannel().close();
+                        iter.remove();
+                    }
+                }
+            } catch (IOException e) {
+                log.warn("Failed to send role request message " + 
+                         "to switch {}: {}. Disconnecting",
+                         sw, e);
+                sw.getChannel().close();
+                iter.remove();
+            }
+        }
+    }
+    
+    /**
+     * Verify that switches have received a role reply message we sent earlier
+     * @param switches the collection of switches to send the request too
+     * @param cookie the cookie of the request
+     */
+    @LogMessageDoc(level="WARN",
+            message="Timeout while waiting for role reply from switch {switch}."
+                    + " Disconnecting",
+            explanation="Timed out waiting for the switch to respond to " +
+            		"a request to change the HA role.",
+            recommendation=LogMessageDoc.CHECK_SWITCH)                              
+    protected void verifyRoleReplyReceived(Collection<OFSwitchImpl> switches,
+                                   long cookie) {
+        for (OFSwitchImpl sw: switches) {
+            if (sw.checkFirstPendingRoleRequestCookie(cookie)) {
+                sw.getChannel().close();
+                log.warn("Timeout while waiting for role reply from switch {}."
+                         + " Disconnecting", sw);
+            }
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/SwitchStateException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/SwitchStateException.java
new file mode 100644
index 0000000..d2a928e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/SwitchStateException.java
@@ -0,0 +1,43 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+/**
+ * 
+ */
+public class SwitchStateException extends Exception {
+
+    private static final long serialVersionUID = 9153954512470002631L;
+
+    public SwitchStateException() {
+        super();
+    }
+
+    public SwitchStateException(String arg0, Throwable arg1) {
+        super(arg0, arg1);
+    }
+
+    public SwitchStateException(String arg0) {
+        super(arg0);
+    }
+
+    public SwitchStateException(Throwable arg0) {
+        super(arg0);
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java
new file mode 100644
index 0000000..0cbae32
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java
@@ -0,0 +1,104 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The service registry for an IFloodlightProvider.
+ * @author alexreimers
+ */
+public class FloodlightModuleContext implements IFloodlightModuleContext {
+	protected Map<Class<? extends IFloodlightService>, IFloodlightService> serviceMap;
+	protected Map<Class<? extends IFloodlightModule>, Map<String, String>> configParams;
+	protected Collection<IFloodlightModule> moduleSet;
+	
+	/**
+	 * Creates the ModuleContext for use with this IFloodlightProvider.
+	 * This will be used as a module registry for all IFloodlightModule(s).
+	 */
+	public FloodlightModuleContext() {
+		serviceMap = 
+		        new HashMap<Class<? extends IFloodlightService>,
+		                              IFloodlightService>();
+		configParams =
+		        new HashMap<Class<? extends IFloodlightModule>,
+		                        Map<String, String>>();
+	}
+	
+	/**
+	 * Adds a IFloodlightModule for this Context.
+	 * @param clazz the service class
+	 * @param service The IFloodlightService to add to the registry
+	 */
+	public void addService(Class<? extends IFloodlightService> clazz, 
+	                       IFloodlightService service) {
+		serviceMap.put(clazz, service);
+	}
+	
+	@SuppressWarnings("unchecked")
+    @Override
+	public <T extends IFloodlightService> T getServiceImpl(Class<T> service) {
+	    IFloodlightService s = serviceMap.get(service);
+		return (T)s;
+	}
+	
+	@Override
+	public Collection<Class<? extends IFloodlightService>> getAllServices() {
+	    return serviceMap.keySet();
+	}
+	
+	@Override
+	public Collection<IFloodlightModule> getAllModules() {
+	    return moduleSet;
+	}
+	
+	public void setModuleSet(Collection<IFloodlightModule> modSet) {
+	    this.moduleSet = modSet;
+	}
+	
+	/**
+	 * Gets the configuration parameter map for a module
+	 * @param module The module to get the configuration map for, usually yourself
+	 * @return A map containing all the configuration parameters for the module, may be empty
+	 */
+	@Override
+	public Map<String, String> getConfigParams(IFloodlightModule module) {
+	    Map<String, String> retMap = configParams.get(module.getClass());
+	    if (retMap == null) {
+	        // Return an empty map if none exists so the module does not
+	        // need to null check the map
+	        retMap = new HashMap<String, String>();
+	        configParams.put(module.getClass(), retMap);
+	    }
+
+	    // also add any configuration parameters for superclasses, but
+	    // only if more specific configuration does not override it
+	    for (Class<? extends IFloodlightModule> c : configParams.keySet()) {
+	        if (c.isInstance(module)) {
+	            for (Map.Entry<String, String> ent : configParams.get(c).entrySet()) {
+	                if (!retMap.containsKey(ent.getKey())) {
+	                    retMap.put(ent.getKey(), ent.getValue());
+	                }
+	            }
+	        }
+	    }
+
+	    return retMap;
+	}
+	
+	/**
+	 * Adds a configuration parameter for a module
+	 * @param mod The fully qualified module name to add the parameter to
+	 * @param key The configuration parameter key
+	 * @param value The configuration parameter value
+	 */
+	public void addConfigParam(IFloodlightModule mod, String key, String value) {
+	    Map<String, String> moduleParams = configParams.get(mod.getClass());
+	    if (moduleParams == null) {
+	        moduleParams = new HashMap<String, String>();
+	        configParams.put(mod.getClass(), moduleParams);
+	    }
+	    moduleParams.put(key, value);
+	}
+ }
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java
new file mode 100644
index 0000000..20ccc86
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java
@@ -0,0 +1,9 @@
+package net.floodlightcontroller.core.module;
+
+public class FloodlightModuleException extends Exception {
+	private static final long serialVersionUID = 1L;
+
+	public FloodlightModuleException(String error) {
+		super(error);
+	}
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java
new file mode 100644
index 0000000..45fe997
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java
@@ -0,0 +1,444 @@
+package net.floodlightcontroller.core.module;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Queue;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Finds all Floodlight modules in the class path and loads/starts them.
+ * @author alexreimers
+ *
+ */
+public class FloodlightModuleLoader {
+    protected static Logger logger = 
+            LoggerFactory.getLogger(FloodlightModuleLoader.class);
+
+    protected static Map<Class<? extends IFloodlightService>,
+                  Collection<IFloodlightModule>> serviceMap;
+    protected static Map<IFloodlightModule,
+                  Collection<Class<? extends 
+                                   IFloodlightService>>> moduleServiceMap;
+    protected static Map<String, IFloodlightModule> moduleNameMap;
+    protected static Object lock = new Object();
+    
+    protected FloodlightModuleContext floodlightModuleContext;
+	
+    public static final String COMPILED_CONF_FILE = 
+            "floodlightdefault.properties";
+    public static final String FLOODLIGHT_MODULES_KEY =
+            "floodlight.modules";
+    
+	public FloodlightModuleLoader() {
+	    floodlightModuleContext = new FloodlightModuleContext();
+	}
+	
+	/**
+	 * Finds all IFloodlightModule(s) in the classpath. It creates 3 Maps.
+	 * serviceMap -> Maps a service to a module
+	 * moduleServiceMap -> Maps a module to all the services it provides
+	 * moduleNameMap -> Maps the string name to the module
+	 * @throws FloodlightModuleException If two modules are specified in the configuration
+	 * that provide the same service.
+	 */
+	protected static void findAllModules(Collection<String> mList) throws FloodlightModuleException {
+	    synchronized (lock) {
+	        if (serviceMap != null) return;
+	        serviceMap = 
+	                new HashMap<Class<? extends IFloodlightService>,
+	                            Collection<IFloodlightModule>>();
+	        moduleServiceMap = 
+	                new HashMap<IFloodlightModule,
+	                            Collection<Class<? extends 
+	                                       IFloodlightService>>>();
+	        moduleNameMap = new HashMap<String, IFloodlightModule>();
+	        
+	        // Get all the current modules in the classpath
+	        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+	        ServiceLoader<IFloodlightModule> moduleLoader
+	            = ServiceLoader.load(IFloodlightModule.class, cl);
+	        // Iterate for each module, iterate through and add it's services
+	        Iterator<IFloodlightModule> moduleIter = moduleLoader.iterator();
+	        while (moduleIter.hasNext()) {
+	        	IFloodlightModule m = null;
+	        	try {
+	        		m = moduleIter.next();
+	        	} catch (ServiceConfigurationError sce) {
+	        		logger.debug("Could not find module");
+	        		//moduleIter.remove();
+	        		continue;
+	        	}
+	        //}
+	        //for (IFloodlightModule m : moduleLoader) {
+	            if (logger.isDebugEnabled()) {
+	                logger.debug("Found module " + m.getClass().getName());
+	            }
+
+	            // Set up moduleNameMap
+	            moduleNameMap.put(m.getClass().getCanonicalName(), m);
+
+	            // Set up serviceMap
+	            Collection<Class<? extends IFloodlightService>> servs =
+	                    m.getModuleServices();
+	            if (servs != null) {
+	                moduleServiceMap.put(m, servs);
+	                for (Class<? extends IFloodlightService> s : servs) {
+	                    Collection<IFloodlightModule> mods = 
+	                            serviceMap.get(s);
+	                    if (mods == null) {
+	                        mods = new ArrayList<IFloodlightModule>();
+	                        serviceMap.put(s, mods);
+	                    }
+	                    mods.add(m);
+	                    // Make sure they haven't specified duplicate modules in the config
+	                    int dupInConf = 0;
+	                    for (IFloodlightModule cMod : mods) {
+	                        if (mList.contains(cMod.getClass().getCanonicalName()))
+	                            dupInConf += 1;
+	                    }
+	                    
+	                    if (dupInConf > 1) {
+	                        String duplicateMods = "";
+                            for (IFloodlightModule mod : mods) {
+                                duplicateMods += mod.getClass().getCanonicalName() + ", ";
+                            }
+	                        throw new FloodlightModuleException("ERROR! The configuraiton" +
+	                                " file specifies more than one module that provides the service " +
+	                                s.getCanonicalName() +". Please specify only ONE of the " +
+	                                "following modules in the config file: " + duplicateMods);
+	                    }
+	                }
+	            }
+	        }
+	    }
+	}
+	
+	/**
+	 * Loads the modules from a specified configuration file.
+	 * @param fName The configuration file path
+	 * @return An IFloodlightModuleContext with all the modules to be started
+	 * @throws FloodlightModuleException
+	 */
+	@LogMessageDocs({
+	    @LogMessageDoc(level="INFO",
+	            message="Loading modules from file {file name}",
+	            explanation="The controller is initializing its module " +
+	                    "configuration from the specified properties file"),
+	    @LogMessageDoc(level="INFO",
+	            message="Loading default modules",
+	            explanation="The controller is initializing its module " +
+	                    "configuration to the default configuration"),
+	    @LogMessageDoc(level="ERROR",
+	            message="Could not load module configuration file",
+	            explanation="The controller failed to read the " +
+	            		"module configuration file",
+	            recommendation="Verify that the module configuration is " +
+	            		"present. " + LogMessageDoc.CHECK_CONTROLLER),
+	    @LogMessageDoc(level="ERROR",
+                message="Could not load default modules",
+                explanation="The controller failed to read the default " +
+                        "module configuration",
+                recommendation=LogMessageDoc.CHECK_CONTROLLER)
+	})
+	public IFloodlightModuleContext loadModulesFromConfig(String fName) 
+	        throws FloodlightModuleException {
+	    Properties prop = new Properties();
+	    
+	    File f = new File(fName);
+	    if (f.isFile()) {
+            logger.info("Loading modules from file {}", fName);
+            try {
+                prop.load(new FileInputStream(fName));
+            } catch (Exception e) {
+                logger.error("Could not load module configuration file", e);
+                System.exit(1);
+            }
+        } else {
+            logger.info("Loading default modules");
+            InputStream is = this.getClass().getClassLoader().
+                                    getResourceAsStream(COMPILED_CONF_FILE);
+            try {
+                prop.load(is);
+            } catch (IOException e) {
+                logger.error("Could not load default modules", e);
+                System.exit(1);
+            }
+        }
+        
+        String moduleList = prop.getProperty(FLOODLIGHT_MODULES_KEY)
+                                .replaceAll("\\s", "");
+        Collection<String> configMods = new ArrayList<String>();
+        configMods.addAll(Arrays.asList(moduleList.split(",")));
+        return loadModulesFromList(configMods, prop);
+	}
+	
+	/**
+	 * Loads modules (and their dependencies) specified in the list
+	 * @param mList The array of fully qualified module names
+	 * @param ignoreList The list of Floodlight services NOT to 
+	 * load modules for. Used for unit testing.
+	 * @return The ModuleContext containing all the loaded modules
+	 * @throws FloodlightModuleException
+	 */
+	protected IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop, 
+			Collection<IFloodlightService> ignoreList) throws FloodlightModuleException {
+		logger.debug("Starting module loader");
+		if (logger.isDebugEnabled() && ignoreList != null)
+			logger.debug("Not loading module services " + ignoreList.toString());
+
+        findAllModules(configMods);
+        
+        Collection<IFloodlightModule> moduleSet = new ArrayList<IFloodlightModule>();
+        Map<Class<? extends IFloodlightService>, IFloodlightModule> moduleMap =
+                new HashMap<Class<? extends IFloodlightService>,
+                            IFloodlightModule>();
+
+        Queue<String> moduleQ = new LinkedList<String>();
+        // Add the explicitly configured modules to the q
+        moduleQ.addAll(configMods);
+        Set<String> modsVisited = new HashSet<String>();
+        
+        while (!moduleQ.isEmpty()) {
+            String moduleName = moduleQ.remove();
+            if (modsVisited.contains(moduleName))
+                continue;
+            modsVisited.add(moduleName);
+            IFloodlightModule module = moduleNameMap.get(moduleName);
+            if (module == null) {
+                throw new FloodlightModuleException("Module " + 
+                        moduleName + " not found");
+            }
+            // If the module provies a service that is in the
+            // services ignorelist don't load it.
+            if ((ignoreList != null) && (module.getModuleServices() != null)) {
+            	for (IFloodlightService ifs : ignoreList) {
+            		for (Class<?> intsIgnore : ifs.getClass().getInterfaces()) {
+            			//System.out.println(intsIgnore.getName());
+        				// Check that the interface extends IFloodlightService
+        				//if (intsIgnore.isAssignableFrom(IFloodlightService.class)) {
+            			//System.out.println(module.getClass().getName());
+    					if (intsIgnore.isAssignableFrom(module.getClass())) {
+    						// We now ignore loading this module.
+    						logger.debug("Not loading module " + 
+    									 module.getClass().getCanonicalName() +
+    									 " because interface " +
+    									 intsIgnore.getCanonicalName() +
+    									 " is in the ignore list.");
+    						
+    						continue;
+    					}
+        				//}
+            		}
+            	}
+            }
+            
+            // Add the module to be loaded
+            addModule(moduleMap, moduleSet, module);
+            // Add it's dep's to the queue
+            Collection<Class<? extends IFloodlightService>> deps = 
+                    module.getModuleDependencies();
+            if (deps != null) {
+                for (Class<? extends IFloodlightService> c : deps) {
+                    IFloodlightModule m = moduleMap.get(c);
+                    if (m == null) {
+                        Collection<IFloodlightModule> mods = serviceMap.get(c);
+                        // Make sure only one module is loaded
+                        if ((mods == null) || (mods.size() == 0)) {
+                            throw new FloodlightModuleException("ERROR! Could not " +
+                                    "find an IFloodlightModule that provides service " +
+                                    c.toString());
+                        } else if (mods.size() == 1) {
+                            IFloodlightModule mod = mods.iterator().next();
+                            if (!modsVisited.contains(mod.getClass().getCanonicalName()))
+                                moduleQ.add(mod.getClass().getCanonicalName());
+                        } else {
+                            boolean found = false;
+                            for (IFloodlightModule moduleDep : mods) {
+                                if (configMods.contains(moduleDep.getClass().getCanonicalName())) {
+                                    // Module will be loaded, we can continue
+                                    found = true;
+                                    break;
+                                }
+                            }
+                            if (!found) {
+                                String duplicateMods = "";
+                                for (IFloodlightModule mod : mods) {
+                                    duplicateMods += mod.getClass().getCanonicalName() + ", ";
+                                }
+                                throw new FloodlightModuleException("ERROR! Found more " + 
+                                    "than one (" + mods.size() + ") IFloodlightModules that provides " +
+                                    "service " + c.toString() + 
+                                    ". Please specify one of the following modules in the config: " + 
+                                    duplicateMods);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        
+        floodlightModuleContext.setModuleSet(moduleSet);
+        parseConfigParameters(prop);
+        initModules(moduleSet);
+        startupModules(moduleSet);
+        
+        return floodlightModuleContext;
+	}
+	
+	/**
+	 * Loads modules (and their dependencies) specified in the list.
+	 * @param configMods The collection of fully qualified module names to load.
+	 * @param prop The list of properties that are configuration options.
+	 * @return The ModuleContext containing all the loaded modules.
+	 * @throws FloodlightModuleException
+	 */
+	public IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop) 
+            throws FloodlightModuleException {
+		return loadModulesFromList(configMods, prop, null);
+    }
+	
+	/**
+	 * Add a module to the set of modules to load and register its services
+	 * @param moduleMap the module map
+	 * @param moduleSet the module set
+	 * @param module the module to add
+	 */
+	protected void addModule(Map<Class<? extends IFloodlightService>, 
+                                           IFloodlightModule> moduleMap,
+                            Collection<IFloodlightModule> moduleSet,
+                            IFloodlightModule module) {
+        if (!moduleSet.contains(module)) {
+            Collection<Class<? extends IFloodlightService>> servs =
+                    moduleServiceMap.get(module);
+            if (servs != null) {
+                for (Class<? extends IFloodlightService> c : servs)
+                    moduleMap.put(c, module);
+            }
+            moduleSet.add(module);
+        }
+	}
+
+    /**
+     * Allocate  service implementations and then init all the modules
+     * @param moduleSet The set of modules to call their init function on
+     * @throws FloodlightModuleException If a module can not properly be loaded
+     */
+    protected void initModules(Collection<IFloodlightModule> moduleSet) 
+                                           throws FloodlightModuleException {
+        for (IFloodlightModule module : moduleSet) {            
+            // Get the module's service instance(s)
+            Map<Class<? extends IFloodlightService>, 
+                IFloodlightService> simpls = module.getServiceImpls();
+
+            // add its services to the context
+            if (simpls != null) {
+                for (Entry<Class<? extends IFloodlightService>, 
+                        IFloodlightService> s : simpls.entrySet()) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Setting " + s.getValue() + 
+                                     "  as provider for " + 
+                                     s.getKey().getCanonicalName());
+                    }
+                    if (floodlightModuleContext.getServiceImpl(s.getKey()) == null) {
+                        floodlightModuleContext.addService(s.getKey(),
+                                                           s.getValue());
+                    } else {
+                        throw new FloodlightModuleException("Cannot set "
+                                                            + s.getValue()
+                                                            + " as the provider for "
+                                                            + s.getKey().getCanonicalName()
+                                                            + " because "
+                                                            + floodlightModuleContext.getServiceImpl(s.getKey())
+                                                            + " already provides it");
+                    }
+                }
+            }
+        }
+        
+        for (IFloodlightModule module : moduleSet) {
+            // init the module
+            if (logger.isDebugEnabled()) {
+                logger.debug("Initializing " + 
+                             module.getClass().getCanonicalName());
+            }
+            module.init(floodlightModuleContext);
+        }
+    }
+    
+    /**
+     * Call each loaded module's startup method
+     * @param moduleSet the module set to start up
+     */
+    protected void startupModules(Collection<IFloodlightModule> moduleSet) {
+        for (IFloodlightModule m : moduleSet) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Starting " + m.getClass().getCanonicalName());
+            }
+            m.startUp(floodlightModuleContext);
+        }
+    }
+    
+    /**
+     * Parses configuration parameters for each module
+     * @param prop The properties file to use
+     */
+    @LogMessageDoc(level="WARN",
+                   message="Module {module} not found or loaded. " +
+                           "Not adding configuration option {key} = {value}",
+                   explanation="Ignoring a configuration parameter for a " +
+                   		"module that is not loaded.")
+    protected void parseConfigParameters(Properties prop) {
+    	if (prop == null) return;
+    	
+        Enumeration<?> e = prop.propertyNames();
+        while (e.hasMoreElements()) {
+            String key = (String) e.nextElement();
+            // Ignore module list key
+            if (key.equals(FLOODLIGHT_MODULES_KEY)) {
+                continue;
+            }
+            
+            String configValue = null;
+            int lastPeriod = key.lastIndexOf(".");
+            String moduleName = key.substring(0, lastPeriod);
+            String configKey = key.substring(lastPeriod + 1);
+            // Check to see if it's overridden on the command line
+            String systemKey = System.getProperty(key);
+            if (systemKey != null) {
+                configValue = systemKey;
+            } else {
+                configValue = prop.getProperty(key);
+            }
+            
+            IFloodlightModule mod = moduleNameMap.get(moduleName);
+            if (mod == null) {
+                logger.warn("Module {} not found or loaded. " +
+                		    "Not adding configuration option {} = {}", 
+                            new Object[]{moduleName, configKey, configValue});
+            } else {
+                floodlightModuleContext.addConfigParam(mod, configKey, configValue);
+            }
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java
new file mode 100644
index 0000000..f8b196b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java
@@ -0,0 +1,74 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.Collection;
+import java.util.Map;
+
+
+/**
+ * Defines an interface for loadable Floodlight modules.
+ * 
+ * At a high level, these functions are called in the following order:
+ * <ol>
+ * <li> getServices() : what services does this module provide
+ * <li> getDependencies() : list the dependencies
+ * <li> init() : internal initializations (don't touch other modules)
+ * <li> startUp() : external initializations (<em>do</em> touch other modules)
+ * </ol>
+ * 
+ * @author alexreimers
+ */
+public interface IFloodlightModule {
+	
+	/**
+	 * Return the list of interfaces that this module implements.
+	 * All interfaces must inherit IFloodlightService
+	 * @return
+	 */
+	
+	public Collection<Class<? extends IFloodlightService>> getModuleServices();
+	
+	/**
+	 * Instantiate (as needed) and return objects that implement each
+	 * of the services exported by this module.  The map returned maps
+	 * the implemented service to the object.  The object could be the
+	 * same object or different objects for different exported services.
+	 * @return The map from service interface class to service implementation
+	 */
+	public Map<Class<? extends IFloodlightService>,
+	           IFloodlightService> getServiceImpls();
+	
+	/**
+	 * Get a list of Modules that this module depends on.  The module system
+	 * will ensure that each these dependencies is resolved before the 
+	 * subsequent calls to init().
+	 * @return The Collection of IFloodlightServices that this module depends
+	 *         on.
+	 */
+	
+	public Collection<Class<? extends IFloodlightService>> getModuleDependencies();
+	
+	/**
+	 * This is a hook for each module to do its <em>internal</em> initialization, 
+	 * e.g., call setService(context.getService("Service"))
+	 * 
+	 * All module dependencies are resolved when this is called, but not every module 
+	 * is initialized.
+	 * 
+	 * @param context
+	 * @throws FloodlightModuleException
+	 */
+	
+	void init(FloodlightModuleContext context) throws FloodlightModuleException;
+	
+	/**
+	 * This is a hook for each module to do its <em>external</em> initializations,
+	 * e.g., register for callbacks or query for state in other modules
+	 * 
+	 * It is expected that this function will not block and that modules that want
+	 * non-event driven CPU will spawn their own threads.
+	 * 
+	 * @param context
+	 */
+	
+	void startUp(FloodlightModuleContext context); 
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java
new file mode 100644
index 0000000..2c058a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java
@@ -0,0 +1,35 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.Collection;
+import java.util.Map;
+
+	
+public interface IFloodlightModuleContext {	
+    /**
+     * Retrieves a casted version of a module from the registry.
+     * @param name The IFloodlightService object type
+     * @return The IFloodlightService
+     * @throws FloodlightModuleException If the module was not found 
+     * or a ClassCastException was encountered.
+     */
+    public <T extends IFloodlightService> T getServiceImpl(Class<T> service);
+    
+    /**
+     * Returns all loaded services
+     * @return A collection of service classes that have been loaded
+     */
+    public Collection<Class<? extends IFloodlightService>> getAllServices();
+    
+    /**
+     * Returns all loaded modules
+     * @return All Floodlight modules that are going to be loaded
+     */
+    public Collection<IFloodlightModule> getAllModules();
+    
+    /**
+     * Gets module specific configuration parameters.
+     * @param module The module to get the configuration parameters for
+     * @return A key, value map of the configuration options
+     */
+    public Map<String, String> getConfigParams(IFloodlightModule module);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightService.java
new file mode 100644
index 0000000..5974b3a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightService.java
@@ -0,0 +1,11 @@
+package net.floodlightcontroller.core.module;
+
+/**
+ * This is the base interface for any IFloodlightModule package that provides 
+ * a service.
+ * @author alexreimers
+ *
+ */
+public abstract interface IFloodlightService {
+    // This space is intentionally left blank....don't touch it
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java
new file mode 100644
index 0000000..a73a17f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java
@@ -0,0 +1,104 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Returns list of modules loaded by Floodlight.
+ * @author Rob Sherwood
+ */
+public class ModuleLoaderResource extends ServerResource {
+    protected static Logger log = 
+            LoggerFactory.getLogger(ModuleLoaderResource.class);
+    
+    /**
+     * Retrieves information about loaded modules.
+     * @return Information about loaded modules.
+     */
+    @Get("json")
+    public Map<String, Object> retrieve() {
+    	return retrieveInternal(false);
+    }
+    
+    /**
+     * Retrieves all modules and their dependencies available
+     * to Floodlight.
+     * @param loadedOnly Whether to return all modules available or only the ones loaded.
+     * @return Information about modules available or loaded.
+     */
+    public Map<String, Object> retrieveInternal(boolean loadedOnly) {    
+        Map<String, Object> model = new HashMap<String, Object>();
+
+        Set<String> loadedModules = new HashSet<String>();
+        for (Object val : getContext().getAttributes().values()) {
+        	if ((val instanceof IFloodlightModule) || (val instanceof IFloodlightService)) {
+        		String serviceImpl = val.getClass().getCanonicalName();
+        		loadedModules.add(serviceImpl);
+        		// log.debug("Tracking serviceImpl " + serviceImpl);
+        	}
+        }
+
+        for (String moduleName : 
+        				FloodlightModuleLoader.moduleNameMap.keySet() ) {
+        	Map<String,Object> moduleInfo = new HashMap<String, Object>();
+
+        	IFloodlightModule module = 
+        				FloodlightModuleLoader.moduleNameMap.get(
+        						moduleName);
+        		
+        	Collection<Class<? extends IFloodlightService>> deps = 
+        			module.getModuleDependencies();
+        	if ( deps == null)
+            	deps = new HashSet<Class<? extends IFloodlightService>>();
+        	Map<String,Object> depsMap = new HashMap<String, Object> ();
+        	for (Class<? extends IFloodlightService> service : deps) {
+        		Object serviceImpl = getContext().getAttributes().get(service.getCanonicalName());
+        		if (serviceImpl != null)
+        			depsMap.put(service.getCanonicalName(), serviceImpl.getClass().getCanonicalName());
+        		else
+        			depsMap.put(service.getCanonicalName(), "<unresolved>");
+
+        	}
+            moduleInfo.put("depends", depsMap);
+        	
+            Collection<Class<? extends IFloodlightService>> provides = 
+            		module.getModuleServices();
+        	if ( provides == null)
+            	provides = new HashSet<Class<? extends IFloodlightService>>();
+        	Map<String,Object> providesMap = new HashMap<String,Object>();
+        	for (Class<? extends IFloodlightService> service : provides) {
+        		providesMap.put(service.getCanonicalName(), module.getServiceImpls().get(service).getClass().getCanonicalName());
+        	}
+        	moduleInfo.put("provides", providesMap);            		
+
+    		moduleInfo.put("loaded", false);	// not loaded, by default
+
+        	// check if this module is loaded directly
+        	if (loadedModules.contains(module.getClass().getCanonicalName())) {
+        		moduleInfo.put("loaded", true);  			
+        	} else {
+        		// if not, then maybe one of the services it exports is loaded
+        		for (Class<? extends IFloodlightService> service : provides) {
+        			String modString = module.getServiceImpls().get(service).getClass().getCanonicalName();
+        			if (loadedModules.contains(modString))
+                		moduleInfo.put("loaded", true);
+        			/* else 
+        				log.debug("ServiceImpl not loaded " + modString); */
+        		}
+        	}
+
+        	if ((Boolean)moduleInfo.get("loaded")|| !loadedOnly )
+        		model.put(moduleName, moduleInfo);
+        }            
+        return model;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java
new file mode 100644
index 0000000..7a44f1d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java
@@ -0,0 +1,44 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.types;
+
+public class MacVlanPair {
+    public Long mac;
+    public Short vlan;
+    public MacVlanPair(Long mac, Short vlan) {
+        this.mac = mac;
+        this.vlan = vlan;
+    }
+    
+    public long getMac() {
+        return mac.longValue();
+    }
+    
+    public short getVlan() {
+        return vlan.shortValue();
+    }
+    
+    public boolean equals(Object o) {
+        return (o instanceof MacVlanPair) && (mac.equals(((MacVlanPair) o).mac))
+            && (vlan.equals(((MacVlanPair) o).vlan));
+    }
+    
+    public int hashCode() {
+        return mac.hashCode() ^ vlan.hashCode();
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java
new file mode 100644
index 0000000..0e91bc9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java
@@ -0,0 +1,40 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.types;
+
+import org.openflow.protocol.OFMessage;
+
+import net.floodlightcontroller.core.IOFSwitch;
+
+public class SwitchMessagePair {
+    private final IOFSwitch sw;
+    private final OFMessage msg;
+    
+    public SwitchMessagePair(IOFSwitch sw, OFMessage msg) {
+        this.sw = sw;
+        this.msg = msg;
+    }
+    
+    public IOFSwitch getSwitch() {
+        return this.sw;
+    }
+    
+    public OFMessage getMessage() {
+        return this.msg;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/AppCookie.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/AppCookie.java
new file mode 100644
index 0000000..210823e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/AppCookie.java
@@ -0,0 +1,54 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.util;
+
+/***
+ * FIXME Need a system for registering/binding applications to a unique ID
+ * 
+ * @author capveg
+ *
+ */
+
+public class AppCookie {
+    static final int APP_ID_BITS = 12;
+    static final int APP_ID_SHIFT = (64 - APP_ID_BITS);
+    // we have bits 13-31 unused here ... that's ok!
+    static final int USER_BITS = 32;
+    static final int USER_SHIFT = 0;
+
+
+    /**
+     * Encapsulate an application ID and a user block of stuff into a cookie
+     * 
+     * @param application An ID to identify the application
+     * @param user Some application specific data
+     * @return a cookie for use in OFFlowMod.setCookie()
+     */
+    
+    static public long makeCookie(int application, int user) {
+        return ((application & ((1L << APP_ID_BITS) - 1)) << APP_ID_SHIFT) | user;
+    }
+    
+    static public int extractApp(long cookie) {
+        return (int)((cookie>> APP_ID_SHIFT) & ((1L << APP_ID_BITS) - 1));
+    }
+    
+    static public int extractUser(long cookie) {
+        return (int)((cookie>> USER_SHIFT) & ((1L << USER_BITS) - 1));
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java
new file mode 100644
index 0000000..58b543c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java
@@ -0,0 +1,136 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.util;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.IListener;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+/**
+ * Maintain lists of listeners ordered by dependency.  
+ * 
+ * @author readams
+ *
+ */
+public class ListenerDispatcher<U, T extends IListener<U>> {
+    protected static Logger logger = LoggerFactory.getLogger(ListenerDispatcher.class);
+    List<T> listeners = null;
+    
+    private void visit(List<T> newlisteners, U type, HashSet<T> visited, 
+                       List<T> ordering, T listener) {
+        if (!visited.contains(listener)) {
+            visited.add(listener);
+            
+            for (T i : newlisteners) {
+                if (ispre(type, i, listener)) {
+                    visit(newlisteners, type, visited, ordering, i);
+                }
+            }
+            ordering.add(listener);
+        }
+    }
+    
+    private boolean ispre(U type, T l1, T l2) {
+        return (l2.isCallbackOrderingPrereq(type, l1.getName()) ||
+                l1.isCallbackOrderingPostreq(type, l2.getName()));
+    }
+    
+    /**
+     * Add a listener to the list of listeners
+     * @param listener
+     */
+    @LogMessageDoc(level="ERROR",
+                   message="No listener dependency solution: " +
+                           "No listeners without incoming dependencies",
+                   explanation="The set of listeners installed " +
+                   		"have dependencies with no solution",
+                   recommendation="Install a different set of listeners " +
+                   		"or install all dependencies.  This is a defect in " +
+                   		"the controller installation.")
+    public void addListener(U type, T listener) {
+        List<T> newlisteners = new ArrayList<T>();
+        if (listeners != null)
+            newlisteners.addAll(listeners);
+
+        newlisteners.add(listener);
+        // Find nodes without outgoing edges
+        List<T> terminals = new ArrayList<T>(); 
+        for (T i : newlisteners) {
+            boolean isterm = true;
+            for (T j : newlisteners) {
+                if (ispre(type, i, j)) {
+                    isterm = false;
+                    break;
+                }
+            }
+            if (isterm) {
+                terminals.add(i);
+            }
+        }
+        
+        if (terminals.size() == 0) {
+            logger.error("No listener dependency solution: " +
+            		     "No listeners without incoming dependencies");
+            listeners = newlisteners;
+            return;
+        }
+        
+        // visit depth-first traversing in the opposite order from
+        // the dependencies.  Note we will not generally detect cycles
+        HashSet<T> visited = new HashSet<T>();
+        List<T> ordering = new ArrayList<T>(); 
+        for (T term : terminals) {
+            visit(newlisteners, type, visited, ordering, term);
+        }
+        listeners = ordering;
+    }
+
+    /**
+     * Remove the given listener
+     * @param listener the listener to remove
+     */
+    public void removeListener(T listener) {
+        if (listeners != null) {
+            List<T> newlisteners = new ArrayList<T>();
+            newlisteners.addAll(listeners);
+            newlisteners.remove(listener);
+            listeners = newlisteners;
+        }
+    }
+    
+    /**
+     * Clear all listeners
+     */
+    public void clearListeners() {
+        listeners = new ArrayList<T>();
+    }
+    
+    /** 
+     * Get the ordered list of listeners ordered by dependencies 
+     * @return
+     */
+    public List<T> getOrderedListeners() {
+        return listeners;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/MutableInteger.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/MutableInteger.java
new file mode 100644
index 0000000..0f070fa
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/MutableInteger.java
@@ -0,0 +1,55 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.util;
+
+public class MutableInteger extends Number {
+    private static final long serialVersionUID = 1L;
+    int mutableInt;
+    
+    public MutableInteger(int value) {
+        this.mutableInt = value;
+    }
+    
+    public void setValue(int value) {
+        this.mutableInt = value;
+    }
+    
+    @Override
+    public double doubleValue() {
+        return (double) mutableInt;
+    }
+
+    @Override
+    public float floatValue() {
+        // TODO Auto-generated method stub
+        return (float) mutableInt;
+    }
+
+    @Override
+    public int intValue() {
+        // TODO Auto-generated method stub
+        return mutableInt;
+    }
+
+    @Override
+    public long longValue() {
+        // TODO Auto-generated method stub
+        return (long) mutableInt;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java
new file mode 100644
index 0000000..07729e5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java
@@ -0,0 +1,162 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.util;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This allows you to represent a task that should be queued for future execution
+ * but where you only want the task to complete once in response to some sequence 
+ * of events.  For example, if you get a change notification and want to reload state,
+ * you only want to reload the state once, at the end, and don't want to queue
+ * an update for every notification that might come in.
+ * 
+ * The semantics are as follows:
+ * * If the task hasn't begun yet, do not queue a new task
+ * * If the task has begun, set a bit to restart it after the current task finishes
+ */
+public class SingletonTask {
+    protected static Logger logger = LoggerFactory.getLogger(SingletonTask.class);
+            
+    protected static class SingletonTaskContext  {
+        protected boolean taskShouldRun = false;
+        protected boolean taskRunning = false;
+
+        protected SingletonTaskWorker waitingTask = null;
+    }
+
+    protected static class SingletonTaskWorker implements Runnable  {
+        SingletonTask parent;
+        boolean canceled = false;
+        long nextschedule = 0;
+
+        public SingletonTaskWorker(SingletonTask parent) {
+            super();
+            this.parent = parent;
+        }
+
+        @Override
+        @LogMessageDoc(level="ERROR",
+                       message="Exception while executing task",
+                       recommendation=LogMessageDoc.GENERIC_ACTION)
+        public void run() {
+            synchronized (parent.context) {
+                if (canceled || !parent.context.taskShouldRun)
+                    return;
+
+                parent.context.taskRunning = true;
+                parent.context.taskShouldRun = false;
+            }
+
+            try {
+                parent.task.run();
+            } catch (Exception e) {
+                logger.error("Exception while executing task", e);
+            }
+
+            synchronized (parent.context) {
+                parent.context.taskRunning = false;
+
+                if (parent.context.taskShouldRun) {
+                    long now = System.nanoTime();
+                    if ((nextschedule <= 0 || (nextschedule - now) <= 0)) {
+                        parent.ses.execute(this);
+                    } else {
+                        parent.ses.schedule(this, 
+                                            nextschedule-now, 
+                                            TimeUnit.NANOSECONDS);
+                    }
+                }
+            }
+        }
+    }
+
+    protected SingletonTaskContext context = new SingletonTaskContext();
+    protected Runnable task;
+    protected ScheduledExecutorService ses;
+
+
+    /**
+     * Construct a new SingletonTask for the given runnable.  The context
+     * is used to manage the state of the task execution and can be shared
+     * by more than one instance of the runnable.
+     * @param context
+     * @param Task
+     */
+    public SingletonTask(ScheduledExecutorService ses,
+            Runnable task) {
+        super();
+        this.task = task;
+        this.ses = ses;
+    }
+
+    /**
+     * Schedule the task to run if there's not already a task scheduled
+     * If there is such a task waiting that has not already started, it
+     * cancel that task and reschedule it to run at the given time.  If the
+     * task is already started, it will cause the task to be rescheduled once
+     * it completes to run after delay from the time of reschedule.
+     * 
+     * @param delay the delay in scheduling
+     * @param unit the timeunit of the delay
+     */
+    public void reschedule(long delay, TimeUnit unit) {
+        boolean needQueue = true;
+        SingletonTaskWorker stw = null;
+
+        synchronized (context) {
+            if (context.taskRunning || context.taskShouldRun) {
+                if (context.taskRunning) {
+                    // schedule to restart at the right time
+                    if (delay > 0) {
+                        long now = System.nanoTime();
+                        long then = 
+                            now + TimeUnit.NANOSECONDS.convert(delay, unit);
+                        context.waitingTask.nextschedule = then;
+                    } else {
+                        context.waitingTask.nextschedule = 0;
+                    }
+                    needQueue = false;
+                } else {
+                    // cancel and requeue
+                    context.waitingTask.canceled = true;
+                    context.waitingTask = null;
+                }
+            }
+
+            context.taskShouldRun = true;
+
+            if (needQueue) {
+                stw = context.waitingTask = new SingletonTaskWorker(this);                    
+            }
+        }
+
+        if (needQueue) {
+            if (delay <= 0)
+                ses.execute(stw);
+            else
+                ses.schedule(stw, delay, unit);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java
new file mode 100644
index 0000000..d012fc8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java
@@ -0,0 +1,174 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import java.lang.Thread.State;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.types.MacVlanPair;
+
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.util.HexString;
+import org.restlet.resource.Get;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Return switch statistics information for all switches
+ * @author readams
+ */
+public class AllSwitchStatisticsResource extends SwitchResourceBase {
+    protected static Logger log = 
+        LoggerFactory.getLogger(AllSwitchStatisticsResource.class);
+    
+    @Get("json")
+    public Map<String, Object> retrieve() {    
+        String statType = (String) getRequestAttributes().get("statType");
+        return retrieveInternal(statType);
+    }
+        
+    public Map<String, Object> retrieveInternal(String statType) {
+        HashMap<String, Object> model = new HashMap<String, Object>();
+
+        OFStatisticsType type = null;
+        REQUESTTYPE rType = null;
+        
+        if (statType.equals("port")) {
+            type = OFStatisticsType.PORT;
+            rType = REQUESTTYPE.OFSTATS;
+        } else if (statType.equals("queue")) {
+            type = OFStatisticsType.QUEUE;
+            rType = REQUESTTYPE.OFSTATS;
+        } else if (statType.equals("flow")) {
+            type = OFStatisticsType.FLOW;
+            rType = REQUESTTYPE.OFSTATS;
+        } else if (statType.equals("aggregate")) {
+            type = OFStatisticsType.AGGREGATE;
+            rType = REQUESTTYPE.OFSTATS;
+        } else if (statType.equals("desc")) {
+            type = OFStatisticsType.DESC;
+            rType = REQUESTTYPE.OFSTATS;
+        } else if (statType.equals("table")) {
+            type = OFStatisticsType.TABLE;
+            rType = REQUESTTYPE.OFSTATS;
+        } else if (statType.equals("features")) {
+            rType = REQUESTTYPE.OFFEATURES;
+        } else {
+            return model;
+        }
+        
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                    get(IFloodlightProviderService.class.getCanonicalName());        
+        Long[] switchDpids = floodlightProvider.getSwitches().keySet().toArray(new Long[0]);
+        List<GetConcurrentStatsThread> activeThreads = new ArrayList<GetConcurrentStatsThread>(switchDpids.length);
+        List<GetConcurrentStatsThread> pendingRemovalThreads = new ArrayList<GetConcurrentStatsThread>();
+        GetConcurrentStatsThread t;
+        for (Long l : switchDpids) {
+            t = new GetConcurrentStatsThread(l, rType, type);
+            activeThreads.add(t);
+            t.start();
+        }
+        
+        // Join all the threads after the timeout. Set a hard timeout
+        // of 12 seconds for the threads to finish. If the thread has not
+        // finished the switch has not replied yet and therefore we won't 
+        // add the switch's stats to the reply.
+        for (int iSleepCycles = 0; iSleepCycles < 12; iSleepCycles++) {
+            for (GetConcurrentStatsThread curThread : activeThreads) {
+                if (curThread.getState() == State.TERMINATED) {
+                    if (rType == REQUESTTYPE.OFSTATS) {
+                        model.put(HexString.toHexString(curThread.getSwitchId()), curThread.getStatisticsReply());
+                    } else if (rType == REQUESTTYPE.OFFEATURES) {
+                        model.put(HexString.toHexString(curThread.getSwitchId()), curThread.getFeaturesReply());
+                    }
+                    pendingRemovalThreads.add(curThread);
+                }
+            }
+            
+            // remove the threads that have completed the queries to the switches
+            for (GetConcurrentStatsThread curThread : pendingRemovalThreads) {
+                activeThreads.remove(curThread);
+            }
+            // clear the list so we don't try to double remove them
+            pendingRemovalThreads.clear();
+            
+            // if we are done finish early so we don't always get the worst case
+            if (activeThreads.isEmpty()) {
+                break;
+            }
+            
+            // sleep for 1 s here
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                log.error("Interrupted while waiting for statistics", e);
+            }
+        }
+        
+        return model;
+    }
+    
+    protected class GetConcurrentStatsThread extends Thread {
+        private List<OFStatistics> switchReply;
+        private long switchId;
+        private OFStatisticsType statType;
+        private REQUESTTYPE requestType;
+        private OFFeaturesReply featuresReply;
+        private Map<MacVlanPair, Short> switchTable;
+        
+        public GetConcurrentStatsThread(long switchId, REQUESTTYPE requestType, OFStatisticsType statType) {
+            this.switchId = switchId;
+            this.requestType = requestType;
+            this.statType = statType;
+            this.switchReply = null;
+            this.featuresReply = null;
+            this.switchTable = null;
+        }
+        
+        public List<OFStatistics> getStatisticsReply() {
+            return switchReply;
+        }
+        
+        public OFFeaturesReply getFeaturesReply() {
+            return featuresReply;
+        }
+        
+        public Map<MacVlanPair, Short> getSwitchTable() {
+            return switchTable;
+        }
+        
+        public long getSwitchId() {
+            return switchId;
+        }
+        
+        public void run() {
+            if ((requestType == REQUESTTYPE.OFSTATS) && (statType != null)) {
+                switchReply = getSwitchStatistics(switchId, statType);
+            } else if (requestType == REQUESTTYPE.OFFEATURES) {
+                featuresReply = getSwitchFeaturesReply(switchId);
+            }
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerMemoryResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerMemoryResource.java
new file mode 100644
index 0000000..bcb2bd1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerMemoryResource.java
@@ -0,0 +1,39 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+/**
+ * Retrieve floodlight memory state
+ * @author readams
+ */
+public class ControllerMemoryResource extends ServerResource {
+    @Get("json")
+    public Map<String, Object> retrieve() {
+        HashMap<String, Object> model = new HashMap<String, Object>();
+        Runtime runtime = Runtime.getRuntime();
+        model.put("total", new Long(runtime.totalMemory()));
+        model.put("free", new Long(runtime.freeMemory()));
+        return model;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java
new file mode 100644
index 0000000..652058e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java
@@ -0,0 +1,57 @@
+package net.floodlightcontroller.core.web;
+
+import org.restlet.data.Status;
+import org.restlet.resource.ServerResource;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ControllerRoleResource extends ServerResource {
+
+    protected static Logger log = LoggerFactory.getLogger(ControllerRoleResource.class);
+
+    @Get("json")
+    public RoleInfo getRole() {
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                    get(IFloodlightProviderService.class.getCanonicalName());
+        return new RoleInfo(floodlightProvider.getRole());
+    }
+    
+    @Post("json")
+    @LogMessageDoc(level="WARN",
+                   message="Invalid role value specified in REST API to " +
+                      "set controller role",
+                   explanation="An HA role change request was malformed.",
+                   recommendation=LogMessageDoc.CHECK_CONTROLLER)
+    public void setRole(RoleInfo roleInfo) {
+        //Role role = Role.lookupRole(roleInfo.getRole());
+        Role role = null;
+        try {
+            role = Role.valueOf(roleInfo.getRole().toUpperCase());
+        }
+        catch (IllegalArgumentException e) {
+            // The role value in the REST call didn't match a valid
+            // role name, so just leave the role as null and handle
+            // the error below.
+        }
+        if (role == null) {
+            log.warn ("Invalid role value specified in REST API to " +
+            		  "set controller role");
+            setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Invalid role value");
+            return;
+        }
+        
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                    get(IFloodlightProviderService.class.getCanonicalName());
+        
+        floodlightProvider.setRole(role);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSummaryResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSummaryResource.java
new file mode 100644
index 0000000..20fbf85
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSummaryResource.java
@@ -0,0 +1,40 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by Shudong Zhou, Big Switch Networks
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import java.util.Map;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+
+/**
+ * Get summary counters registered by all modules
+ * @author shudongz
+ */
+public class ControllerSummaryResource extends ServerResource {
+    @Get("json")
+    public Map<String, Object> retrieve() {
+        IFloodlightProviderService floodlightProvider = 
+            (IFloodlightProviderService)getContext().getAttributes().
+                get(IFloodlightProviderService.class.getCanonicalName());
+        return floodlightProvider.getControllerInfo("summary");
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java
new file mode 100644
index 0000000..454f566
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java
@@ -0,0 +1,81 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.util.FilterIterator;
+
+import org.openflow.util.HexString;
+import org.restlet.data.Form;
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+/**
+ * Get a list of switches connected to the controller
+ * @author readams
+ */
+public class ControllerSwitchesResource extends ServerResource {
+    public static final String DPID_ERROR = 
+            "Invalid Switch DPID: must be a 64-bit quantity, expressed in " + 
+            "hex as AA:BB:CC:DD:EE:FF:00:11";
+    
+    @Get("json")
+    public Iterator<IOFSwitch> retrieve() {
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                    get(IFloodlightProviderService.class.getCanonicalName());
+
+        Long switchDPID = null;
+        
+        Form form = getQuery();
+        String dpid = form.getFirstValue("dpid", true);
+        if (dpid != null) {
+            try {
+                switchDPID = HexString.toLong(dpid);
+            } catch (Exception e) {
+                setStatus(Status.CLIENT_ERROR_BAD_REQUEST, DPID_ERROR);
+                return null;
+            }
+        }
+        if (switchDPID != null) {
+            IOFSwitch sw = 
+                    floodlightProvider.getSwitches().get(switchDPID);
+            if (sw != null)
+                return Collections.singleton(sw).iterator();
+            return Collections.<IOFSwitch>emptySet().iterator();
+        }
+        final String dpidStartsWith = 
+                form.getFirstValue("dpid__startswith", true);
+        Iterator<IOFSwitch> switer = 
+                floodlightProvider.getSwitches().values().iterator();
+        if (dpidStartsWith != null) {
+            return new FilterIterator<IOFSwitch>(switer) {
+                @Override
+                protected boolean matches(IOFSwitch value) {
+                    return value.getStringId().startsWith(dpidStartsWith);
+                }
+            };
+        } 
+        return switer;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java
new file mode 100644
index 0000000..45ef6e9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java
@@ -0,0 +1,65 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.core.module.ModuleLoaderResource;
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+/**
+ * Creates a router to handle all the core web URIs
+ * @author readams
+ */
+public class CoreWebRoutable implements RestletRoutable {
+    @Override
+    public String basePath() {
+        return "/wm/core";
+    }
+
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/module/all/json", ModuleLoaderResource.class);
+        router.attach("/module/loaded/json", LoadedModuleLoaderResource.class);
+        router.attach("/switch/{switchId}/role/json", SwitchRoleResource.class);
+        router.attach("/switch/all/{statType}/json", AllSwitchStatisticsResource.class);
+        router.attach("/switch/{switchId}/{statType}/json", SwitchStatisticsResource.class);
+        router.attach("/controller/switches/json", ControllerSwitchesResource.class);
+        router.attach("/counter/{counterTitle}/json", CounterResource.class);
+        router.attach("/counter/{switchId}/{counterName}/json", SwitchCounterResource.class);
+        router.attach("/counter/categories/{switchId}/{counterName}/{layer}/json", SwitchCounterCategoriesResource.class);
+        router.attach("/memory/json", ControllerMemoryResource.class);
+        router.attach("/packettrace/json", PacketTraceResource.class);
+        // Get the last {count} events from the event histories
+        router.attach("/event-history/topology-switch/{count}/json",
+                EventHistoryTopologySwitchResource.class);
+        router.attach("/event-history/topology-link/{count}/json",
+                EventHistoryTopologyLinkResource.class);
+        router.attach("/event-history/topology-cluster/{count}/json",
+                EventHistoryTopologyClusterResource.class);
+        router.attach("/storage/tables/json", StorageSourceTablesResource.class);
+        router.attach("/controller/summary/json", ControllerSummaryResource.class);
+        router.attach("/role/json", ControllerRoleResource.class);
+        router.attach("/health/json", HealthCheckResource.class);
+        router.attach("/system/uptime/json", SystemUptimeResource.class);
+        return router;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResource.java
new file mode 100644
index 0000000..fb680d7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResource.java
@@ -0,0 +1,70 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import net.floodlightcontroller.counter.CounterValue;
+import net.floodlightcontroller.counter.ICounter;
+
+import org.restlet.resource.Get;
+
+public class CounterResource extends CounterResourceBase {
+    @Get("json")
+    public Map<String, Object> retrieve() {
+        String counterTitle = 
+            (String) getRequestAttributes().get("counterTitle");
+        Map<String, Object> model = new HashMap<String,Object>();
+        CounterValue v;
+        if (counterTitle.equalsIgnoreCase("all")) {
+            Map<String, ICounter> counters = this.counterStore.getAll();
+            if (counters != null) {
+                Iterator<Map.Entry<String, ICounter>> it = 
+                    counters.entrySet().iterator();
+                while (it.hasNext()) {
+                    Entry<String, ICounter> entry = it.next();
+                    String counterName = entry.getKey();
+                    v = entry.getValue().getCounterValue();
+
+                    if (CounterValue.CounterType.LONG == v.getType()) {
+                        model.put(counterName, v.getLong());
+                    } else if (v.getType() == CounterValue.CounterType.DOUBLE) {
+                        model.put(counterName, v.getDouble());
+                    }   
+                }   
+            }   
+        } else {
+            ICounter counter = this.counterStore.getCounter(counterTitle);
+            if (counter != null) {
+                v = counter.getCounterValue();
+            } else {
+                v = new CounterValue(CounterValue.CounterType.LONG);
+            }   
+
+            if (CounterValue.CounterType.LONG == v.getType()) {
+                model.put(counterTitle, v.getLong());
+            } else if (v.getType() == CounterValue.CounterType.DOUBLE) {
+                model.put(counterTitle, v.getDouble());
+            }   
+        }
+        return model;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java
new file mode 100644
index 0000000..70e90ed
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.counter.ICounterStoreService;
+
+import org.restlet.resource.ResourceException;
+import org.restlet.resource.ServerResource;
+
+public class CounterResourceBase extends ServerResource {
+    protected ICounterStoreService counterStore;
+    
+    @Override
+    protected void doInit() throws ResourceException {
+        super.doInit();
+        counterStore = 
+            (ICounterStoreService)getContext().getAttributes().
+                get(ICounterStoreService.class.getCanonicalName());
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyClusterResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyClusterResource.java
new file mode 100644
index 0000000..1be942c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyClusterResource.java
@@ -0,0 +1,45 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.internal.EventHistoryTopologyCluster;
+import net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager;
+import net.floodlightcontroller.util.EventHistory;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyClusterResource extends ServerResource {
+    // TODO - Move this to the LinkDiscovery rest API
+    protected static Logger log = 
+            LoggerFactory.getLogger(EventHistoryTopologyClusterResource.class);
+
+    @Get("json")
+    public EventHistory<EventHistoryTopologyCluster> handleEvHistReq() {
+
+        // Get the event history count. Last <count> events would be returned
+        String evHistCount = (String)getRequestAttributes().get("count");
+        int    count = EventHistory.EV_HISTORY_DEFAULT_SIZE;
+        try {
+            count = Integer.parseInt(evHistCount);
+        }
+        catch(NumberFormatException nFE) {
+            // Invalid input for event count - use default value
+        }
+
+        LinkDiscoveryManager topoManager =
+                (LinkDiscoveryManager)getContext().getAttributes().
+                get(ILinkDiscoveryService.class.getCanonicalName());
+        if (topoManager != null) {
+            return new EventHistory<EventHistoryTopologyCluster>(
+                    topoManager.evHistTopologyCluster, count);
+        }
+        
+        return null;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyLinkResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyLinkResource.java
new file mode 100644
index 0000000..4a21070
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyLinkResource.java
@@ -0,0 +1,45 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.internal.EventHistoryTopologyLink;
+import net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager;
+import net.floodlightcontroller.util.EventHistory;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyLinkResource extends ServerResource {
+    // TODO - Move this to the DeviceManager Rest API
+    protected static Logger log = 
+            LoggerFactory.getLogger(EventHistoryTopologyLinkResource.class);
+
+    @Get("json")
+    public EventHistory<EventHistoryTopologyLink> handleEvHistReq() {
+
+        // Get the event history count. Last <count> events would be returned
+        String evHistCount = (String)getRequestAttributes().get("count");
+        int    count = EventHistory.EV_HISTORY_DEFAULT_SIZE;
+        try {
+            count = Integer.parseInt(evHistCount);
+        }
+        catch(NumberFormatException nFE) {
+            // Invalid input for event count - use default value
+        }
+
+        LinkDiscoveryManager linkDiscoveryManager =
+                (LinkDiscoveryManager)getContext().getAttributes().
+                get(ILinkDiscoveryService.class.getCanonicalName());
+        if (linkDiscoveryManager != null) {
+            return new EventHistory<EventHistoryTopologyLink>(
+                    linkDiscoveryManager.evHistTopologyLink, count);
+        }
+        
+        return null;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologySwitchResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologySwitchResource.java
new file mode 100644
index 0000000..1c95e2c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologySwitchResource.java
@@ -0,0 +1,37 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.internal.EventHistoryTopologySwitch;
+import net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager;
+import net.floodlightcontroller.util.EventHistory;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+/**
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologySwitchResource extends ServerResource {
+
+    @Get("json")
+    public EventHistory<EventHistoryTopologySwitch> handleEvHistReq() {
+
+        // Get the event history count. Last <count> events would be returned
+        String evHistCount = (String)getRequestAttributes().get("count");
+        int    count = EventHistory.EV_HISTORY_DEFAULT_SIZE;
+        try {
+            count = Integer.parseInt(evHistCount);
+        }
+        catch(NumberFormatException nFE) {
+            // Invalid input for event count - use default value
+        }
+
+        LinkDiscoveryManager topoManager =
+           (LinkDiscoveryManager)getContext().getAttributes().
+               get(ILinkDiscoveryService.class.getCanonicalName());
+
+        return new EventHistory<EventHistoryTopologySwitch>(
+                                topoManager.evHistTopologySwitch, count);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/HealthCheckResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/HealthCheckResource.java
new file mode 100644
index 0000000..12ee545
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/HealthCheckResource.java
@@ -0,0 +1,36 @@
+package net.floodlightcontroller.core.web;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class HealthCheckResource extends ServerResource {
+    
+    public static class HealthCheckInfo {
+        
+        protected boolean healthy;
+        
+        public HealthCheckInfo() {
+            this.healthy = true;
+        }
+        
+        public boolean isHealthy() {
+            return healthy;
+        }
+        
+        public void setHealthy(boolean healthy) {
+            this.healthy = healthy;
+        }
+    }
+    
+    @Get("json")
+    public HealthCheckInfo healthCheck() {
+        // Currently this is the simplest possible health check -- basically
+        // just that the controller is still running and able to respond to
+        // REST calls.
+        // Eventually this should be more sophisticated and do things
+        // like monitoring internal data structures of the controller
+        // (e.g. async storage queue length).
+        return new HealthCheckInfo();
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java
new file mode 100644
index 0000000..38367c3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java
@@ -0,0 +1,19 @@
+package net.floodlightcontroller.core.web;
+
+import java.util.Map;
+
+import org.restlet.resource.Get;
+
+import net.floodlightcontroller.core.module.ModuleLoaderResource;
+
+public class LoadedModuleLoaderResource extends ModuleLoaderResource {
+	/**
+	 * Retrieves information about all modules available
+	 * to Floodlight.
+	 * @return Information about all modules available.
+	 */
+    @Get("json")
+    public Map<String, Object> retrieve() {
+    	return retrieveInternal(true);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/PacketTraceResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/PacketTraceResource.java
new file mode 100644
index 0000000..85da942
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/PacketTraceResource.java
@@ -0,0 +1,118 @@
+package net.floodlightcontroller.core.web;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.OFMessageFilterManager;
+
+public class PacketTraceResource extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(PacketTraceResource.class);
+    
+    public static class FilterParameters {
+
+        protected String sessionId = null;
+        protected String mac = null;
+        protected Integer period = null;
+        protected String direction = null;
+        protected String output = null;
+        
+        public String getSessionId() {
+            return sessionId;
+        }
+        public void setSessionId(String sessionId) {
+            this.sessionId = sessionId;
+        }
+        public String getMac() {
+            return mac;
+        }
+        public void setMac(String mac) {
+            this.mac = mac;
+        }
+        public Integer getPeriod() {
+            return period;
+        }
+        public void setPeriod(Integer period) {
+            this.period = period;
+        }
+        public String getDirection() {
+            return direction;
+        }
+        public void setDirection(String direction) {
+            this.direction = direction;
+        }
+        public String getOutput() {
+            return output;
+        }
+        public void setOutput(String output) {
+            this.output = output;
+        }
+
+        public String toString() {
+            return "SessionID: " + sessionId +
+                   "\tmac" + mac +
+                   "\tperiod" + period +
+                   "\tdirection" + direction +
+                   "\toutput" + output;
+        }
+    }
+    
+    public static class PacketTraceOutput {
+        protected String sessionId = null;
+
+        public String getSessionId() {
+            return sessionId;
+        }
+
+        public void setSessionId(String sessionId) {
+            this.sessionId = sessionId;
+        }
+    }
+    
+    @Post("json")
+    public PacketTraceOutput packettrace(FilterParameters fp) {
+        
+        ConcurrentHashMap <String,String> filter = new ConcurrentHashMap<String,String> ();
+        String sid = null;
+        PacketTraceOutput output = new PacketTraceOutput();
+        OFMessageFilterManager manager = 
+                (OFMessageFilterManager)getContext()
+                    .getAttributes().
+                        get(OFMessageFilterManager.class.getCanonicalName());
+
+        if (manager == null) {
+            sid = null;
+            setStatus(Status.SERVER_ERROR_SERVICE_UNAVAILABLE);
+        }
+        
+        if (fp.getSessionId() != null) {
+            filter.put("sessionId", fp.getSessionId());
+        }
+        if (fp.getMac() != null) {
+            filter.put("mac", fp.getMac());
+        }
+        if (fp.getDirection() != null) {
+            filter.put("direction", fp.getDirection());
+        }
+        
+        if (filter.isEmpty()) {
+            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug ("Call setupFilter: sid:{} filter:{}, period:{}", 
+                           new Object[] {fp.getSessionId(), filter, 
+                                         fp.getPeriod()*1000});
+            }
+            sid = manager.setupFilter(fp.getSessionId(), filter, 
+                                      fp.getPeriod()*1000);
+            output.setSessionId(sid);
+            setStatus(Status.SUCCESS_OK);
+        }
+        
+        return output;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/RoleInfo.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/RoleInfo.java
new file mode 100644
index 0000000..e600ea0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/RoleInfo.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+
+public class RoleInfo {
+    protected String role;
+    
+    public RoleInfo() {
+    }
+    
+    public RoleInfo(String role) {
+        setRole(role);
+    }
+    
+    public RoleInfo(Role role) {
+        this.role = (role != null) ? role.name() : "DISABLED";
+    }
+    
+    public String getRole() {
+        return role;
+    }
+    
+    public void setRole(String role) {
+        this.role = role;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/StorageSourceTablesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/StorageSourceTablesResource.java
new file mode 100644
index 0000000..51f514f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/StorageSourceTablesResource.java
@@ -0,0 +1,18 @@
+package net.floodlightcontroller.core.web;
+
+import java.util.Set;
+
+import net.floodlightcontroller.storage.IStorageSourceService;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class StorageSourceTablesResource extends ServerResource {
+    @Get("json")
+    public Set<String> retrieve() {
+        IStorageSourceService storageSource = (IStorageSourceService)getContext().
+                getAttributes().get(IStorageSourceService.class.getCanonicalName());
+        Set<String> allTableNames = storageSource.getAllTableNames();
+        return allTableNames;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java
new file mode 100644
index 0000000..f14d706
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java
@@ -0,0 +1,87 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.openflow.util.HexString;
+import org.restlet.resource.Get;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.counter.CounterStore.NetworkLayer;
+import net.floodlightcontroller.counter.ICounterStoreService;
+
+/**
+ * Get the counter categories for a particular switch
+ * @author readams
+ */
+public class SwitchCounterCategoriesResource extends CounterResourceBase {
+    @Get("json")
+    public Map<String, Object> retrieve() {
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                    get(IFloodlightProviderService.class.getCanonicalName());
+        HashMap<String,Object> model = new HashMap<String,Object>();
+        
+        String switchID = (String) getRequestAttributes().get("switchId");
+        String counterName = (String) getRequestAttributes().get("counterName");
+        String layer = (String) getRequestAttributes().get("layer");
+
+        Long[] switchDpids;
+        if (switchID.equalsIgnoreCase("all")) {
+            switchDpids = floodlightProvider.getSwitches().keySet().toArray(new Long[0]);
+            for (Long dpid : switchDpids) {
+                switchID = HexString.toHexString(dpid);
+
+                getOneSwitchCounterCategoriesJson(model, switchID, counterName, layer);
+            }
+        } else {
+            getOneSwitchCounterCategoriesJson(model, switchID, counterName, layer);
+        }
+        
+        return model;
+    }
+    
+    protected void getOneSwitchCounterCategoriesJson(Map<String, Object> model,
+                                                     String switchID,
+                                                     String counterName, 
+                                                     String layer) {
+        String fullCounterName = "";      
+        NetworkLayer nl = NetworkLayer.L3;
+        
+        try {
+            counterName = URLDecoder.decode(counterName, "UTF-8");
+            layer = URLDecoder.decode(layer, "UTF-8");
+            fullCounterName = switchID + ICounterStoreService.TitleDelimitor + counterName;
+        } catch (UnsupportedEncodingException e) {
+            //Just leave counterTitle undecoded if there is an issue - fail silently
+        }
+
+        if (layer.compareToIgnoreCase("4") == 0) {
+            nl = NetworkLayer.L4;
+        }
+        List<String> categories = this.counterStore.getAllCategories(fullCounterName, nl);
+        if (categories != null) {
+            model.put(fullCounterName + "." + layer, categories);
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java
new file mode 100644
index 0000000..188836d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java
@@ -0,0 +1,83 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openflow.util.HexString;
+import org.restlet.resource.Get;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.counter.ICounter;
+import net.floodlightcontroller.counter.ICounterStoreService;
+
+/**
+ * Get counters for a particular switch 
+ * @author readams
+ */
+public class SwitchCounterResource extends CounterResourceBase {
+    @Get("json")
+    public Map<String, Object> retrieve() {
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                    get(IFloodlightProviderService.class.getCanonicalName());
+        HashMap<String,Object> model = new HashMap<String,Object>();
+        
+        String switchID = (String) getRequestAttributes().get("switchId");
+        String counterName = (String) getRequestAttributes().get("counterName");
+
+        Long[] switchDpids;
+        if (switchID.equalsIgnoreCase("all")) {
+            switchDpids = floodlightProvider.getSwitches().keySet().toArray(new Long[0]);
+            getOneSwitchCounterJson(model, ICounterStoreService.CONTROLLER_NAME, counterName);
+            for (Long dpid : switchDpids) {
+                switchID = HexString.toHexString(dpid);
+
+                getOneSwitchCounterJson(model, switchID, counterName);
+            }
+        } else {
+            getOneSwitchCounterJson(model, switchID, counterName);
+        }
+        return model;
+    }
+    
+    protected void getOneSwitchCounterJson(Map<String, Object> model, 
+                                           String switchID, String counterName) {
+        String fullCounterName = "";      
+        
+        try {
+            counterName = URLDecoder.decode(counterName, "UTF-8");
+            fullCounterName = 
+                switchID + ICounterStoreService.TitleDelimitor + counterName;
+        } catch (UnsupportedEncodingException e) {
+            //Just leave counterTitle undecoded if there is an issue - fail silently
+        }
+
+        ICounter counter = this.counterStore.getCounter(fullCounterName);
+        Map<String, Long> sample = new HashMap<String, Long> ();
+        if (counter != null) {
+            sample.put(counter.getCounterDate().toString(), 
+                       counter.getCounterValue().getLong());
+            model.put(switchID, sample);
+        }
+    }
+    
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java
new file mode 100644
index 0000000..d810024
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java
@@ -0,0 +1,157 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.statistics.OFAggregateStatisticsRequest;
+import org.openflow.protocol.statistics.OFFlowStatisticsRequest;
+import org.openflow.protocol.statistics.OFPortStatisticsRequest;
+import org.openflow.protocol.statistics.OFQueueStatisticsRequest;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.util.HexString;
+import org.restlet.resource.ResourceException;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base class for server resources related to switches
+ * @author readams
+ *
+ */
+public class SwitchResourceBase extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(SwitchResourceBase.class);
+    
+    public enum REQUESTTYPE {
+        OFSTATS,
+        OFFEATURES
+    }
+    
+    @Override
+    protected void doInit() throws ResourceException {
+        super.doInit();
+        
+    }
+    
+    @LogMessageDoc(level="ERROR",
+                   message="Failure retrieving statistics from switch {switch}",
+                   explanation="An error occurred while retrieving statistics" +
+                   		"from the switch",
+                   recommendation=LogMessageDoc.CHECK_SWITCH + " " +
+                   		LogMessageDoc.GENERIC_ACTION)
+    protected List<OFStatistics> getSwitchStatistics(long switchId, 
+                                                     OFStatisticsType statType) {
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                    get(IFloodlightProviderService.class.getCanonicalName());
+        
+        IOFSwitch sw = floodlightProvider.getSwitches().get(switchId);
+        Future<List<OFStatistics>> future;
+        List<OFStatistics> values = null;
+        if (sw != null) {
+            OFStatisticsRequest req = new OFStatisticsRequest();
+            req.setStatisticType(statType);
+            int requestLength = req.getLengthU();
+            if (statType == OFStatisticsType.FLOW) {
+                OFFlowStatisticsRequest specificReq = new OFFlowStatisticsRequest();
+                OFMatch match = new OFMatch();
+                match.setWildcards(0xffffffff);
+                specificReq.setMatch(match);
+                specificReq.setOutPort(OFPort.OFPP_NONE.getValue());
+                specificReq.setTableId((byte) 0xff);
+                req.setStatistics(Collections.singletonList((OFStatistics)specificReq));
+                requestLength += specificReq.getLength();
+            } else if (statType == OFStatisticsType.AGGREGATE) {
+                OFAggregateStatisticsRequest specificReq = new OFAggregateStatisticsRequest();
+                OFMatch match = new OFMatch();
+                match.setWildcards(0xffffffff);
+                specificReq.setMatch(match);
+                specificReq.setOutPort(OFPort.OFPP_NONE.getValue());
+                specificReq.setTableId((byte) 0xff);
+                req.setStatistics(Collections.singletonList((OFStatistics)specificReq));
+                requestLength += specificReq.getLength();
+            } else if (statType == OFStatisticsType.PORT) {
+                OFPortStatisticsRequest specificReq = new OFPortStatisticsRequest();
+                specificReq.setPortNumber((short)OFPort.OFPP_NONE.getValue());
+                req.setStatistics(Collections.singletonList((OFStatistics)specificReq));
+                requestLength += specificReq.getLength();
+            } else if (statType == OFStatisticsType.QUEUE) {
+                OFQueueStatisticsRequest specificReq = new OFQueueStatisticsRequest();
+                specificReq.setPortNumber((short)OFPort.OFPP_ALL.getValue());
+                // LOOK! openflowj does not define OFPQ_ALL! pulled this from openflow.h
+                // note that I haven't seen this work yet though...
+                specificReq.setQueueId(0xffffffff);
+                req.setStatistics(Collections.singletonList((OFStatistics)specificReq));
+                requestLength += specificReq.getLength();
+            } else if (statType == OFStatisticsType.DESC ||
+                       statType == OFStatisticsType.TABLE) {
+                // pass - nothing todo besides set the type above
+            }
+            req.setLengthU(requestLength);
+            try {
+                future = sw.getStatistics(req);
+                values = future.get(10, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                log.error("Failure retrieving statistics from switch " + sw, e);
+            }
+        }
+        return values;
+    }
+
+    protected List<OFStatistics> getSwitchStatistics(String switchId, OFStatisticsType statType) {
+        return getSwitchStatistics(HexString.toLong(switchId), statType);
+    }
+    
+    protected OFFeaturesReply getSwitchFeaturesReply(long switchId) {
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                get(IFloodlightProviderService.class.getCanonicalName());
+
+        IOFSwitch sw = floodlightProvider.getSwitches().get(switchId);
+        Future<OFFeaturesReply> future;
+        OFFeaturesReply featuresReply = null;
+        if (sw != null) {
+            try {
+                future = sw.getFeaturesReplyFromSwitch();
+                featuresReply = future.get(10, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                log.error("Failure getting features reply from switch" + sw, e);
+            }
+        }
+
+        return featuresReply;
+    }
+
+    protected OFFeaturesReply getSwitchFeaturesReply(String switchId) {
+        return getSwitchFeaturesReply(HexString.toLong(switchId));
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java
new file mode 100644
index 0000000..0d73f93
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java
@@ -0,0 +1,46 @@
+package net.floodlightcontroller.core.web;
+
+import java.util.HashMap;
+
+import org.openflow.util.HexString;
+import org.restlet.resource.ServerResource;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+
+import org.restlet.resource.Get;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SwitchRoleResource extends ServerResource {
+
+    protected static Logger log = LoggerFactory.getLogger(SwitchRoleResource.class);
+
+    @Get("json")
+    public Object getRole() {
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                    get(IFloodlightProviderService.class.getCanonicalName());
+
+        String switchId = (String) getRequestAttributes().get("switchId");
+        
+        RoleInfo roleInfo;
+        
+        if (switchId.equalsIgnoreCase("all")) {
+            HashMap<String,RoleInfo> model = new HashMap<String,RoleInfo>();
+            for (IOFSwitch sw: floodlightProvider.getSwitches().values()) {
+            	switchId = sw.getStringId();
+            	roleInfo = new RoleInfo(sw.getRole());
+            	model.put(switchId, roleInfo);
+            }
+            return model;
+        }
+        
+    	Long dpid = HexString.toLong(switchId);
+    	IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+    	if (sw == null)
+    		return null;
+    	roleInfo = new RoleInfo(sw.getRole());
+    	return roleInfo;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java
new file mode 100644
index 0000000..57771f7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java
@@ -0,0 +1,63 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.restlet.resource.Get;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Return switch statistics information for specific switches
+ * @author readams
+ */
+public class SwitchStatisticsResource extends SwitchResourceBase {
+    protected static Logger log = 
+        LoggerFactory.getLogger(SwitchStatisticsResource.class);
+
+    @Get("json")
+    public Map<String, Object> retrieve() {
+        HashMap<String,Object> result = new HashMap<String,Object>();
+        Object values = null;
+        
+        String switchId = (String) getRequestAttributes().get("switchId");
+        String statType = (String) getRequestAttributes().get("statType");
+        
+        if (statType.equals("port")) {
+            values = getSwitchStatistics(switchId, OFStatisticsType.PORT);
+        } else if (statType.equals("queue")) {
+            values = getSwitchStatistics(switchId, OFStatisticsType.QUEUE);
+        } else if (statType.equals("flow")) {
+            values = getSwitchStatistics(switchId, OFStatisticsType.FLOW);
+        } else if (statType.equals("aggregate")) {
+            values = getSwitchStatistics(switchId, OFStatisticsType.AGGREGATE);
+        } else if (statType.equals("desc")) {
+            values = getSwitchStatistics(switchId, OFStatisticsType.DESC);
+        } else if (statType.equals("table")) {
+            values = getSwitchStatistics(switchId, OFStatisticsType.TABLE);
+        } else if (statType.equals("features")) {
+            values = getSwitchFeaturesReply(switchId);
+        }
+
+        result.put(switchId, values);
+        return result;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SystemUptimeResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SystemUptimeResource.java
new file mode 100644
index 0000000..fe4b967
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SystemUptimeResource.java
@@ -0,0 +1,31 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+
+
+public class SystemUptimeResource extends ServerResource {
+	
+	public class UptimeRest {
+		long systemUptimeMsec;
+
+		public long getSystemUptimeMsec() {
+			return systemUptimeMsec;
+		}
+	}
+	
+	@Get("json")
+	public UptimeRest retrieve() {
+		IFloodlightProviderService floodlightProvider = 
+			(IFloodlightProviderService)getContext().getAttributes().
+			get(IFloodlightProviderService.class.getCanonicalName());
+		
+		UptimeRest uptime = new UptimeRest();
+		uptime.systemUptimeMsec = 
+		   System.currentTimeMillis() - floodlightProvider.getSystemStartTime();
+		
+		return (uptime);
+	}
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java
new file mode 100644
index 0000000..66c33f5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java
@@ -0,0 +1,40 @@
+/**
+*    Copyright 2011,2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.util.HexString;
+
+/**
+ * Serialize a MAC as colon-separated hexadecimal
+ */
+public class ByteArrayMACSerializer extends JsonSerializer<byte[]> {
+
+    @Override
+    public void serialize(byte[] mac, JsonGenerator jGen,
+                          SerializerProvider serializer)
+                                  throws IOException, JsonProcessingException {
+        jGen.writeString(HexString.toHexString(mac));
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java
new file mode 100644
index 0000000..e74cc01
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java
@@ -0,0 +1,40 @@
+/**
+*    Copyright 2011,2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.util.HexString;
+
+/**
+ * Serialize a DPID as colon-separated hexadecimal
+ */
+public class DPIDSerializer extends JsonSerializer<Long> {
+
+    @Override
+    public void serialize(Long dpid, JsonGenerator jGen,
+                          SerializerProvider serializer)
+                                  throws IOException, JsonProcessingException {
+        jGen.writeString(HexString.toHexString(dpid, 8));
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java
new file mode 100644
index 0000000..f4a5877
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java
@@ -0,0 +1,41 @@
+/**
+*    Copyright 2011,2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web.serializers;
+
+import java.io.IOException;
+
+import net.floodlightcontroller.packet.IPv4;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+/**
+ * Serialize an integer as an IPv4 Address in dotted decimal format
+ */
+public class IPv4Serializer extends JsonSerializer<Integer> {
+
+    @Override
+    public void serialize(Integer i, JsonGenerator jGen,
+                          SerializerProvider serializer)
+                                  throws IOException, JsonProcessingException {
+        jGen.writeString(IPv4.fromIPv4Address(i));
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java
new file mode 100644
index 0000000..a7c9fb7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java
@@ -0,0 +1,40 @@
+/**
+*    Copyright 2011,2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.util.HexString;
+
+/**
+ * Serialize a MAC as colon-separated hexadecimal
+ */
+public class MACSerializer extends JsonSerializer<Long> {
+
+    @Override
+    public void serialize(Long dpid, JsonGenerator jGen,
+                          SerializerProvider serializer)
+                                  throws IOException, JsonProcessingException {
+        jGen.writeString(HexString.toHexString(dpid, 6));
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java
new file mode 100644
index 0000000..c125c76
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java
@@ -0,0 +1,40 @@
+/**
+*    Copyright 2011,2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.web.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+/**
+ * Serialize a short value as an unsigned short
+ */
+public class UShortSerializer extends JsonSerializer<Short> {
+
+    @Override
+    public void serialize(Short s, JsonGenerator jGen,
+                          SerializerProvider serializer) throws IOException,
+                                                  JsonProcessingException {
+        if (s == null) jGen.writeNull();
+        else jGen.writeNumber(s.shortValue() & 0xffff);
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ConcurrentCounter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ConcurrentCounter.java
new file mode 100644
index 0000000..cdec1e0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ConcurrentCounter.java
@@ -0,0 +1,205 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.counter;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.counter.CounterValue.CounterType;
+
+
+/**
+ * This module needs to be updated with CounterValue.
+ * 
+ * This is a crumby attempt at a highly concurrent implementation of the Counter interface.
+ * 
+ * (Help! Help!  Someone please re-write me!  This will almost certainly break at high loads.)
+ * 
+ * The gist is that this class, ConcurrentCounter, keeps an internal highly transient buffer that is occasionally flushed
+ * in to a set of CountBuffers (circular buffers) which store a longer term historical view of the count values at different
+ * moments in time.
+ * 
+ * This Counter implementation may be a bit over-engineered...  The goal here was to present an implementation that is very
+ * predictable with respect to memory and CPU time and, at the same time, present a very fast increment() method.  The reasoning
+ * here is that this will be a go-to class when it comes to debugging, particularly in high-load situations where logging
+ * may introduce so much variability to the system that it foils the results.
+ * 
+ * @author kyle
+ *
+ */
+public class ConcurrentCounter implements ICounter {
+
+  protected static final Map<DateSpan, Integer> MAX_HISTORY = new HashMap<DateSpan, Integer>();
+  static {
+    MAX_HISTORY.put(DateSpan.REALTIME, new Integer(1));
+    MAX_HISTORY.put(DateSpan.SECONDS, new Integer(120));
+    MAX_HISTORY.put(DateSpan.MINUTES, new Integer(60));
+    MAX_HISTORY.put(DateSpan.HOURS, new Integer(48));
+    MAX_HISTORY.put(DateSpan.DAYS, new Integer(60));
+    MAX_HISTORY.put(DateSpan.WEEKS, new Integer(2)); 
+  }
+  
+  protected static Set<ConcurrentCounter> liveCounters;
+  
+  static {
+    liveCounters = Collections.newSetFromMap(new ConcurrentHashMap<ConcurrentCounter, Boolean>()); //nifty way to get concurrent hash set
+    //Set a background thread to flush any liveCounters every 100 milliseconds
+    Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
+        public void run() {
+            for(ConcurrentCounter c : liveCounters) {
+                c.flush();
+            }
+        }}, 100, 100, TimeUnit.MILLISECONDS);
+  }
+
+  /**
+   * Very simple data structure to store off a single count entry at a single point in time
+   * @author kyle
+   *
+   */
+  protected static final class CountAtom {
+    protected Date date;
+    protected Long delta;
+    
+    protected CountAtom(Date date, Long delta) {
+      this.date = date;
+      this.delta = delta;
+    }
+    
+    public String toString() {
+      return "[" + this.date + ": " + this.delta + "]";
+    }
+  }
+
+  
+  protected Queue<CountAtom> unprocessedCountBuffer;
+  protected Map<DateSpan, CountBuffer> counts;
+  protected Date startDate;
+  
+  /**
+   * Factory method to create a new counter instance.  (Design note - 
+   * use a factory pattern here as it may be necessary to hook in other
+   * registrations around counter objects as they are created.)
+   * 
+   * @param startDate
+   * @return
+   */
+  public static ICounter createCounter(Date startDate) {
+    ConcurrentCounter cc = new ConcurrentCounter(startDate);
+    ConcurrentCounter.liveCounters.add(cc);
+    return cc;
+    
+  }
+  
+  /**
+   * Protected constructor - use createCounter factory method instead
+   * @param startDate
+   */
+  protected ConcurrentCounter(Date startDate) {
+    init(startDate);
+  }
+  
+  protected void init(Date startDate) {
+    this.startDate = startDate;
+    this.unprocessedCountBuffer = new ConcurrentLinkedQueue<CountAtom>();
+    this.counts = new HashMap<DateSpan, CountBuffer>();
+      
+    for(DateSpan ds : DateSpan.values()) {
+      CountBuffer cb = new CountBuffer(startDate, ds, MAX_HISTORY.get(ds));
+      counts.put(ds, cb);
+    }
+  }
+  /**
+   * This is the key method that has to be both fast and very thread-safe.
+   */
+  @Override
+  public void increment() {
+    this.increment(new Date(), (long)1);
+  }
+  
+  @Override
+  public void increment(Date d, long delta) {
+    this.unprocessedCountBuffer.add(new CountAtom(d, delta));
+  }
+  
+  @Override
+  public void setCounter(Date d, CounterValue value) {
+      // To be done later
+  }
+  
+  /**
+   * Reset the value.
+   */
+  @Override
+  public void reset(Date startDate) {
+    init(startDate);
+  }
+  
+  /**
+   * Flushes values out of the internal buffer and in to structures
+   * that can be fetched with a call to snapshot()
+   */
+  public synchronized void flush() {
+    for(CountAtom c = this.unprocessedCountBuffer.poll(); c != null; c = this.unprocessedCountBuffer.poll()) {
+      for(DateSpan ds : DateSpan.values()) {
+        CountBuffer cb = counts.get(ds);
+        cb.increment(c.date, c.delta);
+      }
+    }
+  }
+  
+  @Override
+  public CounterValue getCounterValue() {
+      // To be done later
+      //CountSeries cs = counts.get(DateSpan.REALTIME).snapshot();
+      //return cs.getSeries()[0];
+      return new CounterValue(CounterType.LONG);
+  }
+  
+  @Override
+  public Date getCounterDate() {
+      // To be done later
+      //CountSeries cs = counts.get(DateSpan.REALTIME).snapshot();
+      //return cs.getSeries()[0];
+      return new Date();
+  }
+  
+  @Override
+  /**
+   * This method returns a disconnected copy of the underlying CountSeries corresponding to dateSpan.
+   */
+  public CountSeries snapshot(DateSpan dateSpan) {
+    flush();
+    CountSeries cs = counts.get(dateSpan).snapshot();
+    return cs;
+  }
+
+  
+  
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountBuffer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountBuffer.java
new file mode 100644
index 0000000..fa45862
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountBuffer.java
@@ -0,0 +1,125 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.counter;
+
+import java.util.Date;
+
+import net.floodlightcontroller.counter.ICounter.DateSpan;
+
+
+/**
+ * Implements a circular buffer to store the last x time-based counter values.  This is pretty crumby
+ * implementation, basically wrapping everything with synchronized blocks, in order to ensure that threads
+ * which will be updating the series don't result in a thread which is reading the series getting stuck with
+ * a start date which does not correspond to the count values in getSeries.
+ * 
+ * This could probably use a re-think...
+ * 
+ * @author kyle
+ *
+ */
+public class CountBuffer {
+  protected long[] counterValues;
+  protected Date startDate;
+  protected DateSpan dateSpan;
+  protected int currentIndex;
+  protected int seriesLength;
+
+
+  public CountBuffer(Date startDate, DateSpan dateSpan, int seriesLength) {
+    this.seriesLength = seriesLength;
+    this.counterValues = new long[seriesLength];
+    this.dateSpan = dateSpan;
+    
+    this.startDate = startDate;
+    this.currentIndex = 0;
+  }
+  
+  /**
+   * Increment the count associated with Date d, forgetting some of the older count values if necessary to ensure
+   * that the total span of time covered by this series corresponds to DateSpan * seriesLength (circular buffer).
+   * 
+   * Note - fails silently if the Date falls prior to the start of the tracked count values.
+   * 
+   * Note - this should be a reasonably fast method, though it will have to block if there is another thread reading the
+   * series at the same time.
+   * 
+   * @param d
+   * @param delta
+   */
+  public synchronized void increment(Date d, long delta) {
+
+    long dsMillis = CountSeries.dateSpanToMilliseconds(this.dateSpan);
+    Date endDate = new Date(startDate.getTime() + seriesLength * dsMillis - 1);
+
+    if(d.getTime() < startDate.getTime()) {
+      return; //silently fail rather than insert a count at a time older than the history buffer we're keeping
+    }
+    else if (d.getTime() >= startDate.getTime() && d.getTime() <= endDate.getTime()) {
+        int index = (int)  (( d.getTime() - startDate.getTime() ) / dsMillis); // java rounds down on long/long
+        int modIndex = (index + currentIndex) % seriesLength;
+        long currentValue = counterValues[modIndex];
+        counterValues[modIndex] = currentValue + delta;
+    }
+    else if (d.getTime() > endDate.getTime()) {
+      //Initialize new buckets
+      int newBuckets = (int)((d.getTime() - endDate.getTime()) / dsMillis) + 1; // java rounds down on long/long
+      for(int i = 0; i < newBuckets; i++) {
+        int modIndex = (i + currentIndex) % seriesLength;
+        counterValues[modIndex] = 0;
+      }
+      //Update internal vars
+      this.startDate = new Date(startDate.getTime() + dsMillis * newBuckets);
+      this.currentIndex = (currentIndex + newBuckets) % this.seriesLength;    
+
+      //Call again (date should be in the range this time)
+      this.increment(d, delta);
+    }
+  }
+  
+  /**
+   * Relatively slow method, expected to be called primarily from UI rather than from in-packet-path.
+   * 
+   * @return the count values associated with each time interval starting with startDate and demarc'ed by dateSpan
+   */
+  public long[] getSeries() { //synchronized here should lock on 'this', implying that it shares the lock with increment
+    long[] ret = new long[this.seriesLength];
+    for(int i = 0; i < this.seriesLength; i++) {
+      int modIndex = (currentIndex + i) % this.seriesLength;
+      ret[i] = this.counterValues[modIndex];
+    }
+    return ret;
+  }
+
+  
+  /**
+   * Returns an immutable count series that represents a snapshot of this
+   * series at a specific moment in time.
+   * @return
+   */
+  public synchronized CountSeries snapshot() {
+    long[] cvs = new long[this.seriesLength];
+    for(int i = 0; i < this.seriesLength; i++) {
+      int modIndex = (this.currentIndex + i) % this.seriesLength;
+      cvs[i] = this.counterValues[modIndex];
+    }
+
+    return new CountSeries(this.startDate, this.dateSpan, cvs);
+  }
+  
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountSeries.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountSeries.java
new file mode 100644
index 0000000..e8a547a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountSeries.java
@@ -0,0 +1,88 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.counter;
+
+import java.util.Arrays;
+import java.util.Date;
+
+import net.floodlightcontroller.counter.ICounter.DateSpan;
+
+/**
+ * Simple immutable class to store a series of historic counter values
+ * 
+ * This could probably use a re-think...
+ * 
+ * @author kyle
+ *
+ */
+public class CountSeries {  
+  protected long[] counterValues;
+  protected Date startDate;
+  protected DateSpan dateSpan;
+  
+  public CountSeries(Date startDate, DateSpan dateSpan, long[] counterValues) {
+    this.counterValues = counterValues.clone();
+    this.dateSpan = dateSpan;    
+    this.startDate = startDate;
+  }
+  
+
+  public long[] getSeries() { //synchronized here should lock on 'this', implying that it shares the lock with increment
+    return this.counterValues.clone();
+  }
+  
+  /**
+   * Returns the startDate of this series.  The first long in getSeries represents the sum of deltas from increment calls with dates
+   * that correspond to >= startDate and < startDate + DateSpan.
+   * @return
+   */
+  public Date getStartDate() {//synchronized here should lock on 'this', implying that it shares the lock with increment
+    return this.startDate;
+  }
+  
+  public String toString() {
+    String ret = "{start: " + this.startDate + ", span: " + this.dateSpan + ", series: " + Arrays.toString(getSeries()) + "}";
+    return ret;
+  }
+  
+  /**
+   * Return a long that is the number of milliseconds in a ds (second/minute/hour/day/week).  (Utility method.)
+   * 
+   * @param ds
+   * @return
+   */
+  public static final long dateSpanToMilliseconds(DateSpan ds) {
+    long delta = 1;
+    switch(ds) {
+	    case WEEKS:
+	    	delta *= 7;
+	    case DAYS:
+	    	delta *= 24;
+	    case HOURS:
+	    	delta *= 60;
+	    case MINUTES:
+	    	delta *= 60;
+	    case SECONDS:
+	    	delta *= 1000;
+	    default:
+	    	break;
+    }
+    return delta;
+  }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterStore.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterStore.java
new file mode 100644
index 0000000..26d1302
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterStore.java
@@ -0,0 +1,461 @@
+/**
+ *    Copyright 2011, Big Switch Networks, Inc. 
+ *    Originally created by David Erickson, Stanford University
+ * 
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+/**
+ * Implements a very simple central store for system counters
+ */
+package net.floodlightcontroller.counter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.PostConstruct;
+
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.counter.CounterValue.CounterType;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author kyle
+ *
+ */
+public class CounterStore implements IFloodlightModule, ICounterStoreService {
+    protected static Logger log = LoggerFactory.getLogger(CounterStore.class);
+
+    public enum NetworkLayer {
+        L2, L3, L4
+    }
+
+    protected class CounterEntry {
+        protected ICounter counter;
+        String title;
+    }
+
+    /**
+     * A map of counterName --> Counter
+     */
+    protected ConcurrentHashMap<String, CounterEntry> nameToCEIndex = 
+            new ConcurrentHashMap<String, CounterEntry>();
+
+    protected ICounter heartbeatCounter;
+    protected ICounter randomCounter;
+
+    /**
+     * Counter Categories grouped by network layers
+     * NetworkLayer -> CounterToCategories
+     */
+    protected static Map<NetworkLayer, Map<String, List<String>>> layeredCategories = 
+            new ConcurrentHashMap<NetworkLayer, Map<String, List<String>>> ();
+
+    public void updatePacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth) {
+        OFPacketIn packet = (OFPacketIn)m;
+        
+        // Make sure there is data
+        if (packet.getPacketData().length <= 0) return;
+        
+        /* Extract the etherType and protocol field for IPv4 packet.
+         */
+        String etherType = String.format("%04x", eth.getEtherType());
+        
+        /*
+         * Valid EtherType must be greater than or equal to 0x0600
+         * It is V1 Ethernet Frame if EtherType < 0x0600
+         */
+        if (eth.getEtherType() < 0x0600) {
+            etherType = "0599";
+        }
+
+        if (TypeAliases.l3TypeAliasMap != null && 
+            TypeAliases.l3TypeAliasMap.containsKey(etherType)) {
+            etherType = TypeAliases.l3TypeAliasMap.get(etherType);
+        } else {
+            etherType = "L3_" + etherType;
+        }
+        String switchIdHex = sw.getStringId();
+   
+        String packetName = m.getType().toClass().getName();
+        packetName = packetName.substring(packetName.lastIndexOf('.')+1); 
+        
+        // Construct controller counter for the packet_in
+        String controllerCounterName =
+            CounterStore.createCounterName(CONTROLLER_NAME, 
+                                           -1,
+                                           packetName);
+    
+        String controllerL3CategoryCounterName = 
+            CounterStore.createCounterName(CONTROLLER_NAME, 
+                                           -1,
+                                           packetName, 
+                                           etherType, 
+                                           NetworkLayer.L3);
+
+        String l2Type = null;
+        if (eth.isBroadcast()) {
+        	l2Type = BROADCAST;
+        } else if (eth.isMulticast()) {
+        	l2Type = MULTICAST;
+        } else {
+        	l2Type = UNICAST;
+        }
+        
+        // Construct both port and switch L3 counter for the packet_in
+    	String controllerL2CategoryCounterName = CounterStore.createCounterName(CONTROLLER_NAME, 
+                -1,
+                packetName, 
+                l2Type, 
+                NetworkLayer.L2);
+    	String switchL2CategoryCounterName = CounterStore.createCounterName(switchIdHex, 
+                -1, 
+                packetName, 
+                l2Type, 
+                NetworkLayer.L2);
+    	String portL2CategoryCounterName = CounterStore.createCounterName(switchIdHex, 
+                packet.getInPort(),
+                packetName, 
+                l2Type, 
+                NetworkLayer.L2);
+        
+        // Construct both port and switch L3 counter for the packet_in
+        String portCounterName =
+                CounterStore.createCounterName(switchIdHex, 
+                                               packet.getInPort(),
+                                               packetName);
+        String switchCounterName =
+                CounterStore.createCounterName(switchIdHex, 
+                                               -1,
+                                               packetName);
+        
+        String portL3CategoryCounterName = 
+                CounterStore.createCounterName(switchIdHex, 
+                                               packet.getInPort(),
+                                               packetName, 
+                                               etherType, 
+                                               NetworkLayer.L3);
+        String switchL3CategoryCounterName =
+                CounterStore.createCounterName(switchIdHex, 
+                                               -1, 
+                                               packetName, 
+                                               etherType, 
+                                               NetworkLayer.L3);
+
+        // Controller counters
+        ICounter controllerCounter = getCounter(controllerCounterName);
+        if (controllerCounter == null) {
+            controllerCounter = createCounter(controllerCounterName, 
+                                              CounterType.LONG);
+        }
+        controllerCounter.increment();
+        ICounter portCounter = getCounter(portCounterName);
+        if (portCounter == null) {
+            portCounter = createCounter(portCounterName, 
+                                        CounterType.LONG);
+        }
+        portCounter.increment();
+        ICounter switchCounter = getCounter(switchCounterName);
+        if (switchCounter == null) {
+            switchCounter = createCounter(switchCounterName, 
+                                          CounterType.LONG);
+        }
+        switchCounter.increment();
+
+        // L2 counters
+        ICounter controllerL2Counter = getCounter(controllerL2CategoryCounterName);
+        if (controllerL2Counter == null) {
+            controllerL2Counter = createCounter(controllerL2CategoryCounterName,
+                                                CounterType.LONG);
+        }
+        controllerL2Counter.increment();
+        ICounter switchL2Counter = getCounter(switchL2CategoryCounterName);
+        if (switchL2Counter == null) {
+            switchL2Counter = createCounter(switchL2CategoryCounterName,
+                                            CounterType.LONG);
+        }
+        switchL2Counter.increment();
+        ICounter portL2Counter = getCounter(portL2CategoryCounterName);
+        if (portL2Counter == null) {
+            portL2Counter = createCounter(portL2CategoryCounterName,
+                                          CounterType.LONG);
+        }
+        portL2Counter.increment();
+
+        // L3 counters
+        ICounter controllerL3Counter = getCounter(controllerL3CategoryCounterName);
+        if (controllerL3Counter == null) {
+            controllerL3Counter = createCounter(controllerL3CategoryCounterName,
+                                                CounterType.LONG);
+        }
+        controllerL3Counter.increment();
+        ICounter portL3Counter = getCounter(portL3CategoryCounterName);
+        if (portL3Counter == null) {
+            portL3Counter = createCounter(portL3CategoryCounterName,
+                                          CounterType.LONG);
+        }
+        portL3Counter.increment();
+        ICounter switchL3Counter = getCounter(switchL3CategoryCounterName);
+        if (switchL3Counter == null) {
+            switchL3Counter = createCounter(switchL3CategoryCounterName,
+                                            CounterType.LONG);
+        }
+        switchL3Counter.increment();
+
+        // L4 counters
+        if (etherType.compareTo(CounterStore.L3ET_IPV4) == 0) {
+            IPv4 ipV4 = (IPv4)eth.getPayload();
+            String l4Type = String.format("%02x", ipV4.getProtocol());
+            if (TypeAliases.l4TypeAliasMap != null && 
+                    TypeAliases.l4TypeAliasMap.containsKey(l4Type)) {
+                l4Type = TypeAliases.l4TypeAliasMap.get(l4Type);
+            } else {
+                l4Type = "L4_" + l4Type;
+            }
+            String controllerL4CategoryCounterName = 
+                    CounterStore.createCounterName(CONTROLLER_NAME, 
+                                                   -1, 
+                                                   packetName, 
+                                                   l4Type, 
+                                                   NetworkLayer.L4);
+            String portL4CategoryCounterName =
+                    CounterStore.createCounterName(switchIdHex, 
+                                                   packet.getInPort(), 
+                                                   packetName, 
+                                                   l4Type, 
+                                                   NetworkLayer.L4);
+            String switchL4CategoryCounterName = 
+                    CounterStore.createCounterName(switchIdHex, 
+                                                   -1, 
+                                                   packetName, 
+                                                   l4Type, 
+                                                   NetworkLayer.L4);
+            ICounter controllerL4Counter = getCounter(controllerL4CategoryCounterName);
+            if (controllerL4Counter == null) {
+                controllerL4Counter = createCounter(controllerL4CategoryCounterName, 
+                                                    CounterType.LONG);
+            }
+            controllerL4Counter.increment();
+            ICounter portL4Counter = getCounter(portL4CategoryCounterName);
+            if (portL4Counter == null) {
+                portL4Counter = createCounter(portL4CategoryCounterName, 
+                                              CounterType.LONG);
+            }
+            portL4Counter.increment();
+            ICounter switchL4Counter = getCounter(switchL4CategoryCounterName);
+            if (switchL4Counter == null) {
+                switchL4Counter = createCounter(switchL4CategoryCounterName, 
+                                                CounterType.LONG);
+            }
+            switchL4Counter.increment();
+        }
+    }
+    
+    /**
+     * This method can only be used to update packetOut and flowmod counters
+     * 
+     * @param sw
+     * @param ofMsg
+     */
+    public void updatePktOutFMCounterStore(IOFSwitch sw, OFMessage ofMsg) {
+        String packetName = ofMsg.getType().toClass().getName();
+        packetName = packetName.substring(packetName.lastIndexOf('.')+1);
+        // flowmod is per switch and controller. portid = -1
+        String controllerFMCounterName = CounterStore.createCounterName(CONTROLLER_NAME, -1, packetName);  
+        ICounter counter = getCounter(controllerFMCounterName);
+        if (counter == null) {
+            counter = createCounter(controllerFMCounterName, CounterValue.CounterType.LONG);
+        }
+        counter.increment();
+
+        String switchFMCounterName = CounterStore.createCounterName(sw.getStringId(), -1, packetName);
+        counter = getCounter(switchFMCounterName);
+        if (counter == null) {
+            counter = createCounter(switchFMCounterName, CounterValue.CounterType.LONG);
+        }
+        counter.increment();
+    }
+
+
+    /**
+     * Create a title based on switch ID, portID, vlanID, and counterName
+     * If portID is -1, the title represents the given switch only
+     * If portID is a non-negative number, the title represents the port on the given switch
+     */
+    public static String createCounterName(String switchID, int portID, String counterName) {
+        if (portID < 0) {
+            return switchID + TitleDelimitor + counterName;
+        } else {
+            return switchID + TitleDelimitor + portID + TitleDelimitor + counterName;
+        }
+    }
+
+    /**
+     * Create a title based on switch ID, portID, vlanID, counterName, and subCategory
+     * If portID is -1, the title represents the given switch only
+     * If portID is a non-negative number, the title represents the port on the given switch
+     * For example: PacketIns can be further categorized based on L2 etherType or L3 protocol
+     */
+    public static String createCounterName(String switchID, int portID, String counterName,
+            String subCategory, NetworkLayer layer) {
+        String fullCounterName = "";
+        String groupCounterName = "";
+
+        if (portID < 0) {
+            groupCounterName = switchID + TitleDelimitor + counterName;
+            fullCounterName = groupCounterName + TitleDelimitor + subCategory;
+        } else {
+            groupCounterName = switchID + TitleDelimitor + portID + TitleDelimitor + counterName;
+            fullCounterName = groupCounterName + TitleDelimitor + subCategory;
+        }
+
+        Map<String, List<String>> counterToCategories;      
+        if (layeredCategories.containsKey(layer)) {
+            counterToCategories = layeredCategories.get(layer);
+        } else {
+            counterToCategories = new ConcurrentHashMap<String, List<String>> ();
+            layeredCategories.put(layer, counterToCategories);
+        }
+
+        List<String> categories;
+        if (counterToCategories.containsKey(groupCounterName)) {
+            categories = counterToCategories.get(groupCounterName);
+        } else {
+            categories = new ArrayList<String>();
+            counterToCategories.put(groupCounterName, categories);
+        }
+
+        if (!categories.contains(subCategory)) {
+            categories.add(subCategory);
+        }
+        return fullCounterName;
+    }
+
+    @Override
+    public List<String> getAllCategories(String counterName, NetworkLayer layer) {
+        if (layeredCategories.containsKey(layer)) {
+            Map<String, List<String>> counterToCategories = layeredCategories.get(layer);
+            if (counterToCategories.containsKey(counterName)) {
+                return counterToCategories.get(counterName);
+            }
+        }
+        return null;
+    }
+    
+    @Override
+    public ICounter createCounter(String key, CounterValue.CounterType type) {
+        CounterEntry ce;
+        ICounter c;
+
+        c = SimpleCounter.createCounter(new Date(), type);
+        ce = new CounterEntry();
+        ce.counter = c;
+        ce.title = key;
+        nameToCEIndex.putIfAbsent(key, ce);
+        
+        return nameToCEIndex.get(key).counter;
+    }
+
+    /**
+     * Post construction init method to kick off the health check and random (test) counter threads
+     */
+    @PostConstruct
+    public void startUp() {
+        this.heartbeatCounter = this.createCounter("CounterStore heartbeat", CounterValue.CounterType.LONG);
+        this.randomCounter = this.createCounter("CounterStore random", CounterValue.CounterType.LONG);
+        //Set a background thread to flush any liveCounters every 100 milliseconds
+        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
+            public void run() {
+                heartbeatCounter.increment();
+                randomCounter.increment(new Date(), (long) (Math.random() * 100)); //TODO - pull this in to random timing
+            }}, 100, 100, TimeUnit.MILLISECONDS);
+    }
+    
+    @Override
+    public ICounter getCounter(String key) {
+        CounterEntry counter = nameToCEIndex.get(key);
+        if (counter != null) {
+            return counter.counter;
+        } else {
+            return null;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see net.floodlightcontroller.counter.ICounterStoreService#getAll()
+     */
+    @Override
+    public Map<String, ICounter> getAll() {
+        Map<String, ICounter> ret = new ConcurrentHashMap<String, ICounter>();
+        for(Map.Entry<String, CounterEntry> counterEntry : this.nameToCEIndex.entrySet()) {
+            String key = counterEntry.getKey();
+            ICounter counter = counterEntry.getValue().counter;
+            ret.put(key, counter);
+        }
+        return ret;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> services =
+                new ArrayList<Class<? extends IFloodlightService>>(1);
+        services.add(ICounterStoreService.class);
+        return services;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                    IFloodlightService>();
+        m.put(ICounterStoreService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        // no-op, no dependencies
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+                                 throws FloodlightModuleException {
+        // no-op for now
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // no-op for now
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterValue.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterValue.java
new file mode 100644
index 0000000..1852d5c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterValue.java
@@ -0,0 +1,102 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.counter;
+
+/**
+ * The class defines the counter value type and value
+ * 
+ * @author Kanzhe
+ *
+ */
+public class CounterValue { 
+  public enum CounterType {
+      LONG,
+      DOUBLE
+  }
+  
+  protected CounterType type; 
+  protected long longValue;
+  protected double doubleValue;
+  
+  public CounterValue(CounterType type) {
+    this.type = CounterType.LONG;
+    this.longValue = 0;    
+    this.doubleValue = 0.0;
+  }
+  
+  /**
+   * This method is only applicable to type long.
+   * Setter() should be used for type double
+   */
+  public void increment(long delta) {
+      if (this.type == CounterType.LONG) {
+          this.longValue += delta;
+      } else {
+          throw new IllegalArgumentException("Invalid counter type. This counter is not a long type.");
+      }
+  }
+  
+  public void setLongValue(long value) {
+      if (this.type == CounterType.LONG) {
+          this.longValue = value;
+      } else {
+          throw new IllegalArgumentException("Invalid counter type. This counter is not a long type.");
+      }
+  }
+  
+  public void setDoubleValue(double value) {
+      if (this.type == CounterType.DOUBLE) {
+          this.doubleValue = value;
+      } else {
+          throw new IllegalArgumentException("Invalid counter type. This counter is not a double type.");
+      }
+  }
+  
+  public long getLong() {
+      if (this.type == CounterType.LONG) {
+          return this.longValue;
+      } else {
+          throw new IllegalArgumentException("Invalid counter type. This counter is not a long type.");
+      }
+  }
+  
+  public double getDouble() {
+      if (this.type == CounterType.DOUBLE) {
+          return this.doubleValue;
+      } else {
+          throw new IllegalArgumentException("Invalid counter type. This counter is not a double type.");
+      }
+  }
+  
+
+  public CounterType getType() {
+    return this.type;
+  }
+  
+  public String toString() {
+    String ret = "{type: ";
+    if (this.type == CounterType.DOUBLE) {
+        ret += "Double" + ", value: " + this.doubleValue + "}";
+    } else {
+        ret += "Long" + ", value: " + this.longValue + "}";
+    }
+    return ret;
+  }
+
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounter.java
new file mode 100644
index 0000000..625bebd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounter.java
@@ -0,0 +1,80 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * Simple interface for a counter whose value can be retrieved in several different
+ * time increments (last x seconds, minutes, hours, days)
+ */
+package net.floodlightcontroller.counter;
+
+import java.util.Date;
+
+/**
+ * @author kyle
+ *
+ */
+public interface ICounter {
+  
+  /**
+   * Most commonly used method
+   */
+  public void increment();
+  
+  /**
+   * Used primarily for testing - no performance guarantees
+   */
+  public void increment(Date d, long delta);
+  
+  /**
+   * Counter value setter
+   */
+  public void setCounter(Date d, CounterValue value);
+  
+  /**
+   * Return the most current value
+   */
+  public Date getCounterDate();
+  
+  /**
+   * Return the most current value
+   */
+  public CounterValue getCounterValue();
+  
+  /**
+   * Reset the value
+   */
+  public void reset(Date d);
+  
+  /**
+   * Returns a CountSeries that is a snapshot of the counter's values for the given dateSpan.  (Further changes
+   * to this counter won't be reflected in the CountSeries that comes  back.)
+   * 
+   * @param dateSpan
+   * @return
+   */
+  public CountSeries snapshot(DateSpan dateSpan);
+  
+
+  public static enum DateSpan {
+    REALTIME,
+    SECONDS,
+    MINUTES,
+    HOURS,
+    DAYS,
+    WEEKS
+  }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounterStoreService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounterStoreService.java
new file mode 100644
index 0000000..c89eee0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounterStoreService.java
@@ -0,0 +1,71 @@
+package net.floodlightcontroller.counter;
+
+import java.util.List;
+import java.util.Map;
+
+import org.openflow.protocol.OFMessage;
+
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.counter.CounterStore.NetworkLayer;
+import net.floodlightcontroller.packet.Ethernet;
+
+public interface ICounterStoreService extends IFloodlightService {
+
+	public final static String CONTROLLER_NAME = "controller";
+    public final static String TitleDelimitor = "__";
+
+    /** Broadcast and multicast */
+    public final static String BROADCAST = "broadcast";
+    public final static String MULTICAST = "multicast";
+    public final static String UNICAST = "unicast";
+    
+    /** L2 EtherType subCategories */
+    public final static String L3ET_IPV4 = "L3_IPv4";
+
+    /**
+     * Update packetIn counters
+     * 
+     * @param sw
+     * @param m
+     * @param eth
+     */
+    public void updatePacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth);
+    
+    /**
+     * This method can only be used to update packetOut and flowmod counters
+     * 
+     * @param sw
+     * @param ofMsg
+     */
+    public void updatePktOutFMCounterStore(IOFSwitch sw, OFMessage ofMsg);
+    
+    /**
+     * Retrieve a list of subCategories by counterName.
+     * null if nothing.
+     */
+    public List<String> getAllCategories(String counterName,
+                                         NetworkLayer layer);
+
+    /**
+     * Create a new ICounter and set the title.  Note that the title must be 
+     * unique, otherwise this will throw an IllegalArgumentException.
+     * 
+     * @param key
+     * @param type
+     * @return
+     */
+    public ICounter createCounter(String key, CounterValue.CounterType type);
+
+    /**
+     * Retrieves a counter with the given title, or null if none can be found.
+     */
+    public ICounter getCounter(String key);
+
+    /**
+     * Returns an immutable map of title:counter with all of the counters in the store.
+     * 
+     * (Note - this method may be slow - primarily for debugging/UI)
+     */
+    public Map<String, ICounter> getAll();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/NullCounterStore.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/NullCounterStore.java
new file mode 100644
index 0000000..fed8c1e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/NullCounterStore.java
@@ -0,0 +1,104 @@
+package net.floodlightcontroller.counter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.openflow.protocol.OFMessage;
+
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.counter.CounterStore.NetworkLayer;
+import net.floodlightcontroller.counter.CounterValue.CounterType;
+import net.floodlightcontroller.packet.Ethernet;
+
+/**
+ * An ICounsterStoreService implementation that does nothing.
+ * This is used mainly for performance testing or if you don't
+ * want to use the counterstore.
+ * @author alexreimers
+ *
+ */
+public class NullCounterStore implements IFloodlightModule,
+        ICounterStoreService {
+
+    private ICounter emptyCounter;
+    private List<String> emptyList;
+    private Map<String, ICounter> emptyMap;
+    
+    @Override
+    public void updatePacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth) {
+        // no-op
+    }
+
+    @Override
+    public void updatePktOutFMCounterStore(IOFSwitch sw, OFMessage ofMsg) {
+        // no-op
+    }
+
+    @Override
+    public List<String>
+            getAllCategories(String counterName, NetworkLayer layer) {
+        return emptyList;
+    }
+
+    @Override
+    public ICounter createCounter(String key, CounterType type) {
+        return emptyCounter;
+    }
+
+    @Override
+    public ICounter getCounter(String key) {
+        return emptyCounter;
+    }
+
+    @Override
+    public Map<String, ICounter> getAll() {
+        return emptyMap;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> services =
+                new ArrayList<Class<? extends IFloodlightService>>(1);
+        services.add(ICounterStoreService.class);
+        return services;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                        IFloodlightService>();
+        m.put(ICounterStoreService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        // None, return null
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+                             throws FloodlightModuleException {
+        emptyCounter = new SimpleCounter(new Date(), CounterType.LONG);
+        emptyList = new ArrayList<String>();
+        emptyMap = new HashMap<String, ICounter>();
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // no-op
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/SimpleCounter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/SimpleCounter.java
new file mode 100644
index 0000000..01a0428
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/SimpleCounter.java
@@ -0,0 +1,137 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.counter;
+
+import java.util.Date;
+
+
+
+/**
+ * This is a simple counter implementation that doesn't support data series.
+ * The idea is that floodlight only keeps the realtime value for each counter,
+ * statd, a statistics collection daemon, samples counters at a user-defined interval
+ * and pushes the values to a database, which keeps time-based data series. 
+ * @author Kanzhe
+ *
+ */
+public class SimpleCounter implements ICounter {
+
+  protected CounterValue counter;
+  protected Date samplingTime;
+  protected Date startDate;
+  
+  /**
+   * Factory method to create a new counter instance.  
+   * 
+   * @param startDate
+   * @return
+   */
+  public static ICounter createCounter(Date startDate, CounterValue.CounterType type) {
+    SimpleCounter cc = new SimpleCounter(startDate, type);
+    return cc;
+  }
+  
+  /**
+   * Factory method to create a copy of a counter instance.  
+   * 
+   * @param startDate
+   * @return
+   */
+  public static ICounter createCounter(ICounter copy) {
+    if (copy == null ||
+        copy.getCounterDate() == null ||
+        copy.getCounterValue() == null) {
+        return null;
+    }
+
+     SimpleCounter cc = new SimpleCounter(copy.getCounterDate(),
+            copy.getCounterValue().getType());
+     cc.setCounter(copy.getCounterDate(), copy.getCounterValue());
+     return cc;
+  }
+  
+  /**
+   * Protected constructor - use createCounter factory method instead
+   * @param startDate
+   */
+  protected SimpleCounter(Date startDate, CounterValue.CounterType type) {
+    init(startDate, type);
+  }
+  
+  protected void init(Date startDate, CounterValue.CounterType type) {
+    this.startDate = startDate;
+    this.samplingTime = new Date();
+    this.counter = new CounterValue(type);
+  }
+  
+  /**
+   * This is the key method that has to be both fast and very thread-safe.
+   */
+  @Override
+  synchronized public void increment() {
+    this.increment(new Date(), (long)1);
+  }
+  
+  @Override
+  synchronized public void increment(Date d, long delta) {
+    this.samplingTime = d;
+    this.counter.increment(delta);
+  }
+  
+  synchronized public void setCounter(Date d, CounterValue value) {
+      this.samplingTime = d;
+      this.counter = value;
+  }
+  
+  /**
+   * This is the method to retrieve the current value.
+   */
+  @Override
+  synchronized public CounterValue getCounterValue() {
+    return this.counter;
+  }
+
+  /**
+   * This is the method to retrieve the last sampling time.
+   */
+  @Override
+  synchronized public Date getCounterDate() {
+    return this.samplingTime;
+  }
+  
+  /**
+   * Reset value.
+   */
+  @Override
+  synchronized public void reset(Date startDate) {
+    init(startDate, this.counter.getType());
+  }
+  
+  @Override
+  /**
+   * This method only returns the real-time value.
+   */
+  synchronized public CountSeries snapshot(DateSpan dateSpan) {
+    long[] values = new long[1];
+    values[0] = this.counter.getLong();
+    return new CountSeries(this.samplingTime, DateSpan.DAYS, values);
+  }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/TypeAliases.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/TypeAliases.java
new file mode 100644
index 0000000..0d7e2b5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/TypeAliases.java
@@ -0,0 +1,190 @@
+package net.floodlightcontroller.counter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class to contain some statically initialized data
+ * @author readams
+ *
+ */
+public class TypeAliases {
+    protected static final Map<String,String> l3TypeAliasMap = 
+            new HashMap<String, String>();
+    static {
+        l3TypeAliasMap.put("0599", "L3_V1Ether");
+        l3TypeAliasMap.put("0800", "L3_IPv4");
+        l3TypeAliasMap.put("0806", "L3_ARP");
+        l3TypeAliasMap.put("8035", "L3_RARP");
+        l3TypeAliasMap.put("809b", "L3_AppleTalk");
+        l3TypeAliasMap.put("80f3", "L3_AARP");
+        l3TypeAliasMap.put("8100", "L3_802_1Q");
+        l3TypeAliasMap.put("8137", "L3_Novell_IPX");
+        l3TypeAliasMap.put("8138", "L3_Novell");
+        l3TypeAliasMap.put("86dd", "L3_IPv6");
+        l3TypeAliasMap.put("8847", "L3_MPLS_uni");
+        l3TypeAliasMap.put("8848", "L3_MPLS_multi");
+        l3TypeAliasMap.put("8863", "L3_PPPoE_DS");
+        l3TypeAliasMap.put("8864", "L3_PPPoE_SS");
+        l3TypeAliasMap.put("886f", "L3_MSFT_NLB");
+        l3TypeAliasMap.put("8870", "L3_Jumbo");
+        l3TypeAliasMap.put("889a", "L3_HyperSCSI");
+        l3TypeAliasMap.put("88a2", "L3_ATA_Ethernet");
+        l3TypeAliasMap.put("88a4", "L3_EtherCAT");
+        l3TypeAliasMap.put("88a8", "L3_802_1ad");
+        l3TypeAliasMap.put("88ab", "L3_Ether_Powerlink");
+        l3TypeAliasMap.put("88cc", "L3_LLDP");
+        l3TypeAliasMap.put("88cd", "L3_SERCOS_III");
+        l3TypeAliasMap.put("88e5", "L3_802_1ae");
+        l3TypeAliasMap.put("88f7", "L3_IEEE_1588");
+        l3TypeAliasMap.put("8902", "L3_802_1ag_CFM");
+        l3TypeAliasMap.put("8906", "L3_FCoE");
+        l3TypeAliasMap.put("9000", "L3_Loop");
+        l3TypeAliasMap.put("9100", "L3_Q_in_Q");
+        l3TypeAliasMap.put("cafe", "L3_LLT");
+    }
+    
+    protected static final Map<String,String> l4TypeAliasMap = 
+            new HashMap<String, String>();
+    static {
+        l4TypeAliasMap.put("00", "L4_HOPOPT");
+        l4TypeAliasMap.put("01", "L4_ICMP");
+        l4TypeAliasMap.put("02", "L4_IGAP_IGMP_RGMP");
+        l4TypeAliasMap.put("03", "L4_GGP");
+        l4TypeAliasMap.put("04", "L4_IP");
+        l4TypeAliasMap.put("05", "L4_ST");
+        l4TypeAliasMap.put("06", "L4_TCP");
+        l4TypeAliasMap.put("07", "L4_UCL");
+        l4TypeAliasMap.put("08", "L4_EGP");
+        l4TypeAliasMap.put("09", "L4_IGRP");
+        l4TypeAliasMap.put("0a", "L4_BBN");
+        l4TypeAliasMap.put("0b", "L4_NVP");
+        l4TypeAliasMap.put("0c", "L4_PUP");
+        l4TypeAliasMap.put("0d", "L4_ARGUS");
+        l4TypeAliasMap.put("0e", "L4_EMCON");
+        l4TypeAliasMap.put("0f", "L4_XNET");
+        l4TypeAliasMap.put("10", "L4_Chaos");
+        l4TypeAliasMap.put("11", "L4_UDP");
+        l4TypeAliasMap.put("12", "L4_TMux");
+        l4TypeAliasMap.put("13", "L4_DCN");
+        l4TypeAliasMap.put("14", "L4_HMP");
+        l4TypeAliasMap.put("15", "L4_Packet_Radio");
+        l4TypeAliasMap.put("16", "L4_XEROX_NS_IDP");
+        l4TypeAliasMap.put("17", "L4_Trunk_1");
+        l4TypeAliasMap.put("18", "L4_Trunk_2");
+        l4TypeAliasMap.put("19", "L4_Leaf_1");
+        l4TypeAliasMap.put("1a", "L4_Leaf_2");
+        l4TypeAliasMap.put("1b", "L4_RDP");
+        l4TypeAliasMap.put("1c", "L4_IRTP");
+        l4TypeAliasMap.put("1d", "L4_ISO_TP4");
+        l4TypeAliasMap.put("1e", "L4_NETBLT");
+        l4TypeAliasMap.put("1f", "L4_MFE");
+        l4TypeAliasMap.put("20", "L4_MERIT");
+        l4TypeAliasMap.put("21", "L4_DCCP");
+        l4TypeAliasMap.put("22", "L4_Third_Party_Connect");
+        l4TypeAliasMap.put("23", "L4_IDPR");
+        l4TypeAliasMap.put("24", "L4_XTP");
+        l4TypeAliasMap.put("25", "L4_Datagram_Delivery");
+        l4TypeAliasMap.put("26", "L4_IDPR");
+        l4TypeAliasMap.put("27", "L4_TP");
+        l4TypeAliasMap.put("28", "L4_ILTP");
+        l4TypeAliasMap.put("29", "L4_IPv6_over_IPv4");
+        l4TypeAliasMap.put("2a", "L4_SDRP");
+        l4TypeAliasMap.put("2b", "L4_IPv6_RH");
+        l4TypeAliasMap.put("2c", "L4_IPv6_FH");
+        l4TypeAliasMap.put("2d", "L4_IDRP");
+        l4TypeAliasMap.put("2e", "L4_RSVP");
+        l4TypeAliasMap.put("2f", "L4_GRE");
+        l4TypeAliasMap.put("30", "L4_DSR");
+        l4TypeAliasMap.put("31", "L4_BNA");
+        l4TypeAliasMap.put("32", "L4_ESP");
+        l4TypeAliasMap.put("33", "L4_AH");
+        l4TypeAliasMap.put("34", "L4_I_NLSP");
+        l4TypeAliasMap.put("35", "L4_SWIPE");
+        l4TypeAliasMap.put("36", "L4_NARP");
+        l4TypeAliasMap.put("37", "L4_Minimal_Encapsulation");
+        l4TypeAliasMap.put("38", "L4_TLSP");
+        l4TypeAliasMap.put("39", "L4_SKIP");
+        l4TypeAliasMap.put("3a", "L4_ICMPv6");
+        l4TypeAliasMap.put("3b", "L4_IPv6_No_Next_Header");
+        l4TypeAliasMap.put("3c", "L4_IPv6_Destination_Options");
+        l4TypeAliasMap.put("3d", "L4_Any_host_IP");
+        l4TypeAliasMap.put("3e", "L4_CFTP");
+        l4TypeAliasMap.put("3f", "L4_Any_local");
+        l4TypeAliasMap.put("40", "L4_SATNET");
+        l4TypeAliasMap.put("41", "L4_Kryptolan");
+        l4TypeAliasMap.put("42", "L4_MIT_RVDP");
+        l4TypeAliasMap.put("43", "L4_Internet_Pluribus");
+        l4TypeAliasMap.put("44", "L4_Distributed_FS");
+        l4TypeAliasMap.put("45", "L4_SATNET");
+        l4TypeAliasMap.put("46", "L4_VISA");
+        l4TypeAliasMap.put("47", "L4_IP_Core");
+        l4TypeAliasMap.put("4a", "L4_Wang_Span");
+        l4TypeAliasMap.put("4b", "L4_Packet_Video");
+        l4TypeAliasMap.put("4c", "L4_Backroom_SATNET");
+        l4TypeAliasMap.put("4d", "L4_SUN_ND");
+        l4TypeAliasMap.put("4e", "L4_WIDEBAND_Monitoring");
+        l4TypeAliasMap.put("4f", "L4_WIDEBAND_EXPAK");
+        l4TypeAliasMap.put("50", "L4_ISO_IP");
+        l4TypeAliasMap.put("51", "L4_VMTP");
+        l4TypeAliasMap.put("52", "L4_SECURE_VMTP");
+        l4TypeAliasMap.put("53", "L4_VINES");
+        l4TypeAliasMap.put("54", "L4_TTP");
+        l4TypeAliasMap.put("55", "L4_NSFNET_IGP");
+        l4TypeAliasMap.put("56", "L4_Dissimilar_GP");
+        l4TypeAliasMap.put("57", "L4_TCF");
+        l4TypeAliasMap.put("58", "L4_EIGRP");
+        l4TypeAliasMap.put("59", "L4_OSPF");
+        l4TypeAliasMap.put("5a", "L4_Sprite_RPC");
+        l4TypeAliasMap.put("5b", "L4_Locus_ARP");
+        l4TypeAliasMap.put("5c", "L4_MTP");
+        l4TypeAliasMap.put("5d", "L4_AX");
+        l4TypeAliasMap.put("5e", "L4_IP_within_IP");
+        l4TypeAliasMap.put("5f", "L4_Mobile_ICP");
+        l4TypeAliasMap.put("61", "L4_EtherIP");
+        l4TypeAliasMap.put("62", "L4_Encapsulation_Header");
+        l4TypeAliasMap.put("64", "L4_GMTP");
+        l4TypeAliasMap.put("65", "L4_IFMP");
+        l4TypeAliasMap.put("66", "L4_PNNI");
+        l4TypeAliasMap.put("67", "L4_PIM");
+        l4TypeAliasMap.put("68", "L4_ARIS");
+        l4TypeAliasMap.put("69", "L4_SCPS");
+        l4TypeAliasMap.put("6a", "L4_QNX");
+        l4TypeAliasMap.put("6b", "L4_Active_Networks");
+        l4TypeAliasMap.put("6c", "L4_IPPCP");
+        l4TypeAliasMap.put("6d", "L4_SNP");
+        l4TypeAliasMap.put("6e", "L4_Compaq_Peer_Protocol");
+        l4TypeAliasMap.put("6f", "L4_IPX_in_IP");
+        l4TypeAliasMap.put("70", "L4_VRRP");
+        l4TypeAliasMap.put("71", "L4_PGM");
+        l4TypeAliasMap.put("72", "L4_0_hop");
+        l4TypeAliasMap.put("73", "L4_L2TP");
+        l4TypeAliasMap.put("74", "L4_DDX");
+        l4TypeAliasMap.put("75", "L4_IATP");
+        l4TypeAliasMap.put("76", "L4_ST");
+        l4TypeAliasMap.put("77", "L4_SRP");
+        l4TypeAliasMap.put("78", "L4_UTI");
+        l4TypeAliasMap.put("79", "L4_SMP");
+        l4TypeAliasMap.put("7a", "L4_SM");
+        l4TypeAliasMap.put("7b", "L4_PTP");
+        l4TypeAliasMap.put("7c", "L4_ISIS");
+        l4TypeAliasMap.put("7d", "L4_FIRE");
+        l4TypeAliasMap.put("7e", "L4_CRTP");
+        l4TypeAliasMap.put("7f", "L4_CRUDP");
+        l4TypeAliasMap.put("80", "L4_SSCOPMCE");
+        l4TypeAliasMap.put("81", "L4_IPLT");
+        l4TypeAliasMap.put("82", "L4_SPS");
+        l4TypeAliasMap.put("83", "L4_PIPE");
+        l4TypeAliasMap.put("84", "L4_SCTP");
+        l4TypeAliasMap.put("85", "L4_Fibre_Channel");
+        l4TypeAliasMap.put("86", "L4_RSVP_E2E_IGNORE");
+        l4TypeAliasMap.put("87", "L4_Mobility_Header");
+        l4TypeAliasMap.put("88", "L4_UDP_Lite");
+        l4TypeAliasMap.put("89", "L4_MPLS");
+        l4TypeAliasMap.put("8a", "L4_MANET");
+        l4TypeAliasMap.put("8b", "L4_HIP");
+        l4TypeAliasMap.put("8c", "L4_Shim6");
+        l4TypeAliasMap.put("8d", "L4_WESP");
+        l4TypeAliasMap.put("8e", "L4_ROHC");
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java
new file mode 100644
index 0000000..95969f8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java
@@ -0,0 +1,98 @@
+/**
+*    Copyright 2011,2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager;
+
+import java.util.Date;
+
+
+/**
+ * Represents an independent device on the network.  A device consists of a 
+ * set of entities, and all the information known about a given device comes
+ * only from merging all associated entities for that device.
+ * @author readams
+ */
+public interface IDevice {
+    /**
+     * Get the primary key for this device.
+     * @return the primary key
+     */
+    public Long getDeviceKey();
+    
+    /**
+     * Get the MAC address of the device as a Long value.
+     * @return the MAC address for the device
+     */
+    public long getMACAddress();
+
+    /**
+     * Get the MAC address of the device as a String value.
+     * @return the MAC address for the device
+     */
+    public String getMACAddressString();
+    
+    /**
+     * Get all unique VLAN IDs for the device.  If the device has untagged 
+     * entities, then the value -1 will be returned.
+     * @return an array containing all unique VLAN IDs for the device.
+     */
+    public Short[] getVlanId();
+    
+    /**
+     * Get all unique IPv4 addresses associated with the device.
+     * @return an array containing the unique IPv4 addresses for the device.
+     */
+    public Integer[] getIPv4Addresses();
+    
+    /**
+     * Get all unique attachment points associated with the device.  This will
+     * not include any blocked attachment points.
+     * @return an array containing all unique attachment points for the device
+     */
+    public SwitchPort[] getAttachmentPoints();
+    
+    /**
+     * Get all unique attachment points associated with the device.
+     * @param includeError whether to include blocked attachment points.
+     * Blocked attachment points should not be used for forwarding, but
+     * could be useful to show to a user
+     * @return an array containing all unique attachment points for the device
+     */
+    public SwitchPort[] getAttachmentPoints(boolean includeError);
+
+    /**
+     * Returns all unique VLAN IDs for the device that were observed on 
+     * the given switch port
+     * @param swp the switch port to query
+     * @return an array containing the unique VLAN IDs
+     */
+    public Short[] getSwitchPortVlanIds(SwitchPort swp);
+    
+    /**
+     * Get the most recent timestamp for this device
+     * @return the last seen timestamp
+     */
+    public Date getLastSeen();
+    
+    /**
+     * Get the entity class for the device.
+     * @return the entity class
+     * @see IEntityClassifierService
+     */
+    public IEntityClass getEntityClass();
+    
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceListener.java
new file mode 100644
index 0000000..3c3d599
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceListener.java
@@ -0,0 +1,61 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager;
+
+/**
+ * Implementors of this interface can receive updates from DeviceManager about
+ * the state of devices under its control.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IDeviceListener {
+    /**
+     * Called when a new Device is found
+     * @param device the device that changed
+     */
+    public void deviceAdded(IDevice device);
+
+    /**
+     * Called when a Device is removed, this typically occurs when the port the
+     * Device is attached to goes down, or the switch it is attached to is
+     * removed.
+     * @param device the device that changed
+     */
+    public void deviceRemoved(IDevice device);
+
+    /**
+     * Called when a Device has moved to a new location on the network. Note
+     * that either the switch or the port or both has changed.
+     *
+     * @param device the device that changed
+     */
+    public void deviceMoved(IDevice device);
+    
+    /**
+     * Called when a network address has been added or remove from a device
+     * 
+     * @param device the device that changed
+     */
+    public void deviceIPV4AddrChanged(IDevice device);
+    
+    /**
+     * Called when a VLAN tag for the device has been added or removed
+     * @param device the device that changed
+     */
+    public void deviceVlanChanged(IDevice device);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java
new file mode 100755
index 0000000..ad29a94
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java
@@ -0,0 +1,202 @@
+/**
+*    Copyright 2011,2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Iterator;
+
+import net.floodlightcontroller.core.FloodlightContextStore;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+/**
+ * Device manager allows interacting with devices on the network.  Note
+ * that under normal circumstances, {@link Device} objects should be retrieved
+ * from the {@link FloodlightContext} rather than from {@link IDeviceManager}.
+ */
+public interface IDeviceService extends IFloodlightService {
+    /**
+     * Fields used in devices for indexes and querying
+     * @see IDeviceService#addIndex
+     */
+    enum DeviceField {
+        MAC, IPV4, VLAN, SWITCH, PORT
+    }
+
+    /**
+     * The source device for the current packet-in, if applicable.
+     */
+    public static final String CONTEXT_SRC_DEVICE = 
+            "net.floodlightcontroller.devicemanager.srcDevice"; 
+
+    /**
+     * The destination device for the current packet-in, if applicable.
+     */
+    public static final String CONTEXT_DST_DEVICE = 
+            "net.floodlightcontroller.devicemanager.dstDevice"; 
+
+    /**
+     * A FloodlightContextStore object that can be used to interact with the 
+     * FloodlightContext information created by BVS manager.
+     */
+    public static final FloodlightContextStore<IDevice> fcStore = 
+        new FloodlightContextStore<IDevice>();
+
+    /**
+     * Get the device with the given device key.
+     * 
+     * @param deviceKey the key to search for
+     * @return the device associated with the key, or null if no such device
+     * @see IDevice#getDeviceKey()
+     */
+    public IDevice getDevice(Long deviceKey);
+    
+    /**
+     * Search for a device exactly matching the provided device fields. This 
+     * is the same lookup process that is used for packet_in processing and 
+     * device learning. Thus, findDevice() can be used to match flow entries
+     * from switches to devices. 
+     * Only the key fields as defined by the {@link IEntityClassifierService} will
+     * be important in this search. All key fields MUST be supplied. 
+     * 
+     *{@link queryDevices()} might be more appropriate!
+     * 
+     * @param macAddress The MAC address
+     * @param vlan the VLAN. Null means no VLAN and is valid even if VLAN is a 
+     *        key field.
+     * @param ipv4Address the ipv4 address
+     * @param switchDPID the switch DPID
+     * @param switchPort the switch port
+     * @return an {@link IDevice} or null if no device is found.
+     * @see IDeviceManager#setEntityClassifier(IEntityClassifierService)
+     * @throws IllegalArgumentException if not all key fields of the
+     * current {@link IEntityClassifierService} are specified.
+     */
+    public IDevice findDevice(long macAddress, Short vlan,
+                              Integer ipv4Address, Long switchDPID,
+                              Integer switchPort)
+                              throws IllegalArgumentException;
+    
+    /**
+     * Get a destination device using entity fields that corresponds with
+     * the given source device.  The source device is important since
+     * there could be ambiguity in the destination device without the
+     * attachment point information. 
+     * 
+     * @param source the source device.  The returned destination will be
+     * in the same entity class as the source.
+     * @param macAddress The MAC address for the destination
+     * @param vlan the VLAN if available
+     * @param ipv4Address The IP address if available.
+     * @return an {@link IDevice} or null if no device is found.
+     * @see IDeviceService#findDevice(long, Short, Integer, Long, 
+     * Integer)
+     * @throws IllegalArgumentException if not all key fields of the
+     * source's {@link IEntityClass} are specified.
+     */
+    public IDevice findDestDevice(IDevice source,
+                                  long macAddress, Short vlan,
+                                  Integer ipv4Address)
+                                  throws IllegalArgumentException;
+
+    /**
+     * Get an unmodifiable collection view over all devices currently known.
+     * @return the collection of all devices
+     */
+    public Collection<? extends IDevice> getAllDevices();
+
+    /**
+     * Create an index over a set of fields.  This allows efficient lookup
+     * of devices when querying using the indexed set of specified fields.
+     * The index must be registered before any device learning takes place,
+     * or it may be incomplete.  It's OK if this is called multiple times with
+     * the same fields; only one index will be created for each unique set of 
+     * fields.
+     * 
+     * @param perClass set to true if the index should be maintained for each
+     * entity class separately.
+     * @param keyFields the set of fields on which to index
+     */
+    public void addIndex(boolean perClass,
+                         EnumSet<DeviceField> keyFields);
+    
+    /**
+     * Find devices that match the provided query.  Any fields that are
+     * null will not be included in the query.  If there is an index for 
+     * the query, then it will be performed efficiently using the index.
+     * Otherwise, there will be a full scan of the device list.
+     * 
+     * @param macAddress The MAC address
+     * @param vlan the VLAN
+     * @param ipv4Address the ipv4 address
+     * @param switchDPID the switch DPID
+     * @param switchPort the switch port
+     * @return an iterator over a set of devices matching the query
+     * @see IDeviceService#queryClassDevices(IEntityClass, Long, 
+     * Short, Integer, Long, Integer)
+     */
+    public Iterator<? extends IDevice> queryDevices(Long macAddress,
+                                                    Short vlan,
+                                                    Integer ipv4Address, 
+                                                    Long switchDPID,
+                                                    Integer switchPort);
+
+    /**
+     * Find devices that match the provided query.  Only the index for
+     * the class of the specified reference device will be searched.  
+     * Any fields that are null will not be included in the query.  If
+     * there is an index for the query, then it will be performed
+     * efficiently using the index. Otherwise, there will be a full scan
+     * of the device list.
+     * 
+     * @param reference The reference device to refer to when finding
+     * entity classes.
+     * @param macAddress The MAC address
+     * @param vlan the VLAN
+     * @param ipv4Address the ipv4 address
+     * @param switchDPID the switch DPID
+     * @param switchPort the switch port
+    * @return an iterator over a set of devices matching the query
+     * @see IDeviceService#queryClassDevices(Long, 
+     * Short, Integer, Long, Integer)
+     */
+    public Iterator<? extends IDevice> queryClassDevices(IDevice reference,
+                                                         Long macAddress,
+                                                         Short vlan,
+                                                         Integer ipv4Address, 
+                                                         Long switchDPID,
+                                                         Integer switchPort);
+    
+    /**
+     * Adds a listener to listen for IDeviceManagerServices notifications
+     * 
+     * @param listener The listener that wants the notifications
+     */
+    public void addListener(IDeviceListener listener);
+    
+    /**
+     * Specify points in the network where attachment points are not to
+     * be learned.
+     * @param sw
+     * @param port
+     */
+	public void addSuppressAPs(long swId, short port);
+
+	public void removeSuppressAPs(long swId, short port);
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java
new file mode 100644
index 0000000..bb077f1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java
@@ -0,0 +1,59 @@
+/**
+*    Copyright 2011,2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager;
+
+import java.util.EnumSet;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.Device;
+
+/**
+ * Entities within an entity class are grouped into {@link Device} objects
+ * based on the {@link IEntityClass}, and the key fields specified by the entity 
+ * class. A set of entities are considered to be the same device if and only 
+ * if they belong to the same entity class and they match on all key fields 
+ * for that entity class. A field is effectively wildcarded by not including 
+ * it in the list of key fields returned by {@link IEntityClassifierService} and/or 
+ * {@link IEntityClass}.
+ * 
+ * Note that if you're not using static objects, you'll need to override
+ * {@link Object#equals(Object)} and {@link Object#hashCode()}.
+ * 
+ * @author readams
+ *
+ */
+public interface IEntityClass {
+    /**
+     * Return the set of key fields for this entity class.  Entities 
+     * belonging to this class that differ in fields not included in 
+     * this collection will be considered the same device.  The key 
+     * fields for an entity class must not change unless associated 
+     * with a flush of that entity class.
+     * 
+     * @return a set containing the fields that should not
+     * be wildcarded.  May be null to indicate that all fields are key fields.
+     */
+    EnumSet<DeviceField> getKeyFields();
+    
+    /**
+     * Returns a user-friendly, unique name for this EntityClass
+     * @return the name of the entity class
+     */
+    String getName();
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassListener.java
new file mode 100644
index 0000000..6029af1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassListener.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager;
+
+import java.util.Set;
+
+/**
+ * Implementors of this interface can receive updates from DeviceManager about
+ * the changes entity Classes.
+ *
+ * @author Ananth Suryanarayana (Ananth.Suryanarayana@bigswitch.com)
+ */
+public interface IEntityClassListener {
+
+    /**
+     * Process entity classes change event.
+     * @param  entityClassNames Set of entity classes changed
+     */
+    public void entityClassChanged(Set<String> entityClassNames);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java
new file mode 100644
index 0000000..2569a7d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java
@@ -0,0 +1,108 @@
+/**
+*    Copyright 2011,2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager;
+
+import java.util.Collection;
+import java.util.EnumSet;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/**
+ * A component that wishes to participate in entity classification needs to 
+ * implement the IEntityClassifier interface, and register with the Device
+ * Manager as an entity classifier. An entity is classified by the classifier
+ * into an {@link IEntityClass} 
+ * 
+ * @author readams
+ */
+public interface IEntityClassifierService extends IFloodlightService {
+    /**
+    * Classify the given entity into an IEntityClass.  It is important
+    * that the key fields returned by {@link IEntityClassifierService#getKeyFields()}
+    * be sufficient for classifying entities.  That is, if two entities are
+    * identical except for a field that is not a key field, they must be
+    * assigned the same class.  Furthermore, entity classification must be
+    * transitive: For all entities x, y, z, if x and y belong to a class c, and 
+    * y and z belong class c, then x and z must belong to class c.
+    * 
+    * @param entity the entity to classify
+    * @return the IEntityClass resulting from the classification.
+    * @see IEntityClassifierService#getKeyFields()
+    */
+   IEntityClass classifyEntity(Entity entity);
+
+   /**
+    * Return the most general list of fields that should be used as key 
+    * fields.  If devices differ in any fields not listed here, they can
+    * never be considered a different device by any {@link IEntityClass} 
+    * returned by {@link IEntityClassifierService#classifyEntity}.  The key fields
+    * for an entity classifier must not change unless associated with a 
+    * flush of all entity state.  The list of key fields must be the union
+    * of all key fields that could be returned by
+    * {@link IEntityClass#getKeyFields()}.
+    * 
+    * @return a set containing the fields that should not be
+    * wildcarded.  May be null to indicate that all fields are key fields.
+    * @see {@link IEntityClass#getKeyFields()}
+    * @see {@link IEntityClassifierService#classifyEntity}
+    */
+   EnumSet<DeviceField> getKeyFields();
+
+   /**
+    * Reclassify the given entity into a class.  When reclassifying entities,
+    * it can be helpful to take into account the current classification either
+    * as an optimization or to allow flushing any cached state tied to the key
+    * for that device.  The entity will be assigned to a new device with a new
+    * object if the entity class returned is different from the entity class for
+    * curDevice.
+    * 
+    * <p>Note that you must take steps to ensure you always return classes
+    * in some consistent ordering.
+
+    * @param curDevice the device currently associated with the entity
+    * @param entity the entity to reclassify
+    * @return the IEntityClass resulting from the classification
+    */
+   IEntityClass reclassifyEntity(IDevice curDevice,
+                                             Entity entity);
+
+   /**
+    * Once reclassification is complete for a device, this method will be
+    * called. If any entities within the device changed their classification,
+    * it will split into one or more new devices for each of the entities.  If
+    * two devices are merged because of a reclassification, then this will be
+    * called on each of the devices, with the same device in the newDevices 
+    * collection.
+    * 
+    * @param oldDevice the original device object
+    * @param newDevices all the new devices derived from the entities of the
+    * old device.  If null, the old device was unchanged.
+    */
+   void deviceUpdate(IDevice oldDevice, 
+                     Collection<? extends IDevice> newDevices);
+
+   /**
+    * Adds a listener to listen for IEntityClassifierServices notifications
+    *
+    * @param listener The listener that wants the notifications
+    */
+   public void addListener(IEntityClassListener listener);
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java
new file mode 100644
index 0000000..7426163
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java
@@ -0,0 +1,136 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.ser.ToStringSerializer;
+
+/**
+ * A simple switch DPID/port pair
+ * @author readams
+ *
+ */
+public class SwitchPort {
+    @JsonSerialize(using=ToStringSerializer.class)
+    public enum ErrorStatus {
+        DUPLICATE_DEVICE("duplicate-device");
+        
+        private String value;
+        ErrorStatus(String v) {
+            value = v;
+        }
+        
+        @Override
+        public String toString() {
+            return value;
+        }
+
+        public static ErrorStatus fromString(String str) {
+            for (ErrorStatus m : ErrorStatus.values()) {
+                if (m.value.equals(str)) {
+                    return m;
+                }
+            }
+            return null;
+        }
+    }
+    
+    protected long switchDPID;
+    protected int port;
+    ErrorStatus errorStatus;
+
+    /**
+     * Simple constructor
+     * @param switchDPID the dpid
+     * @param port the port
+     * @param errorStatus any error status for the switch port
+     */
+    public SwitchPort(long switchDPID, int port, ErrorStatus errorStatus) {
+        super();
+        this.switchDPID = switchDPID;
+        this.port = port;
+        this.errorStatus = errorStatus;
+    }
+
+    /**
+     * Simple constructor
+     * @param switchDPID the dpid
+     * @param port the port
+     */
+    public SwitchPort(long switchDPID, int port) {
+        super();
+        this.switchDPID = switchDPID;
+        this.port = port;
+        this.errorStatus = null;
+    }
+    
+    // ***************
+    // Getters/Setters
+    // ***************
+
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getSwitchDPID() {
+        return switchDPID;
+    }
+    
+    public int getPort() {
+        return port;
+    }
+    
+    public ErrorStatus getErrorStatus() {
+        return errorStatus;
+    }
+
+    // ******
+    // Object
+    // ******
+    
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                        + ((errorStatus == null)
+                                ? 0
+                                : errorStatus.hashCode());
+        result = prime * result + port;
+        result = prime * result + (int) (switchDPID ^ (switchDPID >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        SwitchPort other = (SwitchPort) obj;
+        if (errorStatus != other.errorStatus) return false;
+        if (port != other.port) return false;
+        if (switchDPID != other.switchDPID) return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "SwitchPort [switchDPID=" + switchDPID + ", port=" + port
+                + ", errorStatus=" + errorStatus + "]";
+    }
+
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java
new file mode 100644
index 0000000..a08a3a5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java
@@ -0,0 +1,124 @@
+/**
+ *    Copyright 2011,2012 Big Switch Networks, Inc.
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+/**
+ * @author Srini
+ */
+
+package net.floodlightcontroller.devicemanager.internal;
+
+public class AttachmentPoint {
+    long  sw;
+    short port;
+    long  activeSince;
+    long  lastSeen;
+
+    // Timeout for moving attachment points from OF/broadcast
+    // domain to another.
+    public static final long INACTIVITY_INTERVAL = 30000; // 30 seconds
+    public static final long EXTERNAL_TO_EXTERNAL_TIMEOUT = 5000;  // 5 seconds
+    public static final long OPENFLOW_TO_EXTERNAL_TIMEOUT = 30000; // 30 seconds
+    public static final long CONSISTENT_TIMEOUT = 30000;           // 30 seconds
+
+    public AttachmentPoint(long sw, short port, long activeSince,
+                           long lastSeen) {
+        this.sw = sw;
+        this.port = port;
+        this.activeSince = activeSince;
+        this.lastSeen = lastSeen;
+    }
+
+    public AttachmentPoint(long sw, short port, long lastSeen) {
+        this.sw = sw;
+        this.port = port;
+        this.lastSeen = lastSeen;
+        this.activeSince = lastSeen;
+    }
+
+    public AttachmentPoint(AttachmentPoint ap) {
+        this.sw = ap.sw;
+        this.port = ap.port;
+        this.activeSince = ap.activeSince;
+        this.lastSeen = ap.lastSeen;
+    }
+
+    public long getSw() {
+        return sw;
+    }
+    public void setSw(long sw) {
+        this.sw = sw;
+    }
+    public short getPort() {
+        return port;
+    }
+    public void setPort(short port) {
+        this.port = port;
+    }
+    public long getActiveSince() {
+        return activeSince;
+    }
+    public void setActiveSince(long activeSince) {
+        this.activeSince = activeSince;
+    }
+    public long getLastSeen() {
+        return lastSeen;
+    }
+    public void setLastSeen(long lastSeen) {
+        if (this.lastSeen + INACTIVITY_INTERVAL < lastSeen)
+            this.activeSince = lastSeen;
+        if (this.lastSeen < lastSeen)
+            this.lastSeen = lastSeen;
+    }
+
+    /**
+     *  Hash is generated using only switch and port
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + port;
+        result = prime * result + (int) (sw ^ (sw >>> 32));
+        return result;
+    }
+
+    /**
+     * Compares only the switch and port
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        AttachmentPoint other = (AttachmentPoint) obj;
+        if (port != other.port)
+            return false;
+        if (sw != other.sw)
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "AttachmentPoint [sw=" + sw + ", port=" + port
+               + ", activeSince=" + activeSince + ", lastSeen=" + lastSeen
+               + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
new file mode 100644
index 0000000..faed0d4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
@@ -0,0 +1,138 @@
+/**
+*    Copyright 2011,2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassListener;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+
+/**
+ * This is a default entity classifier that simply classifies all
+ * entities into a fixed entity class, with key fields of MAC and VLAN.
+ * @author readams
+ */
+public class DefaultEntityClassifier implements
+        IEntityClassifierService,
+        IFloodlightModule 
+{
+    /**
+     * A default fixed entity class
+     */
+    protected static class DefaultEntityClass implements IEntityClass {
+        String name;
+
+        public DefaultEntityClass(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+            return keyFields;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+    }
+    
+    protected static EnumSet<DeviceField> keyFields;
+    static {
+        keyFields = EnumSet.of(DeviceField.MAC, DeviceField.VLAN);
+    }
+    protected static DefaultEntityClass entityClass =
+        new DefaultEntityClass("DefaultEntityClass");
+
+    @Override
+    public IEntityClass classifyEntity(Entity entity) {
+        return entityClass;
+    }
+
+    @Override
+    public IEntityClass reclassifyEntity(IDevice curDevice,
+                                                     Entity entity) {
+        return entityClass;
+    }
+
+    @Override
+    public void deviceUpdate(IDevice oldDevice, 
+                             Collection<? extends IDevice> newDevices) {
+        // no-op
+    }
+
+    @Override
+    public EnumSet<DeviceField> getKeyFields() {
+        return keyFields;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IEntityClassifierService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m = 
+        new HashMap<Class<? extends IFloodlightService>,
+                    IFloodlightService>();
+        // We are the class that implements the service
+        m.put(IEntityClassifierService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        // No dependencies
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+                                                 throws FloodlightModuleException {
+        // no-op
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // no-op
+    }
+
+    @Override
+    public void addListener(IEntityClassListener listener) {
+        // no-op
+        
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
new file mode 100755
index 0000000..645125e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
@@ -0,0 +1,725 @@
+/**
+ *    Copyright 2011,2012 Big Switch Networks, Inc.
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.web.DeviceSerializer;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.SwitchPort.ErrorStatus;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.topology.ITopologyService;
+
+/**
+ * Concrete implementation of {@link IDevice}
+ * @author readams
+ */
+@JsonSerialize(using=DeviceSerializer.class)
+public class Device implements IDevice {
+    protected static Logger log =
+            LoggerFactory.getLogger(Device.class);
+
+    protected Long deviceKey;
+    protected DeviceManagerImpl deviceManager;
+
+    protected Entity[] entities;
+    protected IEntityClass entityClass;
+
+    protected String macAddressString;
+
+    /**
+     * These are the old attachment points for the device that were
+     * valid no more than INACTIVITY_TIME ago.
+     */
+    protected List<AttachmentPoint> oldAPs;
+    /**
+     * The current attachment points for the device.
+     */
+    protected List<AttachmentPoint> attachmentPoints;
+    // ************
+    // Constructors
+    // ************
+
+    /**
+     * Create a device from an entities
+     * @param deviceManager the device manager for this device
+     * @param deviceKey the unique identifier for this device object
+     * @param entity the initial entity for the device
+     * @param entityClass the entity classes associated with the entity
+     */
+    public Device(DeviceManagerImpl deviceManager,
+                  Long deviceKey,
+                  Entity entity,
+                  IEntityClass entityClass) {
+        this.deviceManager = deviceManager;
+        this.deviceKey = deviceKey;
+        this.entities = new Entity[] {entity};
+        this.macAddressString =
+                HexString.toHexString(entity.getMacAddress(), 6);
+        this.entityClass = entityClass;
+        Arrays.sort(this.entities);
+
+        this.oldAPs = null;
+        this.attachmentPoints = null;
+
+        if (entity.getSwitchDPID() != null &&
+                entity.getSwitchPort() != null){
+            long sw = entity.getSwitchDPID();
+            short port = entity.getSwitchPort().shortValue();
+
+            if (deviceManager.isValidAttachmentPoint(sw, port)) {
+                AttachmentPoint ap;
+                ap = new AttachmentPoint(sw, port,
+entity.getLastSeenTimestamp().getTime());
+
+                this.attachmentPoints = new ArrayList<AttachmentPoint>();
+                this.attachmentPoints.add(ap);
+            }
+        }
+    }
+
+    /**
+     * Create a device from a set of entities
+     * @param deviceManager the device manager for this device
+     * @param deviceKey the unique identifier for this device object
+     * @param entities the initial entities for the device
+     * @param entityClass the entity class associated with the entities
+     */
+    public Device(DeviceManagerImpl deviceManager,
+                  Long deviceKey,
+                  Collection<AttachmentPoint> oldAPs,
+                  Collection<AttachmentPoint> attachmentPoints,
+                  Collection<Entity> entities,
+                  IEntityClass entityClass) {
+        this.deviceManager = deviceManager;
+        this.deviceKey = deviceKey;
+        this.entities = entities.toArray(new Entity[entities.size()]);
+        this.oldAPs = null;
+        this.attachmentPoints = null;
+        if (oldAPs != null) {
+            this.oldAPs =
+                    new ArrayList<AttachmentPoint>(oldAPs);
+        }
+        if (attachmentPoints != null) {
+            this.attachmentPoints =
+                    new ArrayList<AttachmentPoint>(attachmentPoints);
+        }
+        this.macAddressString =
+                HexString.toHexString(this.entities[0].getMacAddress(), 6);
+        this.entityClass = entityClass;
+        Arrays.sort(this.entities);
+    }
+
+    /**
+     * Construct a new device consisting of the entities from the old device
+     * plus an additional entity
+     * @param device the old device object
+     * @param newEntity the entity to add. newEntity must be have the same
+     *        entity class as device
+     */
+    public Device(Device device,
+                  Entity newEntity) {
+        this.deviceManager = device.deviceManager;
+        this.deviceKey = device.deviceKey;
+        this.entities = Arrays.<Entity>copyOf(device.entities,
+                                              device.entities.length + 1);
+        this.entities[this.entities.length - 1] = newEntity;
+        Arrays.sort(this.entities);
+        this.oldAPs = null;
+        if (device.oldAPs != null) {
+            this.oldAPs =
+                    new ArrayList<AttachmentPoint>(device.oldAPs);
+        }
+        this.attachmentPoints = null;
+        if (device.attachmentPoints != null) {
+            this.attachmentPoints =
+                    new ArrayList<AttachmentPoint>(device.attachmentPoints);
+        }
+
+        this.macAddressString =
+                HexString.toHexString(this.entities[0].getMacAddress(), 6);
+
+        this.entityClass = device.entityClass;
+    }
+
+    /**
+     * Given a list of attachment points (apList), the procedure would return
+     * a map of attachment points for each L2 domain.  L2 domain id is the key.
+     * @param apList
+     * @return
+     */
+    private Map<Long, AttachmentPoint> getAPMap(List<AttachmentPoint> apList) {
+
+        if (apList == null) return null;
+        ITopologyService topology = deviceManager.topology;
+
+        // Get the old attachment points and sort them.
+        List<AttachmentPoint>oldAP = new ArrayList<AttachmentPoint>();
+        if (apList != null) oldAP.addAll(apList);
+
+        // Remove invalid attachment points before sorting.
+        List<AttachmentPoint>tempAP =
+                new ArrayList<AttachmentPoint>();
+        for(AttachmentPoint ap: oldAP) {
+            if (deviceManager.isValidAttachmentPoint(ap.getSw(), ap.getPort())){
+                tempAP.add(ap);
+            }
+        }
+        oldAP = tempAP;
+
+        Collections.sort(oldAP, deviceManager.apComparator);
+
+        // Map of attachment point by L2 domain Id.
+        Map<Long, AttachmentPoint> apMap = new HashMap<Long, AttachmentPoint>();
+
+        for(int i=0; i<oldAP.size(); ++i) {
+            AttachmentPoint ap = oldAP.get(i);
+            // if this is not a valid attachment point, continue
+            if (!deviceManager.isValidAttachmentPoint(ap.getSw(),
+                                                      ap.getPort()))
+                continue;
+
+            long id = topology.getL2DomainId(ap.getSw());
+            apMap.put(id, ap);
+        }
+
+        if (apMap.isEmpty()) return null;
+        return apMap;
+    }
+
+    /**
+     * Remove all attachment points that are older than INACTIVITY_INTERVAL
+     * from the list.
+     * @param apList
+     * @return
+     */
+    private boolean removeExpiredAttachmentPoints(List<AttachmentPoint>apList) {
+
+        List<AttachmentPoint> expiredAPs = new ArrayList<AttachmentPoint>();
+
+        if (apList == null) return false;
+
+        for(AttachmentPoint ap: apList) {
+            if (ap.getLastSeen() + AttachmentPoint.INACTIVITY_INTERVAL <
+                    System.currentTimeMillis())
+                expiredAPs.add(ap);
+        }
+        if (expiredAPs.size() > 0) {
+            apList.removeAll(expiredAPs);
+            return true;
+        } else return false;
+    }
+
+    /**
+     * Get a list of duplicate attachment points, given a list of old attachment
+     * points and one attachment point per L2 domain. Given a true attachment
+     * point in the L2 domain, say trueAP, another attachment point in the
+     * same L2 domain, say ap, is duplicate if:
+     * 1. ap is inconsistent with trueAP, and
+     * 2. active time of ap is after that of trueAP; and
+     * 3. last seen time of ap is within the last INACTIVITY_INTERVAL
+     * @param oldAPList
+     * @param apMap
+     * @return
+     */
+    List<AttachmentPoint> getDuplicateAttachmentPoints(List<AttachmentPoint>oldAPList,
+                                                       Map<Long, AttachmentPoint>apMap) {
+        ITopologyService topology = deviceManager.topology;
+        List<AttachmentPoint> dupAPs = new ArrayList<AttachmentPoint>();
+        long timeThreshold = System.currentTimeMillis() -
+                AttachmentPoint.INACTIVITY_INTERVAL;
+
+        if (oldAPList == null || apMap == null)
+            return dupAPs;
+
+        for(AttachmentPoint ap: oldAPList) {
+            long id = topology.getL2DomainId(ap.getSw());
+            AttachmentPoint trueAP = apMap.get(id);
+
+            if (trueAP == null) continue;
+            boolean c = (topology.isConsistent(trueAP.getSw(), trueAP.getPort(),
+                                              ap.getSw(), ap.getPort()));
+            boolean active = (ap.getActiveSince() > trueAP.getActiveSince());
+            boolean last = ap.getLastSeen() > timeThreshold;
+            if (!c && active && last) {
+                dupAPs.add(ap);
+            }
+        }
+
+        return dupAPs;
+    }
+
+    /**
+     * Update the known attachment points.  This method is called whenever
+     * topology changes. The method returns true if there's any change to
+     * the list of attachment points -- which indicates a possible device
+     * move.
+     * @return
+     */
+    protected boolean updateAttachmentPoint() {
+        boolean moved = false;
+
+        if (attachmentPoints == null || attachmentPoints.isEmpty())
+            return false;
+
+        List<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
+        if (attachmentPoints != null) apList.addAll(attachmentPoints);
+        Map<Long, AttachmentPoint> newMap = getAPMap(apList);
+        if (newMap == null || newMap.size() != apList.size()) {
+            moved = true;
+        }
+
+        // Prepare the new attachment point list.
+        if (moved) {
+            List<AttachmentPoint> newAPList =
+                    new ArrayList<AttachmentPoint>();
+            if (newMap != null) newAPList.addAll(newMap.values());
+            this.attachmentPoints = newAPList;
+        }
+
+        // Set the oldAPs to null.
+        this.oldAPs = null;
+        return moved;
+    }
+
+    /**
+     * Update the list of attachment points given that a new packet-in
+     * was seen from (sw, port) at time (lastSeen).  The return value is true
+     * if there was any change to the list of attachment points for the device
+     * -- which indicates a device move.
+     * @param sw
+     * @param port
+     * @param lastSeen
+     * @return
+     */
+    protected boolean updateAttachmentPoint(long sw, short port, long lastSeen){
+        ITopologyService topology = deviceManager.topology;
+        List<AttachmentPoint> oldAPList;
+        List<AttachmentPoint> apList;
+        boolean oldAPFlag = false;
+
+        if (!deviceManager.isValidAttachmentPoint(sw, port)) return false;
+        AttachmentPoint newAP = new AttachmentPoint(sw, port, lastSeen);
+
+        //Copy the oldAP and ap list.
+        apList = new ArrayList<AttachmentPoint>();
+        if (attachmentPoints != null) apList.addAll(attachmentPoints);
+        oldAPList = new ArrayList<AttachmentPoint>();
+        if (oldAPs != null) oldAPList.addAll(oldAPs);
+
+        // if the sw, port is in old AP, remove it from there
+        // and update the lastSeen in that object.
+        if (oldAPList.contains(newAP)) {
+            int index = oldAPList.indexOf(newAP);
+            newAP = oldAPList.remove(index);
+            newAP.setLastSeen(lastSeen);
+            this.oldAPs = oldAPList;
+            oldAPFlag = true;
+        }
+
+        // newAP now contains the new attachment point.
+
+        // Get the APMap is null or empty.
+        Map<Long, AttachmentPoint> apMap = getAPMap(apList);
+        if (apMap == null || apMap.isEmpty()) {
+            apList.add(newAP);
+            attachmentPoints = apList;
+            return true;
+        }
+
+        long id = topology.getL2DomainId(sw);
+        AttachmentPoint oldAP = apMap.get(id);
+
+        if (oldAP == null) // No attachment on this L2 domain.
+        {
+            apList = new ArrayList<AttachmentPoint>();
+            apList.addAll(apMap.values());
+            apList.add(newAP);
+            this.attachmentPoints = apList;
+            return true; // new AP found on an L2 island.
+        }
+
+        // There is already a known attachment point on the same L2 island.
+        // we need to compare oldAP and newAP.
+        if (oldAP.equals(newAP)) {
+            // nothing to do here. just the last seen has to be changed.
+            if (newAP.lastSeen > oldAP.lastSeen) {
+                oldAP.setLastSeen(newAP.lastSeen);
+            }
+            this.attachmentPoints =
+                    new ArrayList<AttachmentPoint>(apMap.values());
+            return false; // nothing to do here.
+        }
+
+        int x = deviceManager.apComparator.compare(oldAP, newAP);
+        if (x < 0) {
+            // newAP replaces oldAP.
+            apMap.put(id, newAP);
+            this.attachmentPoints =
+                    new ArrayList<AttachmentPoint>(apMap.values());
+
+            oldAPList = new ArrayList<AttachmentPoint>();
+            if (oldAPs != null) oldAPList.addAll(oldAPs);
+            oldAPList.add(oldAP);
+            this.oldAPs = oldAPList;
+            if (!topology.isInSameBroadcastDomain(oldAP.getSw(), oldAP.getPort(),
+                                                  newAP.getSw(), newAP.getPort()))
+                return true; // attachment point changed.
+        } else  if (oldAPFlag) {
+            // retain oldAP  as is.  Put the newAP in oldAPs for flagging
+            // possible duplicates.
+                oldAPList = new ArrayList<AttachmentPoint>();
+                if (oldAPs != null) oldAPList.addAll(oldAPs);
+                // Add ot oldAPList only if it was picked up from the oldAPList
+                oldAPList.add(newAP);
+                this.oldAPs = oldAPList;
+        }
+        return false;
+    }
+
+    /**
+     * Delete (sw,port) from the list of list of attachment points
+     * and oldAPs.
+     * @param sw
+     * @param port
+     * @return
+     */
+    public boolean deleteAttachmentPoint(long sw, short port) {
+        AttachmentPoint ap = new AttachmentPoint(sw, port, 0);
+
+        if (this.oldAPs != null) {
+            ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
+            apList.addAll(this.oldAPs);
+            int index = apList.indexOf(ap);
+            if (index > 0) {
+                apList.remove(index);
+                this.oldAPs = apList;
+            }
+        }
+
+        if (this.attachmentPoints != null) {
+            ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
+            apList.addAll(this.attachmentPoints);
+            int index = apList.indexOf(ap);
+            if (index > 0) {
+                apList.remove(index);
+                this.attachmentPoints = apList;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean deleteAttachmentPoint(long sw) {
+        boolean deletedFlag;
+        ArrayList<AttachmentPoint> apList;
+        ArrayList<AttachmentPoint> modifiedList;
+
+        // Delete the APs on switch sw in oldAPs.
+        deletedFlag = false;
+        apList = new ArrayList<AttachmentPoint>();
+        if (this.oldAPs != null)
+            apList.addAll(this.oldAPs);
+        modifiedList = new ArrayList<AttachmentPoint>();
+
+        for(AttachmentPoint ap: apList) {
+            if (ap.getSw() == sw) {
+                deletedFlag = true;
+            } else {
+                modifiedList.add(ap);
+            }
+        }
+
+        if (deletedFlag) {
+            this.oldAPs = modifiedList;
+        }
+
+        // Delete the APs on switch sw in attachmentPoints.
+        deletedFlag = false;
+        apList = new ArrayList<AttachmentPoint>();
+        if (this.attachmentPoints != null)
+            apList.addAll(this.attachmentPoints);
+        modifiedList = new ArrayList<AttachmentPoint>();
+
+        for(AttachmentPoint ap: apList) {
+            if (ap.getSw() == sw) {
+                deletedFlag = true;
+            } else {
+                modifiedList.add(ap);
+            }
+        }
+
+        if (deletedFlag) {
+            this.attachmentPoints = modifiedList;
+            return true;
+        }
+
+        return false;
+    }
+
+
+    @Override
+    public SwitchPort[] getAttachmentPoints() {
+        return getAttachmentPoints(false);
+    }
+
+    @Override
+    public SwitchPort[] getAttachmentPoints(boolean includeError) {
+        List<SwitchPort> sp = new ArrayList<SwitchPort>();
+        SwitchPort [] returnSwitchPorts = new SwitchPort[] {};
+        if (attachmentPoints == null) return returnSwitchPorts;
+        if (attachmentPoints.isEmpty()) return returnSwitchPorts;
+
+
+        // copy ap list.
+        List<AttachmentPoint> apList;
+        apList = new ArrayList<AttachmentPoint>();
+        if (attachmentPoints != null) apList.addAll(attachmentPoints);
+        // get AP map.
+        Map<Long, AttachmentPoint> apMap = getAPMap(apList);
+
+        if (apMap != null) {
+            for(AttachmentPoint ap: apMap.values()) {
+                SwitchPort swport = new SwitchPort(ap.getSw(),
+                                                   ap.getPort());
+                    sp.add(swport);
+            }
+        }
+
+        if (!includeError)
+            return sp.toArray(new SwitchPort[sp.size()]);
+
+        List<AttachmentPoint> oldAPList;
+        oldAPList = new ArrayList<AttachmentPoint>();
+
+        if (oldAPs != null) oldAPList.addAll(oldAPs);
+
+        if (removeExpiredAttachmentPoints(oldAPList))
+            this.oldAPs = oldAPList;
+
+        List<AttachmentPoint> dupList;
+        dupList = this.getDuplicateAttachmentPoints(oldAPList, apMap);
+        if (dupList != null) {
+            for(AttachmentPoint ap: dupList) {
+                SwitchPort swport = new SwitchPort(ap.getSw(),
+                                                   ap.getPort(),
+                                                   ErrorStatus.DUPLICATE_DEVICE);
+                    sp.add(swport);
+            }
+        }
+        return sp.toArray(new SwitchPort[sp.size()]);
+    }
+
+    // *******
+    // IDevice
+    // *******
+
+    @Override
+    public Long getDeviceKey() {
+        return deviceKey;
+    }
+
+    @Override
+    public long getMACAddress() {
+        // we assume only one MAC per device for now.
+        return entities[0].getMacAddress();
+    }
+
+    @Override
+    public String getMACAddressString() {
+        return macAddressString;
+    }
+
+    @Override
+    public Short[] getVlanId() {
+        if (entities.length == 1) {
+            if (entities[0].getVlan() != null) {
+                return new Short[]{ entities[0].getVlan() };
+            } else {
+                return new Short[] { Short.valueOf((short)-1) };
+            }
+        }
+
+        TreeSet<Short> vals = new TreeSet<Short>();
+        for (Entity e : entities) {
+            if (e.getVlan() == null)
+                vals.add((short)-1);
+            else
+                vals.add(e.getVlan());
+        }
+        return vals.toArray(new Short[vals.size()]);
+    }
+
+    static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4);
+
+    @Override
+    public Integer[] getIPv4Addresses() {
+        // XXX - TODO we can cache this result.  Let's find out if this
+        // is really a performance bottleneck first though.
+
+        TreeSet<Integer> vals = new TreeSet<Integer>();
+        for (Entity e : entities) {
+            if (e.getIpv4Address() == null) continue;
+
+            // We have an IP address only if among the devices within the class
+            // we have the most recent entity with that IP.
+            boolean validIP = true;
+            Iterator<Device> devices =
+                    deviceManager.queryClassByEntity(entityClass, ipv4Fields, e);
+            while (devices.hasNext()) {
+                Device d = devices.next();
+                if (deviceKey.equals(d.getDeviceKey())) 
+                    continue;
+                for (Entity se : d.entities) {
+                    if (se.getIpv4Address() != null &&
+                            se.getIpv4Address().equals(e.getIpv4Address()) &&
+                            se.getLastSeenTimestamp() != null &&
+                            0 < se.getLastSeenTimestamp().
+                            compareTo(e.getLastSeenTimestamp())) {
+                        validIP = false;
+                        break;
+                    }
+                }
+                if (!validIP)
+                    break;
+            }
+
+            if (validIP)
+                vals.add(e.getIpv4Address());
+        }
+
+        return vals.toArray(new Integer[vals.size()]);
+    }
+
+    @Override
+    public Short[] getSwitchPortVlanIds(SwitchPort swp) {
+        TreeSet<Short> vals = new TreeSet<Short>();
+        for (Entity e : entities) {
+            if (e.switchDPID == swp.getSwitchDPID() 
+                    && e.switchPort == swp.getPort()) {
+                if (e.getVlan() == null)
+                    vals.add(Ethernet.VLAN_UNTAGGED);
+                else
+                    vals.add(e.getVlan());
+            }
+        }
+        return vals.toArray(new Short[vals.size()]);
+    }
+
+    @Override
+    public Date getLastSeen() {
+        Date d = null;
+        for (int i = 0; i < entities.length; i++) {
+            if (d == null ||
+                    entities[i].getLastSeenTimestamp().compareTo(d) > 0)
+                d = entities[i].getLastSeenTimestamp();
+        }
+        return d;
+    }
+
+    // ***************
+    // Getters/Setters
+    // ***************
+
+    @Override
+    public IEntityClass getEntityClass() {
+        return entityClass;
+    }
+
+    public Entity[] getEntities() {
+        return entities;
+    }
+
+    // ***************
+    // Utility Methods
+    // ***************
+
+    /**
+     * Check whether the device contains the specified entity
+     * @param entity the entity to search for
+     * @return the index of the entity, or <0 if not found
+     */
+    protected int entityIndex(Entity entity) {
+        return Arrays.binarySearch(entities, entity);
+    }
+
+    // ******
+    // Object
+    // ******
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(entities);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        Device other = (Device) obj;
+        if (!deviceKey.equals(other.deviceKey)) return false;
+        if (!Arrays.equals(entities, other.entities)) return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Device [deviceKey=");
+        builder.append(deviceKey);
+        builder.append(", entityClass=");
+        builder.append(entityClass.getName());
+        builder.append(", MAC=");
+        builder.append(macAddressString);
+        builder.append(", IPs=[");
+        boolean isFirst = true;
+        for (Integer ip: getIPv4Addresses()) {
+            if (!isFirst)
+                builder.append(", ");
+            isFirst = false;
+            builder.append(IPv4.fromIPv4Address(ip));
+        }
+        builder.append("], APs=");
+        builder.append(Arrays.toString(getAttachmentPoints(true)));
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java
new file mode 100644
index 0000000..0d8ea75
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java
@@ -0,0 +1,116 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+
+/**
+ * An index that maps key fields of an entity to device keys
+ */
+public abstract class DeviceIndex {
+    /**
+     * The key fields for this index
+     */
+    protected EnumSet<DeviceField> keyFields;
+
+    /**
+     * Construct a new device index using the provided key fields
+     * @param keyFields the key fields to use
+     */
+    public DeviceIndex(EnumSet<DeviceField> keyFields) {
+        super();
+        this.keyFields = keyFields;
+    }
+
+    /**
+     * Find all device keys in the index that match the given entity
+     * on all the key fields for this index
+     * @param e the entity to search for
+     * @return an iterator over device keys
+     */
+    public abstract Iterator<Long> queryByEntity(Entity entity);
+    
+    /**
+     * Get all device keys in the index.  If certain devices exist
+     * multiple times, then these devices may be returned multiple times
+     * @return an iterator over device keys
+     */
+    public abstract Iterator<Long> getAll();
+
+    /**
+     * Attempt to update an index with the entities in the provided
+     * {@link Device}.  If the update fails because of a concurrent update,
+     * will return false.
+     * @param device the device to update
+     * @param deviceKey the device key for the device
+     * @return true if the update succeeded, false otherwise.
+     */
+    public abstract boolean updateIndex(Device device, Long deviceKey);
+
+    /**
+     * Add a mapping from the given entity to the given device key.  This
+     * update will not fail because of a concurrent update 
+     * @param device the device to update
+     * @param deviceKey the device key for the device
+     */
+    public abstract void updateIndex(Entity entity, Long deviceKey);
+
+    /**
+     * Remove the entry for the given entity
+     * @param entity the entity to remove
+     */
+    public abstract void removeEntity(Entity entity);
+
+    /**
+     * Remove the given device key from the index for the given entity
+     * @param entity the entity to search for
+     * @param deviceKey the key to remove
+     */
+    public abstract void removeEntity(Entity entity, Long deviceKey);
+    
+    /**
+     * Remove the give device from the index only if this the collection
+     * of others does not contain an entity that is identical on all the key
+     * fields for this index.
+     * @param entity the entity to search for
+     * @param deviceKey the key to remove
+     * @param others the others against which to check
+     */
+    public void removeEntityIfNeeded(Entity entity, Long deviceKey,
+                                     Collection<Entity> others) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        for (Entity o : others) {
+            IndexedEntity oio = new IndexedEntity(keyFields, o);
+            if (oio.equals(ie)) return;
+        }
+
+        Iterator<Long> keyiter = this.queryByEntity(entity);
+        while (keyiter.hasNext()) {
+                Long key = keyiter.next();
+                if (key.equals(deviceKey)) {
+                    removeEntity(entity, deviceKey);
+                    break;
+                }
+        }
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndexInterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndexInterator.java
new file mode 100644
index 0000000..2015bbe
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndexInterator.java
@@ -0,0 +1,59 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.Iterator;
+
+/**
+ * An iterator for handling device index queries
+ */
+public class DeviceIndexInterator implements Iterator<Device> {
+    private DeviceManagerImpl deviceManager;
+    private Iterator<Long> subIterator;
+
+    /**
+     * Construct a new device index iterator referring to a device manager
+     * instance and an iterator over device keys
+     * 
+     * @param deviceManager the device manager
+     * @param subIterator an iterator over device keys
+     */
+    public DeviceIndexInterator(DeviceManagerImpl deviceManager,
+                                Iterator<Long> subIterator) {
+        super();
+        this.deviceManager = deviceManager;
+        this.subIterator = subIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return subIterator.hasNext();
+    }
+
+    @Override
+    public Device next() {
+        Long next = subIterator.next();
+        return deviceManager.deviceMap.get(next);
+    }
+
+    @Override
+    public void remove() {
+        subIterator.remove();
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
new file mode 100644
index 0000000..2cbea66
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
@@ -0,0 +1,117 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.util.FilterIterator;
+
+/**
+ * An iterator for handling device queries
+ */
+public class DeviceIterator extends FilterIterator<Device> {
+    private IEntityClass[] entityClasses;
+    
+    private Long macAddress;
+    private Short vlan;
+    private Integer ipv4Address; 
+    private Long switchDPID;
+    private Integer switchPort;
+    
+    /**
+     * Construct a new device iterator over the key fields
+     * @param subIterator an iterator over the full data structure to scan
+     * @param entityClasses the entity classes to search for
+     * @param macAddress The MAC address
+     * @param vlan the VLAN
+     * @param ipv4Address the ipv4 address
+     * @param switchDPID the switch DPID
+     * @param switchPort the switch port
+     */
+    public DeviceIterator(Iterator<Device> subIterator, 
+                          IEntityClass[] entityClasses,
+                          Long macAddress,
+                          Short vlan, 
+                          Integer ipv4Address, 
+                          Long switchDPID,
+                          Integer switchPort) {
+        super(subIterator);
+        this.entityClasses = entityClasses;
+        this.subIterator = subIterator;
+        this.macAddress = macAddress;
+        this.vlan = vlan;
+        this.ipv4Address = ipv4Address;
+        this.switchDPID = switchDPID;
+        this.switchPort = switchPort;
+    }
+
+    @Override
+    protected boolean matches(Device value) {
+        boolean match;
+        if (entityClasses != null) {
+            IEntityClass clazz = value.getEntityClass();
+            if (clazz == null) return false;
+
+            match = false;
+            for (IEntityClass entityClass : entityClasses) {
+                if (clazz.equals(entityClass)) {
+                    match = true;
+                    break;
+                }
+            }
+            if (!match) return false;                
+        }
+        if (macAddress != null) {
+            if (macAddress.longValue() != value.getMACAddress())
+                return false;
+        }
+        if (vlan != null) {
+            Short[] vlans = value.getVlanId();
+            if (Arrays.binarySearch(vlans, vlan) < 0) 
+                return false;
+        }
+        if (ipv4Address != null) {
+            Integer[] ipv4Addresses = value.getIPv4Addresses();
+            if (Arrays.binarySearch(ipv4Addresses, ipv4Address) < 0) 
+                return false;
+        }
+        if (switchDPID != null || switchPort != null) {
+            SwitchPort[] sps = value.getAttachmentPoints();
+            if (sps == null) return false;
+            
+            match = false;
+            for (SwitchPort sp : sps) {
+                if (switchDPID != null) {
+                    if (switchDPID.longValue() != sp.getSwitchDPID())
+                        return false;
+                }
+                if (switchPort != null) {
+                    if (switchPort.intValue() != sp.getPort())
+                        return false;
+                }
+                match = true;
+                break;
+            }
+            if (!match) return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
new file mode 100755
index 0000000..feccdc4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
@@ -0,0 +1,1717 @@
+/**
+ *    Copyright 2011,2012 Big Switch Networks, Inc.
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IInfoProvider;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.SingletonTask;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassListener;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.web.DeviceRoutable;
+import net.floodlightcontroller.flowcache.IFlowReconcileListener;
+import net.floodlightcontroller.flowcache.IFlowReconcileService;
+import net.floodlightcontroller.flowcache.OFMatchReconcile;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
+import net.floodlightcontroller.packet.ARP;
+import net.floodlightcontroller.packet.DHCP;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.ITopologyListener;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.util.MultiIterator;
+import static net.floodlightcontroller.devicemanager.internal.
+DeviceManagerImpl.DeviceUpdate.Change.*;
+
+import org.openflow.protocol.OFMatchWithSwDpid;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DeviceManager creates Devices based upon MAC addresses seen in the network.
+ * It tracks any network addresses mapped to the Device, and its location
+ * within the network.
+ * @author readams
+ */
+public class DeviceManagerImpl implements
+IDeviceService, IOFMessageListener, ITopologyListener,
+IFloodlightModule, IEntityClassListener,
+IFlowReconcileListener, IInfoProvider, IHAListener {
+    protected static Logger logger =
+            LoggerFactory.getLogger(DeviceManagerImpl.class);
+
+    protected IFloodlightProviderService floodlightProvider;
+    protected ITopologyService topology;
+    protected IStorageSourceService storageSource;
+    protected IRestApiService restApi;
+    protected IThreadPoolService threadPool;
+    protected IFlowReconcileService flowReconcileMgr;
+
+    /**
+     * Time in milliseconds before entities will expire
+     */
+    protected static final int ENTITY_TIMEOUT = 60*60*1000;
+
+    /**
+     * Time in seconds between cleaning up old entities/devices
+     */
+    protected static final int ENTITY_CLEANUP_INTERVAL = 60*60;
+
+    /**
+     * This is the master device map that maps device IDs to {@link Device}
+     * objects.
+     */
+    protected ConcurrentHashMap<Long, Device> deviceMap;
+
+    /**
+     * Counter used to generate device keys
+     */
+    protected long deviceKeyCounter = 0;
+
+    /**
+     * Lock for incrementing the device key counter
+     */
+    protected Object deviceKeyLock = new Object();
+
+    /**
+     * This is the primary entity index that contains all entities
+     */
+    protected DeviceUniqueIndex primaryIndex;
+
+    /**
+     * This stores secondary indices over the fields in the devices
+     */
+    protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
+
+    /**
+     * This map contains state for each of the {@ref IEntityClass}
+     * that exist
+     */
+    protected ConcurrentHashMap<String, ClassState> classStateMap;
+
+    /**
+     * This is the list of indices we want on a per-class basis
+     */
+    protected Set<EnumSet<DeviceField>> perClassIndices;
+
+    /**
+     * The entity classifier currently in use
+     */
+    protected IEntityClassifierService entityClassifier;
+
+    /**
+     * Used to cache state about specific entity classes
+     */
+    protected class ClassState {
+
+        /**
+         * The class index
+         */
+        protected DeviceUniqueIndex classIndex;
+
+        /**
+         * This stores secondary indices over the fields in the device for the
+         * class
+         */
+        protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
+
+        /**
+         * Allocate a new {@link ClassState} object for the class
+         * @param clazz the class to use for the state
+         */
+        public ClassState(IEntityClass clazz) {
+            EnumSet<DeviceField> keyFields = clazz.getKeyFields();
+            EnumSet<DeviceField> primaryKeyFields =
+                    entityClassifier.getKeyFields();
+            boolean keyFieldsMatchPrimary =
+                    primaryKeyFields.equals(keyFields);
+
+            if (!keyFieldsMatchPrimary)
+                classIndex = new DeviceUniqueIndex(keyFields);
+
+            secondaryIndexMap =
+                    new HashMap<EnumSet<DeviceField>, DeviceIndex>();
+            for (EnumSet<DeviceField> fields : perClassIndices) {
+                secondaryIndexMap.put(fields,
+                                      new DeviceMultiIndex(fields));
+            }
+        }
+    }
+
+    /**
+     * Device manager event listeners
+     */
+    protected Set<IDeviceListener> deviceListeners;
+
+    /**
+     * A device update event to be dispatched
+     */
+    protected static class DeviceUpdate {
+        public enum Change {
+            ADD, DELETE, CHANGE;
+        }
+
+        /**
+         * The affected device
+         */
+        protected IDevice device;
+
+        /**
+         * The change that was made
+         */
+        protected Change change;
+
+        /**
+         * If not added, then this is the list of fields changed
+         */
+        protected EnumSet<DeviceField> fieldsChanged;
+
+        public DeviceUpdate(IDevice device, Change change,
+                            EnumSet<DeviceField> fieldsChanged) {
+            super();
+            this.device = device;
+            this.change = change;
+            this.fieldsChanged = fieldsChanged;
+        }
+
+        @Override
+        public String toString() {
+            String devIdStr = device.getEntityClass().getName() + "::" +
+                    device.getMACAddressString();
+            return "DeviceUpdate [device=" + devIdStr + ", change=" + change
+                   + ", fieldsChanged=" + fieldsChanged + "]";
+        }
+        
+    }
+
+    /**
+     * AttachmentPointComparator
+     * 
+     * Compares two attachment points and returns the latest one.
+     * It is assumed that the two attachment points are in the same
+     * L2 domain.
+     * 
+     * @author srini
+     */
+    protected class AttachmentPointComparator
+    implements Comparator<AttachmentPoint> {
+        public AttachmentPointComparator() {
+            super();
+        }
+
+        @Override
+        public int compare(AttachmentPoint oldAP, AttachmentPoint newAP) {
+
+            //First compare based on L2 domain ID; 
+            long oldSw = oldAP.getSw();
+            short oldPort = oldAP.getPort();
+            long oldDomain = topology.getL2DomainId(oldSw);
+            boolean oldBD = topology.isBroadcastDomainPort(oldSw, oldPort);
+
+            long newSw = newAP.getSw();
+            short newPort = newAP.getPort();
+            long newDomain = topology.getL2DomainId(newSw);
+            boolean newBD = topology.isBroadcastDomainPort(newSw, newPort);
+
+            if (oldDomain < newDomain) return -1;
+            else if (oldDomain > newDomain) return 1;
+
+            // We expect that the last seen of the new AP is higher than
+            // old AP, if it is not, just reverse and send the negative
+            // of the result.
+            if (oldAP.getActiveSince() > newAP.getActiveSince())
+                return -compare(newAP, oldAP);
+
+            long activeOffset = 0;
+            if (!topology.isConsistent(oldSw, oldPort, newSw, newPort)) {
+                if (!newBD && oldBD) {
+                    return -1;
+                }
+                if (newBD && oldBD) {
+                    activeOffset = AttachmentPoint.EXTERNAL_TO_EXTERNAL_TIMEOUT;
+                }
+                else if (newBD && !oldBD){
+                    activeOffset = AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT;
+                }
+
+            } else {
+                // The attachment point is consistent.
+                activeOffset = AttachmentPoint.CONSISTENT_TIMEOUT;
+            }
+
+
+            if ((newAP.getActiveSince() > oldAP.getLastSeen() + activeOffset) ||
+                    (newAP.getLastSeen() > oldAP.getLastSeen() +
+                            AttachmentPoint.INACTIVITY_INTERVAL)) {
+                return -1;
+            }
+            return 1;
+        }
+    }
+    /**
+     * Comparator for sorting by cluster ID
+     */
+    public AttachmentPointComparator apComparator;
+
+    /**
+     * Switch ports where attachment points shouldn't be learned
+     */
+    private Set<SwitchPort> suppressAPs;
+
+    /**
+     * Periodic task to clean up expired entities
+     */
+    public SingletonTask entityCleanupTask;
+
+    // *********************
+    // IDeviceManagerService
+    // *********************
+
+    @Override
+    public IDevice getDevice(Long deviceKey) {
+        return deviceMap.get(deviceKey);
+    }
+
+    @Override
+    public IDevice findDevice(long macAddress, Short vlan,
+                              Integer ipv4Address, Long switchDPID,
+                              Integer switchPort)
+                              throws IllegalArgumentException {
+        if (vlan != null && vlan.shortValue() <= 0)
+            vlan = null;
+        if (ipv4Address != null && ipv4Address == 0)
+            ipv4Address = null;
+        Entity e = new Entity(macAddress, vlan, ipv4Address, switchDPID,
+                              switchPort, null);
+        if (!allKeyFieldsPresent(e, entityClassifier.getKeyFields())) {
+            throw new IllegalArgumentException("Not all key fields specified."
+                      + " Required fields: " + entityClassifier.getKeyFields());
+        }
+        return findDeviceByEntity(e);
+    }
+
+    @Override
+    public IDevice findDestDevice(IDevice source, long macAddress,
+                                  Short vlan, Integer ipv4Address) 
+                                  throws IllegalArgumentException {
+        if (vlan != null && vlan.shortValue() <= 0)
+            vlan = null;
+        if (ipv4Address != null && ipv4Address == 0)
+            ipv4Address = null;
+        Entity e = new Entity(macAddress, vlan, ipv4Address,
+                              null, null, null);
+        if (source == null || 
+                !allKeyFieldsPresent(e, source.getEntityClass().getKeyFields())) {
+            throw new IllegalArgumentException("Not all key fields and/or "
+                    + " no source device specified. Required fields: " + 
+                    entityClassifier.getKeyFields());
+        }
+        return findDestByEntity(source, e);
+    }
+
+    @Override
+    public Collection<? extends IDevice> getAllDevices() {
+        return Collections.unmodifiableCollection(deviceMap.values());
+    }
+
+    @Override
+    public void addIndex(boolean perClass,
+                         EnumSet<DeviceField> keyFields) {
+        if (perClass) {
+            perClassIndices.add(keyFields);
+        } else {
+            secondaryIndexMap.put(keyFields,
+                                  new DeviceMultiIndex(keyFields));
+        }
+    }
+
+    @Override
+    public Iterator<? extends IDevice> queryDevices(Long macAddress,
+                                                    Short vlan,
+                                                    Integer ipv4Address,
+                                                    Long switchDPID,
+                                                    Integer switchPort) {
+        DeviceIndex index = null;
+        if (secondaryIndexMap.size() > 0) {
+            EnumSet<DeviceField> keys =
+                    getEntityKeys(macAddress, vlan, ipv4Address,
+                                  switchDPID, switchPort);
+            index = secondaryIndexMap.get(keys);
+        }
+
+        Iterator<Device> deviceIterator = null;
+        if (index == null) {
+            // Do a full table scan
+            deviceIterator = deviceMap.values().iterator();
+        } else {
+            // index lookup
+            Entity entity = new Entity((macAddress == null ? 0 : macAddress),
+                                       vlan,
+                                       ipv4Address,
+                                       switchDPID,
+                                       switchPort,
+                                       null);
+            deviceIterator =
+                    new DeviceIndexInterator(this, index.queryByEntity(entity));
+        }
+
+        DeviceIterator di =
+                new DeviceIterator(deviceIterator,
+                                   null,
+                                   macAddress,
+                                   vlan,
+                                   ipv4Address,
+                                   switchDPID,
+                                   switchPort);
+        return di;
+    }
+
+    @Override
+    public Iterator<? extends IDevice> queryClassDevices(IDevice reference,
+                                                         Long macAddress,
+                                                         Short vlan,
+                                                         Integer ipv4Address,
+                                                         Long switchDPID,
+                                                         Integer switchPort) {
+        IEntityClass entityClass = reference.getEntityClass();
+        ArrayList<Iterator<Device>> iterators =
+                new ArrayList<Iterator<Device>>();
+        ClassState classState = getClassState(entityClass);
+        
+        DeviceIndex index = null;
+        if (classState.secondaryIndexMap.size() > 0) {
+            EnumSet<DeviceField> keys =
+                    getEntityKeys(macAddress, vlan, ipv4Address,
+                                  switchDPID, switchPort);
+            index = classState.secondaryIndexMap.get(keys);
+        }
+        
+        Iterator<Device> iter;
+        if (index == null) {
+            index = classState.classIndex;
+            if (index == null) {
+                // scan all devices
+                return new DeviceIterator(deviceMap.values().iterator(),
+                                          new IEntityClass[] { entityClass },
+                                          macAddress, vlan, ipv4Address,
+                                          switchDPID, switchPort);
+            } else {
+                // scan the entire class
+                iter = new DeviceIndexInterator(this, index.getAll());
+            }
+        } else {
+            // index lookup
+            Entity entity =
+                    new Entity((macAddress == null ? 0 : macAddress),
+                               vlan,
+                               ipv4Address,
+                               switchDPID,
+                               switchPort,
+                               null);
+            iter = new DeviceIndexInterator(this,
+                                            index.queryByEntity(entity));
+        }
+        iterators.add(iter);
+        
+        return new MultiIterator<Device>(iterators.iterator());
+    }
+    
+    protected Iterator<Device> getDeviceIteratorForQuery(Long macAddress,
+                                                        Short vlan,
+                                                        Integer ipv4Address,
+                                                        Long switchDPID,
+                                                        Integer switchPort) {
+        DeviceIndex index = null;
+        if (secondaryIndexMap.size() > 0) {
+            EnumSet<DeviceField> keys =
+                getEntityKeys(macAddress, vlan, ipv4Address,
+                            switchDPID, switchPort);
+            index = secondaryIndexMap.get(keys);
+        }
+
+        Iterator<Device> deviceIterator = null;
+        if (index == null) {
+            // Do a full table scan
+            deviceIterator = deviceMap.values().iterator();
+        } else {
+            // index lookup
+            Entity entity = new Entity((macAddress == null ? 0 : macAddress),
+                                vlan,
+                                ipv4Address,
+                                switchDPID,
+                                switchPort,
+                                null);
+            deviceIterator =
+                new DeviceIndexInterator(this, index.queryByEntity(entity));
+        }
+
+        DeviceIterator di =
+            new DeviceIterator(deviceIterator,
+                                null,
+                                macAddress,
+                                vlan,
+                                ipv4Address,
+                                switchDPID,
+                                switchPort);
+        return di;
+    }
+
+    @Override
+    public void addListener(IDeviceListener listener) {
+        deviceListeners.add(listener);
+    }
+
+    // *************
+    // IInfoProvider
+    // *************
+
+    @Override
+    public Map<String, Object> getInfo(String type) {
+        if (!"summary".equals(type))
+            return null;
+
+        Map<String, Object> info = new HashMap<String, Object>();
+        info.put("# hosts", deviceMap.size());
+        return info;
+    }
+
+    // ******************
+    // IOFMessageListener
+    // ******************
+
+    @Override
+    public String getName() {
+        return "devicemanager";
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        return ((type == OFType.PACKET_IN || type == OFType.FLOW_MOD)
+                && name.equals("topology"));
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return false;
+    }
+
+    @Override
+    public Command receive(IOFSwitch sw, OFMessage msg,
+                           FloodlightContext cntx) {
+        switch (msg.getType()) {
+            case PACKET_IN:
+                return this.processPacketInMessage(sw,
+                                                   (OFPacketIn) msg, cntx);
+            default:
+                break;
+        }
+        return Command.CONTINUE;
+    }
+
+    // ***************
+    // IFlowReconcileListener
+    // ***************
+    @Override
+    public Command reconcileFlows(ArrayList<OFMatchReconcile> ofmRcList) {
+        ListIterator<OFMatchReconcile> iter = ofmRcList.listIterator();
+        while (iter.hasNext()) {
+            OFMatchReconcile ofm = iter.next();
+            
+            // Remove the STOPPed flow.
+            if (Command.STOP == reconcileFlow(ofm)) {
+                iter.remove();
+            }
+        }
+        
+        if (ofmRcList.size() > 0) {
+            return Command.CONTINUE;
+        } else {
+            return Command.STOP;
+        }
+    }
+
+    protected Command reconcileFlow(OFMatchReconcile ofm) {
+        // Extract source entity information
+        Entity srcEntity =
+                getEntityFromFlowMod(ofm.ofmWithSwDpid, true);
+        if (srcEntity == null)
+            return Command.STOP;
+
+        // Find the device by source entity
+        Device srcDevice = findDeviceByEntity(srcEntity);
+        if (srcDevice == null)
+            return Command.STOP;
+
+        // Store the source device in the context
+        fcStore.put(ofm.cntx, CONTEXT_SRC_DEVICE, srcDevice);
+
+        // Find the device matching the destination from the entity
+        // classes of the source.
+        Entity dstEntity = getEntityFromFlowMod(ofm.ofmWithSwDpid, false);
+        Device dstDevice = null;
+        if (dstEntity != null) {
+            dstDevice = findDestByEntity(srcDevice, dstEntity);
+            if (dstDevice != null)
+                fcStore.put(ofm.cntx, CONTEXT_DST_DEVICE, dstDevice);
+        }
+        if (logger.isTraceEnabled()) {
+            logger.trace("Reconciling flow: match={}, srcEntity={}, srcDev={}, " 
+                         + "dstEntity={}, dstDev={}",
+                         new Object[] {ofm.ofmWithSwDpid.getOfMatch(),
+                                       srcEntity, srcDevice, 
+                                       dstEntity, dstDevice } );
+        }
+        return Command.CONTINUE;
+    }
+
+    // *****************
+    // IFloodlightModule
+    // *****************
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l =
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IDeviceService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+    getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m =
+        new HashMap<Class<? extends IFloodlightService>,
+        IFloodlightService>();
+        // We are the class that implements the service
+        m.put(IDeviceService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l =
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        l.add(IStorageSourceService.class);
+        l.add(ITopologyService.class);
+        l.add(IRestApiService.class);
+        l.add(IThreadPoolService.class);
+        l.add(IFlowReconcileService.class);
+        l.add(IEntityClassifierService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext fmc) {
+        this.perClassIndices =
+                new HashSet<EnumSet<DeviceField>>();
+        addIndex(true, EnumSet.of(DeviceField.IPV4));
+
+        this.deviceListeners = new HashSet<IDeviceListener>();
+        this.suppressAPs =
+                Collections.synchronizedSet(new HashSet<SwitchPort>());
+
+        this.floodlightProvider =
+                fmc.getServiceImpl(IFloodlightProviderService.class);
+        this.storageSource =
+                fmc.getServiceImpl(IStorageSourceService.class);
+        this.topology =
+                fmc.getServiceImpl(ITopologyService.class);
+        this.restApi = fmc.getServiceImpl(IRestApiService.class);
+        this.threadPool = fmc.getServiceImpl(IThreadPoolService.class);
+        this.flowReconcileMgr = fmc.getServiceImpl(IFlowReconcileService.class);
+        this.entityClassifier = fmc.getServiceImpl(IEntityClassifierService.class);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext fmc) {
+        primaryIndex = new DeviceUniqueIndex(entityClassifier.getKeyFields());
+        secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>();
+
+        deviceMap = new ConcurrentHashMap<Long, Device>();
+        classStateMap =
+                new ConcurrentHashMap<String, ClassState>();
+        apComparator = new AttachmentPointComparator();
+
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+        floodlightProvider.addHAListener(this);
+        if (topology != null)
+            topology.addListener(this);
+        flowReconcileMgr.addFlowReconcileListener(this);
+        entityClassifier.addListener(this);
+
+        Runnable ecr = new Runnable() {
+            @Override
+            public void run() {
+                cleanupEntities();
+                entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL,
+                                             TimeUnit.SECONDS);
+            }
+        };
+        ScheduledExecutorService ses = threadPool.getScheduledExecutor();
+        entityCleanupTask = new SingletonTask(ses, ecr);
+        entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL,
+                                     TimeUnit.SECONDS);
+
+        if (restApi != null) {
+            restApi.addRestletRoutable(new DeviceRoutable());
+        } else {
+            logger.debug("Could not instantiate REST API");
+        }
+    }
+
+    // ***************
+    // IHAListener
+    // ***************
+
+    @Override
+    public void roleChanged(Role oldRole, Role newRole) {
+        switch(newRole) {
+            case SLAVE:
+                logger.debug("Resetting device state because of role change");
+                startUp(null);
+                break;
+            default:
+                break;
+        }
+    }
+
+    @Override
+    public void controllerNodeIPsChanged(
+                                         Map<String, String> curControllerNodeIPs,
+                                         Map<String, String> addedControllerNodeIPs,
+                                         Map<String, String> removedControllerNodeIPs) {
+        // no-op
+    }
+
+    // ****************
+    // Internal methods
+    // ****************
+
+    protected Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi,
+                                             FloodlightContext cntx) {
+        Ethernet eth =
+                IFloodlightProviderService.bcStore.
+                get(cntx,IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+
+        // Extract source entity information
+        Entity srcEntity =
+                getSourceEntityFromPacket(eth, sw.getId(), pi.getInPort());
+        if (srcEntity == null)
+            return Command.STOP;
+
+        // Learn/lookup device information
+        Device srcDevice = learnDeviceByEntity(srcEntity);
+        if (srcDevice == null)
+            return Command.STOP;
+
+        // Store the source device in the context
+        fcStore.put(cntx, CONTEXT_SRC_DEVICE, srcDevice);
+
+        // Find the device matching the destination from the entity
+        // classes of the source.
+        Entity dstEntity = getDestEntityFromPacket(eth);
+        Device dstDevice = null;
+        if (dstEntity != null) {
+            dstDevice =
+                    findDestByEntity(srcDevice, dstEntity);
+            if (dstDevice != null)
+                fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice);
+        }
+
+       if (logger.isTraceEnabled()) {
+           logger.trace("Received PI: {} on switch {}, port {} *** eth={}" +
+                        " *** srcDev={} *** dstDev={} *** ",
+                        new Object[] { pi, sw.getStringId(), pi.getInPort(), eth,
+                        srcDevice, dstDevice });
+       }
+        return Command.CONTINUE;
+    }
+
+    /**
+     * Check whether the given attachment point is valid given the current
+     * topology
+     * @param switchDPID the DPID
+     * @param switchPort the port
+     * @return true if it's a valid attachment point
+     */
+    public boolean isValidAttachmentPoint(long switchDPID,
+                                             int switchPort) {
+        if (topology.isAttachmentPointPort(switchDPID,
+                                           (short)switchPort) == false)
+            return false;
+
+        if (suppressAPs.contains(new SwitchPort(switchDPID, switchPort)))
+            return false;
+
+        return true;
+    }
+
+    /**
+     * Get IP address from packet if the packet is either an ARP 
+     * or a DHCP packet
+     * @param eth
+     * @param dlAddr
+     * @return
+     */
+    private int getSrcNwAddr(Ethernet eth, long dlAddr) {
+        if (eth.getPayload() instanceof ARP) {
+            ARP arp = (ARP) eth.getPayload();
+            if ((arp.getProtocolType() == ARP.PROTO_TYPE_IP) &&
+                    (Ethernet.toLong(arp.getSenderHardwareAddress()) == dlAddr)) {
+                return IPv4.toIPv4Address(arp.getSenderProtocolAddress());
+            }
+        } else if (eth.getPayload() instanceof IPv4) {
+            IPv4 ipv4 = (IPv4) eth.getPayload();
+            if (ipv4.getPayload() instanceof UDP) {
+                UDP udp = (UDP)ipv4.getPayload();
+                if (udp.getPayload() instanceof DHCP) {
+                    DHCP dhcp = (DHCP)udp.getPayload();
+                    if (dhcp.getOpCode() == DHCP.OPCODE_REPLY) {
+                        return ipv4.getSourceAddress();
+                    }
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Parse an entity from an {@link Ethernet} packet.
+     * @param eth the packet to parse
+     * @param sw the switch on which the packet arrived
+     * @param pi the original packetin
+     * @return the entity from the packet
+     */
+    protected Entity getSourceEntityFromPacket(Ethernet eth,
+                                             long swdpid,
+                                             int port) {
+        byte[] dlAddrArr = eth.getSourceMACAddress();
+        long dlAddr = Ethernet.toLong(dlAddrArr);
+
+        // Ignore broadcast/multicast source
+        if ((dlAddrArr[0] & 0x1) != 0)
+            return null;
+
+        short vlan = eth.getVlanID();
+        int nwSrc = getSrcNwAddr(eth, dlAddr);
+        return new Entity(dlAddr,
+                          ((vlan >= 0) ? vlan : null),
+                          ((nwSrc != 0) ? nwSrc : null),
+                          swdpid,
+                          port,
+                          new Date());
+    }
+
+    /**
+     * Get a (partial) entity for the destination from the packet.
+     * @param eth
+     * @return
+     */
+    protected Entity getDestEntityFromPacket(Ethernet eth) {
+        byte[] dlAddrArr = eth.getDestinationMACAddress();
+        long dlAddr = Ethernet.toLong(dlAddrArr);
+        short vlan = eth.getVlanID();
+        int nwDst = 0;
+
+        // Ignore broadcast/multicast destination
+        if ((dlAddrArr[0] & 0x1) != 0)
+            return null;
+
+        if (eth.getPayload() instanceof IPv4) {
+            IPv4 ipv4 = (IPv4) eth.getPayload();
+            nwDst = ipv4.getDestinationAddress();
+        }
+
+        return new Entity(dlAddr,
+                          ((vlan >= 0) ? vlan : null),
+                          ((nwDst != 0) ? nwDst : null),
+                          null,
+                          null,
+                          null);
+    }
+
+    /**
+     * Parse an entity from an OFMatchWithSwDpid.
+     * @param ofmWithSwDpid
+     * @return the entity from the packet
+     */
+    private Entity getEntityFromFlowMod(OFMatchWithSwDpid ofmWithSwDpid,
+                boolean isSource) {
+        byte[] dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerSource();
+        int nwSrc = ofmWithSwDpid.getOfMatch().getNetworkSource();
+        if (!isSource) {
+            dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerDestination();
+            nwSrc = ofmWithSwDpid.getOfMatch().getNetworkDestination();
+        }
+
+        long dlAddr = Ethernet.toLong(dlAddrArr);
+
+        // Ignore broadcast/multicast source
+        if ((dlAddrArr[0] & 0x1) != 0)
+            return null;
+
+        Long swDpid = null;
+        Short inPort = null;
+        
+        if (isSource) {
+            swDpid = ofmWithSwDpid.getSwitchDataPathId();
+            inPort = ofmWithSwDpid.getOfMatch().getInputPort();
+        }
+
+        boolean learnap = true;
+        if (swDpid == null ||
+            inPort == null ||
+            !isValidAttachmentPoint(swDpid, inPort)) {
+            // If this is an internal port or we otherwise don't want
+            // to learn on these ports.  In the future, we should
+            // handle this case by labeling flows with something that
+            // will give us the entity class.  For now, we'll do our
+            // best assuming attachment point information isn't used
+            // as a key field.
+            learnap = false;
+        }
+
+        short vlan = ofmWithSwDpid.getOfMatch().getDataLayerVirtualLan();
+        return new Entity(dlAddr,
+                          ((vlan >= 0) ? vlan : null),
+                          ((nwSrc != 0) ? nwSrc : null),
+                          (learnap ? swDpid : null),
+                          (learnap ? (int)inPort : null),
+                          new Date());
+    }
+    /**
+     * Look up a {@link Device} based on the provided {@link Entity}. We first
+     * check the primary index. If we do not find an entry there we classify
+     * the device into its IEntityClass and query the classIndex. 
+     * This implies that all key field of the current IEntityClassifier must 
+     * be present in the entity for the lookup to succeed!
+     * @param entity the entity to search for
+     * @return The {@link Device} object if found
+     */
+    protected Device findDeviceByEntity(Entity entity) {
+        // Look up the fully-qualified entity to see if it already
+        // exists in the primary entity index.
+        Long deviceKey = primaryIndex.findByEntity(entity);
+        IEntityClass entityClass = null;
+
+        if (deviceKey == null) {
+            // If the entity does not exist in the primary entity index,
+            // use the entity classifier for find the classes for the
+            // entity. Look up the entity in the returned class'
+            // class entity index.
+            entityClass = entityClassifier.classifyEntity(entity);
+            if (entityClass == null) {
+                return null;
+            }
+            ClassState classState = getClassState(entityClass);
+
+            if (classState.classIndex != null) {
+                deviceKey =
+                        classState.classIndex.findByEntity(entity);
+            }
+        }
+        if (deviceKey == null) return null;
+        return deviceMap.get(deviceKey);
+    }
+
+    /**
+     * Get a destination device using entity fields that corresponds with
+     * the given source device.  The source device is important since
+     * there could be ambiguity in the destination device without the
+     * attachment point information.
+     * @param source the source device.  The returned destination will be
+     * in the same entity class as the source.
+     * @param dstEntity the entity to look up
+     * @return an {@link Device} or null if no device is found.
+     */
+    protected Device findDestByEntity(IDevice source,
+                                      Entity dstEntity) {
+        
+        // Look  up the fully-qualified entity to see if it 
+        // exists in the primary entity index
+        Long deviceKey = primaryIndex.findByEntity(dstEntity);
+        
+        if (deviceKey == null) {
+            // This could happen because:
+            // 1) no destination known, or a broadcast destination
+            // 2) if we have attachment point key fields since
+            // attachment point information isn't available for
+            // destination devices.
+            // For the second case, we'll need to match up the
+            // destination device with the class of the source
+            // device.
+            ClassState classState = getClassState(source.getEntityClass());
+            if (classState.classIndex == null) {
+                return null;
+            }
+            deviceKey = classState.classIndex.findByEntity(dstEntity);
+        }
+        if (deviceKey == null) return null;
+        return deviceMap.get(deviceKey);
+    }
+    
+
+    /**
+     * Look up a {@link Device} within a particular entity class based on
+     * the provided {@link Entity}.
+     * @param clazz the entity class to search for the entity
+     * @param entity the entity to search for
+     * @return The {@link Device} object if found
+    private Device findDeviceInClassByEntity(IEntityClass clazz,
+                                               Entity entity) {
+        // XXX - TODO
+        throw new UnsupportedOperationException();
+    }
+     */
+
+    /**
+     * Look up a {@link Device} based on the provided {@link Entity}.  Also
+     * learns based on the new entity, and will update existing devices as
+     * required.
+     *
+     * @param entity the {@link Entity}
+     * @return The {@link Device} object if found
+     */
+    protected Device learnDeviceByEntity(Entity entity) {
+        ArrayList<Long> deleteQueue = null;
+        LinkedList<DeviceUpdate> deviceUpdates = null;
+        Device device = null;
+
+        // we may need to restart the learning process if we detect
+        // concurrent modification.  Note that we ensure that at least
+        // one thread should always succeed so we don't get into infinite
+        // starvation loops
+        while (true) {
+            deviceUpdates = null;
+
+            // Look up the fully-qualified entity to see if it already
+            // exists in the primary entity index.
+            Long deviceKey = primaryIndex.findByEntity(entity);
+            IEntityClass entityClass = null;
+
+            if (deviceKey == null) {
+                // If the entity does not exist in the primary entity index,
+                // use the entity classifier for find the classes for the
+                // entity. Look up the entity in the returned class'
+                // class entity index.
+                entityClass = entityClassifier.classifyEntity(entity);
+                if (entityClass == null) {
+                    // could not classify entity. No device
+                    return null;
+                }
+                ClassState classState = getClassState(entityClass);
+
+                if (classState.classIndex != null) {
+                    deviceKey =
+                            classState.classIndex.findByEntity(entity);
+                }
+            }
+            if (deviceKey != null) {
+                // If the primary or secondary index contains the entity
+                // use resulting device key to look up the device in the
+                // device map, and use the referenced Device below.
+                device = deviceMap.get(deviceKey);
+                if (device == null)
+                    throw new IllegalStateException("Corrupted device index");
+            } else {
+                // If the secondary index does not contain the entity,
+                // create a new Device object containing the entity, and
+                // generate a new device ID. However, we first check if 
+                // the entity is allowed (e.g., for spoofing protection)
+                if (!isEntityAllowed(entity, entityClass)) {
+                    logger.info("PacketIn is not allowed {} {}", 
+                                entityClass.getName(), entity);
+                    return null;
+                }
+                synchronized (deviceKeyLock) {
+                    deviceKey = Long.valueOf(deviceKeyCounter++);
+                }
+                device = allocateDevice(deviceKey, entity, entityClass);
+                if (logger.isDebugEnabled()) {
+                    logger.debug("New device created: {} deviceKey={}, entity={}",
+                                 new Object[]{device, deviceKey, entity});
+                }
+
+                // Add the new device to the primary map with a simple put
+                deviceMap.put(deviceKey, device);
+
+                // update indices
+                if (!updateIndices(device, deviceKey)) {
+                    if (deleteQueue == null)
+                        deleteQueue = new ArrayList<Long>();
+                    deleteQueue.add(deviceKey);
+                    continue;
+                }
+
+                updateSecondaryIndices(entity, entityClass, deviceKey);
+
+                // generate new device update
+                deviceUpdates =
+                        updateUpdates(deviceUpdates,
+                                      new DeviceUpdate(device, ADD, null));
+
+                break;
+            }
+
+            if (!isEntityAllowed(entity, device.getEntityClass())) {
+                logger.info("PacketIn is not allowed {} {}", 
+                            device.getEntityClass().getName(), entity);
+                return null;
+            }
+            int entityindex = -1;
+            if ((entityindex = device.entityIndex(entity)) >= 0) {
+                // update timestamp on the found entity
+                Date lastSeen = entity.getLastSeenTimestamp();
+                if (lastSeen == null) lastSeen = new Date();
+                device.entities[entityindex].setLastSeenTimestamp(lastSeen);
+                if (device.entities[entityindex].getSwitchDPID() != null &&
+                        device.entities[entityindex].getSwitchPort() != null) {
+                    long sw = device.entities[entityindex].getSwitchDPID();
+                    short port = device.entities[entityindex].getSwitchPort().shortValue();
+
+                    boolean moved =
+                            device.updateAttachmentPoint(sw,
+                                                         port,
+                                                         lastSeen.getTime());
+
+                    if (moved) {
+                        sendDeviceMovedNotification(device);
+                        if (logger.isTraceEnabled()) {
+                            logger.trace("Device moved: attachment points {}," +
+                                    "entities {}", device.attachmentPoints,
+                                    device.entities);
+                        }
+                    } else {
+                        if (logger.isTraceEnabled()) {
+                            logger.trace("Device attachment point NOT updated: " +
+                                         "attachment points {}," +
+                                         "entities {}", device.attachmentPoints,
+                                         device.entities);
+                        }
+                    }
+                }
+                break;
+            } else {
+                boolean moved = false;
+                Device newDevice = allocateDevice(device, entity);
+                if (entity.getSwitchDPID() != null && entity.getSwitchPort() != null) {
+                    moved = newDevice.updateAttachmentPoint(entity.getSwitchDPID(),
+                                                            entity.getSwitchPort().shortValue(),
+                                                            entity.getLastSeenTimestamp().getTime());
+                }
+
+                // generate updates
+                EnumSet<DeviceField> changedFields =
+                        findChangedFields(device, entity);
+                if (changedFields.size() > 0)
+                    deviceUpdates =
+                    updateUpdates(deviceUpdates,
+                                  new DeviceUpdate(newDevice, CHANGE,
+                                                   changedFields));
+
+                // update the device map with a replace call
+                boolean res = deviceMap.replace(deviceKey, device, newDevice);
+                // If replace returns false, restart the process from the
+                // beginning (this implies another thread concurrently
+                // modified this Device).
+                if (!res)
+                    continue;
+
+                device = newDevice;
+
+                // update indices
+                if (!updateIndices(device, deviceKey)) {
+                    continue;
+                }
+                updateSecondaryIndices(entity,
+                                       device.getEntityClass(),
+                                       deviceKey);
+
+                if (moved) {
+                    sendDeviceMovedNotification(device);
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Device moved: attachment points {}," +
+                                "entities {}", device.attachmentPoints,
+                                device.entities);
+                    }
+                } else {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Device attachment point updated: " +
+                                     "attachment points {}," +
+                                     "entities {}", device.attachmentPoints,
+                                     device.entities);
+                    }
+                }
+                break;
+            }
+        }
+
+        if (deleteQueue != null) {
+            for (Long l : deleteQueue) {
+                Device dev = deviceMap.get(l);
+                this.deleteDevice(dev);
+                
+
+                // generate new device update
+                deviceUpdates =
+                        updateUpdates(deviceUpdates,
+                                      new DeviceUpdate(dev, DELETE, null));
+            }
+        }
+
+        processUpdates(deviceUpdates);
+
+        return device;
+    }
+
+    protected boolean isEntityAllowed(Entity entity, IEntityClass entityClass) {
+        return true;
+    }
+
+    protected EnumSet<DeviceField> findChangedFields(Device device,
+                                                     Entity newEntity) {
+        EnumSet<DeviceField> changedFields =
+                EnumSet.of(DeviceField.IPV4,
+                           DeviceField.VLAN,
+                           DeviceField.SWITCH);
+
+        if (newEntity.getIpv4Address() == null)
+            changedFields.remove(DeviceField.IPV4);
+        if (newEntity.getVlan() == null)
+            changedFields.remove(DeviceField.VLAN);
+        if (newEntity.getSwitchDPID() == null ||
+                newEntity.getSwitchPort() == null)
+            changedFields.remove(DeviceField.SWITCH);
+
+        if (changedFields.size() == 0) return changedFields;
+
+        for (Entity entity : device.getEntities()) {
+            if (newEntity.getIpv4Address() == null ||
+                    (entity.getIpv4Address() != null &&
+                    entity.getIpv4Address().equals(newEntity.getIpv4Address())))
+                changedFields.remove(DeviceField.IPV4);
+            if (newEntity.getVlan() == null ||
+                    (entity.getVlan() != null &&
+                    entity.getVlan().equals(newEntity.getVlan())))
+                changedFields.remove(DeviceField.VLAN);
+            if (newEntity.getSwitchDPID() == null ||
+                    newEntity.getSwitchPort() == null ||
+                    (entity.getSwitchDPID() != null &&
+                    entity.getSwitchPort() != null &&
+                    entity.getSwitchDPID().equals(newEntity.getSwitchDPID()) &&
+                    entity.getSwitchPort().equals(newEntity.getSwitchPort())))
+                changedFields.remove(DeviceField.SWITCH);
+        }
+
+        return changedFields;
+    }
+
+    /**
+     * Send update notifications to listeners
+     * @param updates the updates to process.
+     */
+    protected void processUpdates(Queue<DeviceUpdate> updates) {
+        if (updates == null) return;
+        DeviceUpdate update = null;
+        while (null != (update = updates.poll())) {
+            if (logger.isTraceEnabled()) {
+                logger.trace("Dispatching device update: {}", update);
+            }
+            for (IDeviceListener listener : deviceListeners) {
+                switch (update.change) {
+                    case ADD:
+                        listener.deviceAdded(update.device);
+                        break;
+                    case DELETE:
+                        listener.deviceRemoved(update.device);
+                        break;
+                    case CHANGE:
+                        for (DeviceField field : update.fieldsChanged) {
+                            switch (field) {
+                                case IPV4:
+                                    listener.deviceIPV4AddrChanged(update.device);
+                                    break;
+                                case SWITCH:
+                                case PORT:
+                                    //listener.deviceMoved(update.device);
+                                    break;
+                                case VLAN:
+                                    listener.deviceVlanChanged(update.device);
+                                    break;
+                                default:
+                                    logger.debug("Unknown device field changed {}",
+                                                update.fieldsChanged.toString());
+                                    break;
+                            }
+                        }
+                        break;
+                }
+            }
+        }
+    }
+    
+    /**
+     * Check if the entity e has all the keyFields set. Returns false if not
+     * @param e entity to check 
+     * @param keyFields the key fields to check e against
+     * @return
+     */
+    protected boolean allKeyFieldsPresent(Entity e, EnumSet<DeviceField> keyFields) {
+        for (DeviceField f : keyFields) {
+            switch (f) {
+                case MAC:
+                    // MAC address is always present
+                    break;
+                case IPV4:
+                    if (e.ipv4Address == null) return false;
+                    break;
+                case SWITCH:
+                    if (e.switchDPID == null) return false;
+                    break;
+                case PORT:
+                    if (e.switchPort == null) return false;
+                    break;
+                case VLAN:
+                    // FIXME: vlan==null is ambiguous: it can mean: not present
+                    // or untagged
+                    //if (e.vlan == null) return false;
+                    break;
+                default:
+                    // we should never get here. unless somebody extended 
+                    // DeviceFields
+                    throw new IllegalStateException();
+            }
+        }
+        return true;
+    }
+
+    private LinkedList<DeviceUpdate>
+    updateUpdates(LinkedList<DeviceUpdate> list, DeviceUpdate update) {
+        if (update == null) return list;
+        if (list == null)
+            list = new LinkedList<DeviceUpdate>();
+        list.add(update);
+
+        return list;
+    }
+
+    /**
+     * Get the secondary index for a class.  Will return null if the
+     * secondary index was created concurrently in another thread.
+     * @param clazz the class for the index
+     * @return
+     */
+    private ClassState getClassState(IEntityClass clazz) {
+        ClassState classState = classStateMap.get(clazz.getName());
+        if (classState != null) return classState;
+
+        classState = new ClassState(clazz);
+        ClassState r = classStateMap.putIfAbsent(clazz.getName(), classState);
+        if (r != null) {
+            // concurrent add
+            return r;
+        }
+        return classState;
+    }
+
+    /**
+     * Update both the primary and class indices for the provided device.
+     * If the update fails because of an concurrent update, will return false.
+     * @param device the device to update
+     * @param deviceKey the device key for the device
+     * @return true if the update succeeded, false otherwise.
+     */
+    private boolean updateIndices(Device device, Long deviceKey) {
+        if (!primaryIndex.updateIndex(device, deviceKey)) {
+            return false;
+        }
+        IEntityClass entityClass = device.getEntityClass();
+        ClassState classState = getClassState(entityClass);
+
+        if (classState.classIndex != null) {
+            if (!classState.classIndex.updateIndex(device,
+                                                   deviceKey))
+                return false;
+        }
+    return true;
+    }
+
+    /**
+     * Update the secondary indices for the given entity and associated
+     * entity classes
+     * @param entity the entity to update
+     * @param entityClass the entity class for the entity
+     * @param deviceKey the device key to set up
+     */
+    private void updateSecondaryIndices(Entity entity,
+                                        IEntityClass entityClass,
+                                        Long deviceKey) {
+        for (DeviceIndex index : secondaryIndexMap.values()) {
+            index.updateIndex(entity, deviceKey);
+        }
+        ClassState state = getClassState(entityClass);
+        for (DeviceIndex index : state.secondaryIndexMap.values()) {
+            index.updateIndex(entity, deviceKey);
+        }
+    }
+
+    // *********************
+    // IEntityClassListener
+    // *********************
+    @Override
+    public void entityClassChanged (Set<String> entityClassNames) {
+        /* iterate through the devices, reclassify the devices that belong
+         * to these entity class names
+         */
+        Iterator<Device> diter = deviceMap.values().iterator();
+        while (diter.hasNext()) {
+            Device d = diter.next();
+            if (d.getEntityClass() == null ||
+                entityClassNames.contains(d.getEntityClass().getName()))
+                reclassifyDevice(d);
+        }
+    }
+
+    /**
+     * Clean up expired entities/devices
+     */
+    protected void cleanupEntities () {
+
+        Calendar c = Calendar.getInstance();
+        c.add(Calendar.MILLISECOND, -ENTITY_TIMEOUT);
+        Date cutoff = c.getTime();
+
+        ArrayList<Entity> toRemove = new ArrayList<Entity>();
+        ArrayList<Entity> toKeep = new ArrayList<Entity>();
+
+        Iterator<Device> diter = deviceMap.values().iterator();
+        LinkedList<DeviceUpdate> deviceUpdates =
+                new LinkedList<DeviceUpdate>();
+
+        while (diter.hasNext()) {
+            Device d = diter.next();
+
+            while (true) {
+                deviceUpdates.clear();
+                toRemove.clear();
+                toKeep.clear();
+                for (Entity e : d.getEntities()) {
+                    if (e.getLastSeenTimestamp() != null &&
+                         0 > e.getLastSeenTimestamp().compareTo(cutoff)) {
+                        // individual entity needs to be removed
+                        toRemove.add(e);
+                    } else {
+                        toKeep.add(e);
+                    }
+                }
+                if (toRemove.size() == 0) {
+                    break;
+                }
+
+                for (Entity e : toRemove) {
+                    removeEntity(e, d.getEntityClass(), d.deviceKey, toKeep);
+                }
+
+                if (toKeep.size() > 0) {
+                    Device newDevice = allocateDevice(d.getDeviceKey(),
+                                                      d.oldAPs,
+                                                      d.attachmentPoints,
+                                                      toKeep,
+                                                      d.entityClass);
+
+                    EnumSet<DeviceField> changedFields =
+                            EnumSet.noneOf(DeviceField.class);
+                    for (Entity e : toRemove) {
+                        changedFields.addAll(findChangedFields(newDevice, e));
+                    }
+                    if (changedFields.size() > 0)
+                        deviceUpdates.add(new DeviceUpdate(d, CHANGE,
+                                                           changedFields));
+
+                    if (!deviceMap.replace(newDevice.getDeviceKey(),
+                                           d,
+                                           newDevice)) {
+                        // concurrent modification; try again
+                        // need to use device that is the map now for the next
+                        // iteration
+                        d = deviceMap.get(d.getDeviceKey());
+                        if (null != d)
+                            continue;
+                    }
+                } else {
+                    deviceUpdates.add(new DeviceUpdate(d, DELETE, null));
+                    if (!deviceMap.remove(d.getDeviceKey(), d))
+                        // concurrent modification; try again
+                        // need to use device that is the map now for the next
+                        // iteration
+                        d = deviceMap.get(d.getDeviceKey());
+                        if (null != d)
+                            continue;
+                }
+                processUpdates(deviceUpdates);
+                break;
+            }
+        }
+    }
+
+    protected void removeEntity(Entity removed,
+                              IEntityClass entityClass,
+                              Long deviceKey,
+                              Collection<Entity> others) {
+        for (DeviceIndex index : secondaryIndexMap.values()) {
+            index.removeEntityIfNeeded(removed, deviceKey, others);
+        }
+        ClassState classState = getClassState(entityClass);
+        for (DeviceIndex index : classState.secondaryIndexMap.values()) {
+            index.removeEntityIfNeeded(removed, deviceKey, others);
+        }
+
+        primaryIndex.removeEntityIfNeeded(removed, deviceKey, others);
+
+        if (classState.classIndex != null) {
+            classState.classIndex.removeEntityIfNeeded(removed,
+                                                       deviceKey,
+                                                       others);
+        }
+    }
+    
+    /**
+     * method to delete a given device, remove all entities first and then
+     * finally delete the device itself.
+     * @param device
+     */
+    protected void deleteDevice(Device device) {
+        ArrayList<Entity> emptyToKeep = new ArrayList<Entity>();
+        for (Entity entity : device.getEntities()) {
+            this.removeEntity(entity, device.getEntityClass(), 
+                device.getDeviceKey(), emptyToKeep);
+        }
+        if (!deviceMap.remove(device.getDeviceKey(), device)) {
+            if (logger.isDebugEnabled())
+                logger.debug("device map does not have this device -" + 
+                    device.toString());
+        }
+    }
+
+    private EnumSet<DeviceField> getEntityKeys(Long macAddress,
+                                               Short vlan,
+                                               Integer ipv4Address,
+                                               Long switchDPID,
+                                               Integer switchPort) {
+        // FIXME: vlan==null is a valid search. Need to handle this
+        // case correctly. Note that the code will still work correctly. 
+        // But we might do a full device search instead of using an index.
+        EnumSet<DeviceField> keys = EnumSet.noneOf(DeviceField.class);
+        if (macAddress != null) keys.add(DeviceField.MAC);
+        if (vlan != null) keys.add(DeviceField.VLAN);
+        if (ipv4Address != null) keys.add(DeviceField.IPV4);
+        if (switchDPID != null) keys.add(DeviceField.SWITCH);
+        if (switchPort != null) keys.add(DeviceField.PORT);
+        return keys;
+    }
+
+
+    protected Iterator<Device> queryClassByEntity(IEntityClass clazz,
+                                                  EnumSet<DeviceField> keyFields,
+                                                  Entity entity) {
+        ClassState classState = getClassState(clazz);
+        DeviceIndex index = classState.secondaryIndexMap.get(keyFields);
+        if (index == null) return Collections.<Device>emptySet().iterator();
+        return new DeviceIndexInterator(this, index.queryByEntity(entity));
+    }
+
+    protected Device allocateDevice(Long deviceKey,
+                                    Entity entity,
+                                    IEntityClass entityClass) {
+        return new Device(this, deviceKey, entity, entityClass);
+    }
+
+    // TODO: FIX THIS.
+    protected Device allocateDevice(Long deviceKey,
+                                    List<AttachmentPoint> aps,
+                                    List<AttachmentPoint> trueAPs,
+                                    Collection<Entity> entities,
+                                    IEntityClass entityClass) {
+        return new Device(this, deviceKey, aps, trueAPs, entities, entityClass);
+    }
+
+    protected Device allocateDevice(Device device,
+                                    Entity entity) {
+        return new Device(device, entity);
+    }
+    
+    protected Device allocateDevice(Device device, Set <Entity> entities) {
+        List <AttachmentPoint> newPossibleAPs = 
+                new ArrayList<AttachmentPoint>();
+        List <AttachmentPoint> newAPs = 
+                new ArrayList<AttachmentPoint>();
+        for (Entity entity : entities) { 
+            if (entity.switchDPID != null && entity.switchPort != null) {
+                AttachmentPoint aP = 
+                        new AttachmentPoint(entity.switchDPID.longValue(), 
+                                    entity.switchPort.shortValue(), 0);
+                newPossibleAPs.add(aP);
+            }
+        }
+        if (device.attachmentPoints != null) {
+            for (AttachmentPoint oldAP : device.attachmentPoints) {
+                if (newPossibleAPs.contains(oldAP)) {
+                    newAPs.add(oldAP);
+                }
+            }
+        }
+        if (newAPs.isEmpty())
+            newAPs = null;
+        Device d = new Device(this, device.getDeviceKey(),newAPs, null,
+                        entities, device.getEntityClass());
+        d.updateAttachmentPoint();
+        return d;
+    }
+
+    @Override
+    public void addSuppressAPs(long swId, short port) {
+        suppressAPs.add(new SwitchPort(swId, port));
+    }
+
+    @Override
+    public void removeSuppressAPs(long swId, short port) {
+        suppressAPs.remove(new SwitchPort(swId, port));
+    }
+
+    /**
+     * Topology listener method.
+     */
+    @Override
+    public void topologyChanged() {
+        Iterator<Device> diter = deviceMap.values().iterator();
+        List<LDUpdate> updateList = topology.getLastLinkUpdates();
+        if (updateList != null) {
+            if (logger.isTraceEnabled()) {
+                for(LDUpdate update: updateList) {
+                    logger.trace("Topo update: {}", update);
+                }
+            }
+        }
+
+        while (diter.hasNext()) {
+            Device d = diter.next();
+            if (d.updateAttachmentPoint()) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Attachment point changed for device: {}", d);
+                }
+                sendDeviceMovedNotification(d);
+            }
+        }
+    }
+
+    /**
+     * Send update notifications to listeners
+     * @param updates the updates to process.
+     */
+    protected void sendDeviceMovedNotification(Device d) {
+        for (IDeviceListener listener : deviceListeners) {
+            listener.deviceMoved(d);
+        }
+    }
+    
+    /**
+     * this method will reclassify and reconcile a device - possibilities
+     * are - create new device(s), remove entities from this device. If the 
+     * device entity class did not change then it returns false else true.
+     * @param device
+     */
+    protected boolean reclassifyDevice(Device device)
+    {
+        // first classify all entities of this device
+        if (device == null) {
+            logger.debug("In reclassify for null device");
+            return false;
+        }
+        boolean needToReclassify = false;
+        for (Entity entity : device.entities) {
+            IEntityClass entityClass = 
+                    this.entityClassifier.classifyEntity(entity);
+            if (entityClass == null || device.getEntityClass() == null) {
+                needToReclassify = true;                
+                break;
+            }
+            if (!entityClass.getName().
+                    equals(device.getEntityClass().getName())) {
+                needToReclassify = true;
+                break;
+            }
+        }
+        if (needToReclassify == false) {
+            return false;
+        }
+            
+        LinkedList<DeviceUpdate> deviceUpdates =
+                new LinkedList<DeviceUpdate>();
+        // delete this device and then re-learn all the entities
+        this.deleteDevice(device);
+        deviceUpdates.add(new DeviceUpdate(device, 
+                DeviceUpdate.Change.DELETE, null));
+        if (!deviceUpdates.isEmpty())
+            processUpdates(deviceUpdates);
+        for (Entity entity: device.entities ) {
+            this.learnDeviceByEntity(entity);
+        }
+        return true;
+    }   
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java
new file mode 100644
index 0000000..c6aa980
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java
@@ -0,0 +1,108 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.util.IterableIterator;
+
+/**
+ * An index that maps key fields of an entity to device keys, with multiple
+ * device keys allowed per entity
+ */
+public class DeviceMultiIndex extends DeviceIndex {
+    /**
+     * The index
+     */
+    private ConcurrentHashMap<IndexedEntity, Collection<Long>> index;
+
+    /**
+     * @param keyFields
+     */
+    public DeviceMultiIndex(EnumSet<DeviceField> keyFields) {
+        super(keyFields);
+        index = new ConcurrentHashMap<IndexedEntity, Collection<Long>>();
+    }
+
+    // ***********
+    // DeviceIndex
+    // ***********
+
+    @Override
+    public Iterator<Long> queryByEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        Collection<Long> devices = index.get(ie);
+        if (devices != null)
+            return devices.iterator();
+        
+        return Collections.<Long>emptySet().iterator();
+    }
+    
+    @Override
+    public Iterator<Long> getAll() {
+        Iterator<Collection<Long>> iter = index.values().iterator();
+        return new IterableIterator<Long>(iter);
+    }
+    
+    @Override
+    public boolean updateIndex(Device device, Long deviceKey) {
+        for (Entity e : device.entities) {
+            updateIndex(e, deviceKey);
+        }
+        return true;
+    }
+    
+    @Override
+    public void updateIndex(Entity entity, Long deviceKey) {
+        Collection<Long> devices = null;
+
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        if (!ie.hasNonNullKeys()) return;
+
+        devices = index.get(ie);
+        if (devices == null) {
+            Map<Long,Boolean> chm = new ConcurrentHashMap<Long,Boolean>();
+            devices = Collections.newSetFromMap(chm);
+            Collection<Long> r = index.putIfAbsent(ie, devices);
+            if (r != null)
+                devices = r;
+        }
+        
+        devices.add(deviceKey);
+    }
+
+    @Override
+    public void removeEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        index.remove(ie);        
+    }
+
+    @Override
+    public void removeEntity(Entity entity, Long deviceKey) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        Collection<Long> devices = index.get(ie);
+        if (devices != null)
+            devices.remove(deviceKey);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java
new file mode 100644
index 0000000..4f2d3f8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java
@@ -0,0 +1,116 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+
+/**
+ * An index that maps key fields of an entity uniquely to a device key
+ */
+public class DeviceUniqueIndex extends DeviceIndex {
+    /**
+     * The index
+     */
+    private ConcurrentHashMap<IndexedEntity, Long> index;
+
+    /**
+     * Construct a new device index using the provided key fields
+     * @param keyFields the key fields to use
+     */
+    public DeviceUniqueIndex(EnumSet<DeviceField> keyFields) {
+        super(keyFields);
+        index = new ConcurrentHashMap<IndexedEntity, Long>();
+    }
+
+    // ***********
+    // DeviceIndex
+    // ***********
+
+    @Override
+    public Iterator<Long> queryByEntity(Entity entity) {
+        final Long deviceKey = findByEntity(entity);
+        if (deviceKey != null)
+            return Collections.<Long>singleton(deviceKey).iterator();
+        
+        return Collections.<Long>emptySet().iterator();
+    }
+    
+    @Override
+    public Iterator<Long> getAll() {
+        return index.values().iterator();
+    }
+
+    @Override
+    public boolean updateIndex(Device device, Long deviceKey) {
+        for (Entity e : device.entities) {
+            IndexedEntity ie = new IndexedEntity(keyFields, e);
+            if (!ie.hasNonNullKeys()) continue;
+
+            Long ret = index.putIfAbsent(ie, deviceKey);
+            if (ret != null && !ret.equals(deviceKey)) {
+                // If the return value is non-null, then fail the insert 
+                // (this implies that a device using this entity has 
+                // already been created in another thread).
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    @Override
+    public void updateIndex(Entity entity, Long deviceKey) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        if (!ie.hasNonNullKeys()) return;
+        index.put(ie, deviceKey);
+    }
+
+    @Override
+    public void removeEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        index.remove(ie);
+    }
+
+    @Override
+    public void removeEntity(Entity entity, Long deviceKey) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        index.remove(ie, deviceKey);
+    }
+
+    // **************
+    // Public Methods
+    // **************
+
+    /**
+     * Look up a {@link Device} based on the provided {@link Entity}.
+     * @param entity the entity to search for
+     * @return The key for the {@link Device} object if found
+     */
+    public Long findByEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        Long deviceKey = index.get(ie);
+        if (deviceKey == null)
+            return null;
+        return deviceKey;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
new file mode 100644
index 0000000..36c5471
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
@@ -0,0 +1,279 @@
+/**
+*    Copyright 2011,2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.Date;
+
+import net.floodlightcontroller.core.web.serializers.IPv4Serializer;
+import net.floodlightcontroller.core.web.serializers.MACSerializer;
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.packet.IPv4;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+
+/**
+ * An entity on the network is a visible trace of a device that corresponds
+ * to a packet received from a particular interface on the edge of a network,
+ * with a particular VLAN tag, and a particular MAC address, along with any
+ * other packet characteristics we might want to consider as helpful for
+ * disambiguating devices.
+ * 
+ * Entities are the most basic element of devices; devices consist of one or
+ * more entities.  Entities are immutable once created, except for the last
+ * seen timestamp.
+ *  
+ * @author readams
+ *
+ */
+public class Entity implements Comparable<Entity> {
+    /**
+     * Timeout for computing {@link Entity#activeSince}.
+     * @see {@link Entity#activeSince}
+     */
+    protected static int ACTIVITY_TIMEOUT = 30000;
+    
+    /**
+     * The MAC address associated with this entity
+     */
+    protected long macAddress;
+    
+    /**
+     * The IP address associated with this entity, or null if no IP learned
+     * from the network observation associated with this entity
+     */
+    protected Integer ipv4Address;
+    
+    /**
+     * The VLAN tag on this entity, or null if untagged
+     */
+    protected Short vlan;
+    
+    /**
+     * The DPID of the switch for the ingress point for this entity,
+     * or null if not present
+     */
+    protected Long switchDPID;
+    
+    /**
+     * The port number of the switch for the ingress point for this entity,
+     * or null if not present
+     */
+    protected Integer switchPort;
+    
+    /**
+     * The last time we observed this entity on the network
+     */
+    protected Date lastSeenTimestamp;
+
+    /**
+     * The time between {@link Entity#activeSince} and 
+     * {@link Entity#lastSeenTimestamp} is a period of activity for this
+     * entity where it was observed repeatedly.  If, when the entity is
+     * observed, the  is longer ago than the activity timeout, 
+     * {@link Entity#lastSeenTimestamp} and {@link Entity#activeSince} will 
+     * be set to the current time.
+     */
+    protected Date activeSince;
+    
+    private int hashCode = 0;
+
+    // ************
+    // Constructors
+    // ************
+    
+    /**
+     * Create a new entity
+     * 
+     * @param macAddress
+     * @param vlan
+     * @param ipv4Address
+     * @param switchDPID
+     * @param switchPort
+     * @param lastSeenTimestamp
+     */
+    public Entity(long macAddress, Short vlan, 
+                  Integer ipv4Address, Long switchDPID, Integer switchPort, 
+                  Date lastSeenTimestamp) {
+        this.macAddress = macAddress;
+        this.ipv4Address = ipv4Address;
+        this.vlan = vlan;
+        this.switchDPID = switchDPID;
+        this.switchPort = switchPort;
+        this.lastSeenTimestamp = lastSeenTimestamp;
+        this.activeSince = lastSeenTimestamp;
+    }
+
+    // ***************
+    // Getters/Setters
+    // ***************
+
+    @JsonSerialize(using=MACSerializer.class)
+    public long getMacAddress() {
+        return macAddress;
+    }
+
+    @JsonSerialize(using=IPv4Serializer.class)
+    public Integer getIpv4Address() {
+        return ipv4Address;
+    }
+
+    public Short getVlan() {
+        return vlan;
+    }
+
+    @JsonSerialize(using=DPIDSerializer.class)
+    public Long getSwitchDPID() {
+        return switchDPID;
+    }
+
+    public Integer getSwitchPort() {
+        return switchPort;
+    }
+
+    public Date getLastSeenTimestamp() {
+        return lastSeenTimestamp;
+    }
+
+    /**
+     * Set the last seen timestamp and also update {@link Entity#activeSince}
+     * if appropriate
+     * @param lastSeenTimestamp the new last seen timestamp
+     * @see {@link Entity#activeSince}
+     */
+    public void setLastSeenTimestamp(Date lastSeenTimestamp) {
+        if (activeSince == null ||
+            (activeSince.getTime() +  ACTIVITY_TIMEOUT) <
+                lastSeenTimestamp.getTime())
+            this.activeSince = lastSeenTimestamp;
+        this.lastSeenTimestamp = lastSeenTimestamp;
+    }
+
+    public Date getActiveSince() {
+        return activeSince;
+    }
+
+    public void setActiveSince(Date activeSince) {
+        this.activeSince = activeSince;
+    }
+    
+    @Override
+    public int hashCode() {
+        if (hashCode != 0) return hashCode;
+        final int prime = 31;
+        hashCode = 1;
+        hashCode = prime * hashCode
+                 + ((ipv4Address == null) ? 0 : ipv4Address.hashCode());
+        hashCode = prime * hashCode + (int) (macAddress ^ (macAddress >>> 32));
+        hashCode = prime * hashCode
+                 + ((switchDPID == null) ? 0 : switchDPID.hashCode());
+        hashCode = prime * hashCode
+                 + ((switchPort == null) ? 0 : switchPort.hashCode());
+        hashCode = prime * hashCode + ((vlan == null) ? 0 : vlan.hashCode());
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        Entity other = (Entity) obj;
+        if (hashCode() != other.hashCode()) return false;
+        if (ipv4Address == null) {
+            if (other.ipv4Address != null) return false;
+        } else if (!ipv4Address.equals(other.ipv4Address)) return false;
+        if (macAddress != other.macAddress) return false;
+        if (switchDPID == null) {
+            if (other.switchDPID != null) return false;
+        } else if (!switchDPID.equals(other.switchDPID)) return false;
+        if (switchPort == null) {
+            if (other.switchPort != null) return false;
+        } else if (!switchPort.equals(other.switchPort)) return false;
+        if (vlan == null) {
+            if (other.vlan != null) return false;
+        } else if (!vlan.equals(other.vlan)) return false;
+        return true;
+    }
+
+    
+    
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Entity [macAddress=");
+        builder.append(HexString.toHexString(macAddress, 6));
+        builder.append(", ipv4Address=");
+        builder.append(IPv4.fromIPv4Address(ipv4Address==null ?
+                       0 : ipv4Address.intValue()));
+        builder.append(", vlan=");
+        builder.append(vlan);
+        builder.append(", switchDPID=");
+        builder.append(switchDPID);
+        builder.append(", switchPort=");
+        builder.append(switchPort);
+        builder.append(", lastSeenTimestamp=");
+        builder.append(lastSeenTimestamp == null? "null" : lastSeenTimestamp.getTime());
+        builder.append(", activeSince=");
+        builder.append(activeSince == null? "null" : activeSince.getTime());
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public int compareTo(Entity o) {
+        if (macAddress < o.macAddress) return -1;
+        if (macAddress > o.macAddress) return 1;
+
+        int r;
+        if (switchDPID == null)
+            r = o.switchDPID == null ? 0 : -1;
+        else if (o.switchDPID == null)
+            r = 1;
+        else
+            r = switchDPID.compareTo(o.switchDPID);
+        if (r != 0) return r;
+
+        if (switchPort == null)
+            r = o.switchPort == null ? 0 : -1;
+        else if (o.switchPort == null)
+            r = 1;
+        else
+            r = switchPort.compareTo(o.switchPort);
+        if (r != 0) return r;
+
+        if (ipv4Address == null)
+            r = o.ipv4Address == null ? 0 : -1;
+        else if (o.ipv4Address == null)
+            r = 1;
+        else
+            r = ipv4Address.compareTo(o.ipv4Address);
+        if (r != 0) return r;
+
+        if (vlan == null)
+            r = o.vlan == null ? 0 : -1;
+        else if (o.vlan == null)
+            r = 1;
+        else
+            r = vlan.compareTo(o.vlan);
+        if (r != 0) return r;
+
+        return 0;
+    }
+    
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
new file mode 100644
index 0000000..3e0829d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
@@ -0,0 +1,155 @@
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.EnumSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+
+
+/**
+ * This is a thin wrapper around {@link Entity} that allows overriding
+ * the behavior of {@link Object#hashCode()} and {@link Object#equals(Object)}
+ * so that the keying behavior in a hash map can be changed dynamically
+ * @author readams
+ */
+public class IndexedEntity {
+    protected EnumSet<DeviceField> keyFields;
+    protected Entity entity;
+    private int hashCode = 0;
+    protected static Logger logger =
+            LoggerFactory.getLogger(IndexedEntity.class);
+    /**
+     * Create a new {@link IndexedEntity} for the given {@link Entity} using 
+     * the provided key fields.
+     * @param keyFields The key fields that will be used for computing
+     * {@link IndexedEntity#hashCode()} and {@link IndexedEntity#equals(Object)}
+     * @param entity the entity to wrap
+     */
+    public IndexedEntity(EnumSet<DeviceField> keyFields, Entity entity) {
+        super();
+        this.keyFields = keyFields;
+        this.entity = entity;
+    }
+
+    /**
+     * Check whether this entity has non-null values in any of its key fields
+     * @return true if any key fields have a non-null value
+     */
+    public boolean hasNonNullKeys() {
+        for (DeviceField f : keyFields) {
+            switch (f) {
+                case MAC:
+                    return true;
+                case IPV4:
+                    if (entity.ipv4Address != null) return true;
+                    break;
+                case SWITCH:
+                    if (entity.switchDPID != null) return true;
+                    break;
+                case PORT:
+                    if (entity.switchPort != null) return true;
+                    break;
+                case VLAN:
+                    if (entity.vlan != null) return true;
+                    break;
+            }
+        }
+        return false;
+    }
+    
+    @Override
+    public int hashCode() {
+    	
+        if (hashCode != 0) {
+        	return hashCode;
+        }
+
+        final int prime = 31;
+        hashCode = 1;
+        for (DeviceField f : keyFields) {
+            switch (f) {
+                case MAC:
+                    hashCode = prime * hashCode
+                        + (int) (entity.macAddress ^ 
+                                (entity.macAddress >>> 32));
+                    break;
+                case IPV4:
+                    hashCode = prime * hashCode
+                        + ((entity.ipv4Address == null) 
+                            ? 0 
+                            : entity.ipv4Address.hashCode());
+                    break;
+                case SWITCH:
+                    hashCode = prime * hashCode
+                        + ((entity.switchDPID == null) 
+                            ? 0 
+                            : entity.switchDPID.hashCode());
+                    break;
+                case PORT:
+                    hashCode = prime * hashCode
+                        + ((entity.switchPort == null) 
+                            ? 0 
+                            : entity.switchPort.hashCode());
+                    break;
+                case VLAN:
+                    hashCode = prime * hashCode 
+                        + ((entity.vlan == null) 
+                            ? 0 
+                            : entity.vlan.hashCode());
+                    break;
+            }
+        }
+        return hashCode;
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+       if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        IndexedEntity other = (IndexedEntity) obj;
+        
+        if (!keyFields.equals(other.keyFields))
+            return false;
+
+        for (IDeviceService.DeviceField f : keyFields) {
+            switch (f) {
+                case MAC:
+                    if (entity.macAddress != other.entity.macAddress)
+                        return false;
+                    break;
+                case IPV4:
+                    if (entity.ipv4Address == null) {
+                        if (other.entity.ipv4Address != null) return false;
+                    } else if (!entity.ipv4Address.
+                            equals(other.entity.ipv4Address)) return false;
+                    break;
+                case SWITCH:
+                    if (entity.switchDPID == null) {
+                        if (other.entity.switchDPID != null) return false;
+                    } else if (!entity.switchDPID.
+                            equals(other.entity.switchDPID)) return false;
+                    break;
+                case PORT:
+                    if (entity.switchPort == null) {
+                        if (other.entity.switchPort != null) return false;
+                    } else if (!entity.switchPort.
+                            equals(other.entity.switchPort)) return false;
+                    break;
+                case VLAN:
+                    if (entity.vlan == null) {
+                        if (other.entity.vlan != null) return false;
+                    } else if (!entity.vlan.
+                            equals(other.entity.vlan)) return false;
+                    break;
+            }
+        }
+        
+        return true;
+    }
+    
+    
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java
new file mode 100644
index 0000000..58e79e4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java
@@ -0,0 +1,197 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.web;
+
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.internal.Device;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.util.FilterIterator;
+
+import org.openflow.util.HexString;
+import org.restlet.data.Form;
+import org.restlet.data.Status;
+import org.restlet.resource.ServerResource;
+
+/**
+ * Resource for querying and displaying devices that exist in the system
+ */
+public abstract class AbstractDeviceResource extends ServerResource {
+    public static final String MAC_ERROR = 
+            "Invalid MAC address: must be a 48-bit quantity, " + 
+            "expressed in hex as AA:BB:CC:DD:EE:FF";
+    public static final String VLAN_ERROR = 
+            "Invalid VLAN: must be an integer in the range 0-4095";
+    public static final String IPV4_ERROR = 
+            "Invalid IPv4 address: must be in dotted decimal format, " + 
+            "234.0.59.1";
+    public static final String DPID_ERROR = 
+            "Invalid Switch DPID: must be a 64-bit quantity, expressed in " + 
+            "hex as AA:BB:CC:DD:EE:FF:00:11";
+    public static final String PORT_ERROR = 
+            "Invalid Port: must be a positive integer";
+    
+    public Iterator<? extends IDevice> getDevices() {
+        IDeviceService deviceManager = 
+                (IDeviceService)getContext().getAttributes().
+                    get(IDeviceService.class.getCanonicalName());  
+                
+        Long macAddress = null;
+        Short vlan = null;
+        Integer ipv4Address = null;
+        Long switchDPID = null;
+        Integer switchPort = null;
+        
+        Form form = getQuery();
+        String macAddrStr = form.getFirstValue("mac", true);
+        String vlanStr = form.getFirstValue("vlan", true);
+        String ipv4Str = form.getFirstValue("ipv4", true);
+        String dpid = form.getFirstValue("dpid", true);
+        String port = form.getFirstValue("port", true);
+        
+        if (macAddrStr != null) {
+            try {
+                macAddress = HexString.toLong(macAddrStr);
+            } catch (Exception e) {
+                setStatus(Status.CLIENT_ERROR_BAD_REQUEST, MAC_ERROR);
+                return null;
+            }
+        }
+        if (vlanStr != null) {
+            try {
+                vlan = Short.parseShort(vlanStr);
+                if (vlan > 4095 || vlan < 0) {
+                    setStatus(Status.CLIENT_ERROR_BAD_REQUEST, VLAN_ERROR);
+                    return null;
+                }
+            } catch (Exception e) {
+                setStatus(Status.CLIENT_ERROR_BAD_REQUEST, VLAN_ERROR);
+                return null;
+            }
+        }
+        if (ipv4Str != null) {
+            try {
+                ipv4Address = IPv4.toIPv4Address(ipv4Str);
+            } catch (Exception e) {
+                setStatus(Status.CLIENT_ERROR_BAD_REQUEST, IPV4_ERROR);
+                return null;
+            }
+        }
+        if (dpid != null) {
+            try {
+                switchDPID = HexString.toLong(dpid);
+            } catch (Exception e) {
+                setStatus(Status.CLIENT_ERROR_BAD_REQUEST, DPID_ERROR);
+                return null;
+            }
+        }
+        if (port != null) {
+            try {
+                switchPort = Integer.parseInt(port);
+                if (switchPort < 0) {
+                    setStatus(Status.CLIENT_ERROR_BAD_REQUEST, PORT_ERROR);
+                    return null;
+                }
+            } catch (Exception e) {
+                setStatus(Status.CLIENT_ERROR_BAD_REQUEST, PORT_ERROR);
+                return null;
+            }
+        }
+        
+        @SuppressWarnings("unchecked")
+        Iterator<Device> diter = (Iterator<Device>)
+                deviceManager.queryDevices(macAddress, 
+                                           vlan, 
+                                           ipv4Address, 
+                                           switchDPID, 
+                                           switchPort);
+        
+        final String macStartsWith = 
+                form.getFirstValue("mac__startswith", true);
+        final String vlanStartsWith = 
+                form.getFirstValue("vlan__startswith", true);
+        final String ipv4StartsWith = 
+                form.getFirstValue("ipv4__startswith", true);
+        final String dpidStartsWith = 
+                form.getFirstValue("dpid__startswith", true);
+        final String portStartsWith = 
+                form.getFirstValue("port__startswith", true);
+        
+        return new FilterIterator<Device>(diter) {
+            @Override
+            protected boolean matches(Device value) {
+                if (macStartsWith != null) {
+                    if (!value.getMACAddressString().startsWith(macStartsWith))
+                        return false;
+                }
+                if (vlanStartsWith != null) {
+                    boolean match = false;
+                    for (Short v : value.getVlanId()) {
+                        if (v != null && 
+                            v.toString().startsWith(vlanStartsWith)) {
+                            match = true;
+                            break;
+                        }
+                    }
+                    if (!match) return false;
+                }
+                if (ipv4StartsWith != null) {
+                    boolean match = false;
+                    for (Integer v : value.getIPv4Addresses()) {
+                        String str = IPv4.fromIPv4Address(v);
+                        if (v != null && 
+                            str.startsWith(ipv4StartsWith)) {
+                            match = true;
+                            break;
+                        }
+                    }
+                    if (!match) return false;
+                }
+                if (dpidStartsWith != null) {
+                    boolean match = false;
+                    for (SwitchPort v : value.getAttachmentPoints(true)) {
+                        String str = 
+                                HexString.toHexString(v.getSwitchDPID(), 8);
+                        if (v != null && 
+                            str.startsWith(dpidStartsWith)) {
+                            match = true;
+                            break;
+                        }
+                    }
+                    if (!match) return false;
+                }
+                if (portStartsWith != null) {
+                    boolean match = false;
+                    for (SwitchPort v : value.getAttachmentPoints(true)) {
+                        String str = Integer.toString(v.getPort());
+                        if (v != null && 
+                            str.startsWith(portStartsWith)) {
+                            match = true;
+                            break;
+                        }
+                    }
+                    if (!match) return false;
+                }
+                return true;
+            }
+        };
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceEntityResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceEntityResource.java
new file mode 100644
index 0000000..2783a26
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceEntityResource.java
@@ -0,0 +1,55 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.web;
+
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.internal.Device;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+import org.restlet.resource.Get;
+
+/**
+ * Resource for querying and displaying internal debug information on
+ * network entities associated with devices
+ */
+public class DeviceEntityResource extends AbstractDeviceResource {
+    @Get("json")
+    public Iterator<Entity[]> getDeviceEntities() {
+        final Iterator<? extends IDevice> devices = super.getDevices();
+        return new Iterator<Entity[]>() {
+
+            @Override
+            public boolean hasNext() {
+                return devices.hasNext();
+            }
+
+            @Override
+            public Entity[] next() {
+                Device d = (Device)devices.next();
+                return d.getEntities();
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java
new file mode 100644
index 0000000..c479af0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java
@@ -0,0 +1,33 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.web;
+
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IDevice;
+import org.restlet.resource.Get;
+
+/**
+ * Resource for querying and displaying devices that exist in the system
+ */
+public class DeviceResource extends AbstractDeviceResource {
+    @Get("json")
+    public Iterator<? extends IDevice> getDevices() {
+        return super.getDevices();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java
new file mode 100644
index 0000000..9a76505
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java
@@ -0,0 +1,44 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.web;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+/**
+ * Routable for device rest api
+ */
+public class DeviceRoutable implements RestletRoutable {
+
+    @Override
+    public String basePath() {
+        return "/wm/device";
+    }
+    
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/", DeviceResource.class);
+        router.attach("/debug", DeviceEntityResource.class);
+        return router;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
new file mode 100644
index 0000000..66bdaef
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
@@ -0,0 +1,70 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.web;
+
+import java.io.IOException;
+
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.internal.Device;
+import net.floodlightcontroller.packet.IPv4;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.util.HexString;
+
+/**
+ * Serialize a device object
+ */
+public class DeviceSerializer extends JsonSerializer<Device> {
+
+    @Override
+    public void serialize(Device device, JsonGenerator jGen,
+                          SerializerProvider serializer) throws IOException,
+            JsonProcessingException {
+        jGen.writeStartObject();
+        
+        jGen.writeStringField("entityClass", device.getEntityClass().getName());
+        
+        jGen.writeArrayFieldStart("mac");
+        jGen.writeString(HexString.toHexString(device.getMACAddress(), 6));
+        jGen.writeEndArray();
+
+        jGen.writeArrayFieldStart("ipv4");
+        for (Integer ip : device.getIPv4Addresses())
+            jGen.writeString(IPv4.fromIPv4Address(ip));
+        jGen.writeEndArray();
+
+        jGen.writeArrayFieldStart("vlan");
+        for (Short vlan : device.getVlanId())
+            if (vlan >= 0)
+                jGen.writeNumber(vlan);
+        jGen.writeEndArray();
+        jGen.writeArrayFieldStart("attachmentPoint");
+        for (SwitchPort ap : device.getAttachmentPoints(true)) {
+            serializer.defaultSerializeValue(ap, jGen);
+        }
+        jGen.writeEndArray();
+
+        jGen.writeNumberField("lastSeen", device.getLastSeen().getTime());
+        
+        jGen.writeEndObject();
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/Firewall.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/Firewall.java
new file mode 100644
index 0000000..3f8ff6c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/Firewall.java
@@ -0,0 +1,667 @@
+package net.floodlightcontroller.firewall;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFType;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+
+import java.util.ArrayList;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.routing.IRoutingDecision;
+import net.floodlightcontroller.routing.RoutingDecision;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.StorageException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Stateless firewall implemented as a Google Summer of Code project.
+ * Configuration done through REST API
+ * 
+ * @author Amer Tahir
+ * @edited KC Wang
+ */
+public class Firewall implements IFirewallService, IOFMessageListener,
+        IFloodlightModule {
+
+    // service modules needed
+    protected IFloodlightProviderService floodlightProvider;
+    protected IStorageSourceService storageSource;
+    protected IRestApiService restApi;
+    protected static Logger logger;
+
+    protected List<FirewallRule> rules; // protected by synchronized
+    protected boolean enabled;
+    protected int subnet_mask = IPv4.toIPv4Address("255.255.255.0");
+
+    // constant strings for storage/parsing
+    public static final String TABLE_NAME = "controller_firewallrules";
+    public static final String COLUMN_RULEID = "ruleid";
+    public static final String COLUMN_DPID = "dpid";
+    public static final String COLUMN_IN_PORT = "in_port";
+    public static final String COLUMN_DL_SRC = "dl_src";
+    public static final String COLUMN_DL_DST = "dl_dst";
+    public static final String COLUMN_DL_TYPE = "dl_type";
+    public static final String COLUMN_NW_SRC_PREFIX = "nw_src_prefix";
+    public static final String COLUMN_NW_SRC_MASKBITS = "nw_src_maskbits";
+    public static final String COLUMN_NW_DST_PREFIX = "nw_dst_prefix";
+    public static final String COLUMN_NW_DST_MASKBITS = "nw_dst_maskbits";
+    public static final String COLUMN_NW_PROTO = "nw_proto";
+    public static final String COLUMN_TP_SRC = "tp_src";
+    public static final String COLUMN_TP_DST = "tp_dst";
+    public static final String COLUMN_WILDCARD_DPID = "wildcard_dpid";
+    public static final String COLUMN_WILDCARD_IN_PORT = "wildcard_in_port";
+    public static final String COLUMN_WILDCARD_DL_SRC = "wildcard_dl_src";
+    public static final String COLUMN_WILDCARD_DL_DST = "wildcard_dl_dst";
+    public static final String COLUMN_WILDCARD_DL_TYPE = "wildcard_dl_type";
+    public static final String COLUMN_WILDCARD_NW_SRC = "wildcard_nw_src";
+    public static final String COLUMN_WILDCARD_NW_DST = "wildcard_nw_dst";
+    public static final String COLUMN_WILDCARD_NW_PROTO = "wildcard_nw_proto";
+    public static final String COLUMN_WILDCARD_TP_SRC = "wildcard_tp_src";
+    public static final String COLUMN_WILDCARD_TP_DST = "wildcard_tp_dst";
+    public static final String COLUMN_PRIORITY = "priority";
+    public static final String COLUMN_ACTION = "action";
+    public static String ColumnNames[] = { COLUMN_RULEID, COLUMN_DPID,
+            COLUMN_IN_PORT, COLUMN_DL_SRC, COLUMN_DL_DST, COLUMN_DL_TYPE,
+            COLUMN_NW_SRC_PREFIX, COLUMN_NW_SRC_MASKBITS, COLUMN_NW_DST_PREFIX,
+            COLUMN_NW_DST_MASKBITS, COLUMN_NW_PROTO, COLUMN_TP_SRC,
+            COLUMN_TP_DST, COLUMN_WILDCARD_DPID, COLUMN_WILDCARD_IN_PORT,
+            COLUMN_WILDCARD_DL_SRC, COLUMN_WILDCARD_DL_DST,
+            COLUMN_WILDCARD_DL_TYPE, COLUMN_WILDCARD_NW_SRC,
+            COLUMN_WILDCARD_NW_DST, COLUMN_WILDCARD_NW_PROTO, COLUMN_PRIORITY,
+            COLUMN_ACTION };
+
+    @Override
+    public String getName() {
+        return "firewall";
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        // no prereq
+        return false;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return (type.equals(OFType.PACKET_IN) && name.equals("forwarding"));
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFirewallService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+        Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>();
+        // We are the class that implements the service
+        m.put(IFirewallService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        l.add(IStorageSourceService.class);
+        l.add(IRestApiService.class);
+        return l;
+    }
+
+    /**
+     * Reads the rules from the storage and creates a sorted arraylist of
+     * FirewallRule from them.
+     * 
+     * Similar to getStorageRules(), which only reads contents for REST GET and
+     * does no parsing, checking, nor putting into FirewallRule objects
+     * 
+     * @return the sorted arraylist of FirewallRule instances (rules from
+     *         storage)
+     */
+    protected ArrayList<FirewallRule> readRulesFromStorage() {
+        ArrayList<FirewallRule> l = new ArrayList<FirewallRule>();
+
+        try {
+            Map<String, Object> row;
+
+            // (..., null, null) for no predicate, no ordering
+            IResultSet resultSet = storageSource.executeQuery(TABLE_NAME,
+                    ColumnNames, null, null);
+
+            // put retrieved rows into FirewallRules
+            for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) {
+                row = it.next().getRow();
+                // now, parse row
+                FirewallRule r = new FirewallRule();
+                if (!row.containsKey(COLUMN_RULEID)
+                        || !row.containsKey(COLUMN_DPID)) {
+                    logger.error(
+                            "skipping entry with missing required 'ruleid' or 'switchid' entry: {}",
+                            row);
+                    return l;
+                }
+                try {
+                    r.ruleid = Integer
+                            .parseInt((String) row.get(COLUMN_RULEID));
+                    r.dpid = Long.parseLong((String) row.get(COLUMN_DPID));
+
+                    for (String key : row.keySet()) {
+                        if (row.get(key) == null)
+                            continue;
+                        if (key.equals(COLUMN_RULEID)
+                                || key.equals(COLUMN_DPID)
+                                || key.equals("id")) {
+                            continue; // already handled
+                        } 
+                        
+                        else if (key.equals(COLUMN_IN_PORT)) {
+                            r.in_port = Short.parseShort((String) row
+                                    .get(COLUMN_IN_PORT));
+                        } 
+                        
+                        else if (key.equals(COLUMN_DL_SRC)) {
+                            r.dl_src = Long.parseLong((String) row
+                                    .get(COLUMN_DL_SRC));
+                        } 
+                        
+                        else if (key.equals(COLUMN_DL_DST)) {
+                            r.dl_dst = Long.parseLong((String) row
+                                    .get(COLUMN_DL_DST));
+                        } 
+                        
+                        else if (key.equals(COLUMN_DL_TYPE)) {
+                            r.dl_type = Short.parseShort((String) row
+                                    .get(COLUMN_DL_TYPE));
+                        } 
+                        
+                        else if (key.equals(COLUMN_NW_SRC_PREFIX)) {
+                            r.nw_src_prefix = Integer.parseInt((String) row
+                                    .get(COLUMN_NW_SRC_PREFIX));
+                        } 
+                        
+                        else if (key.equals(COLUMN_NW_SRC_MASKBITS)) {
+                            r.nw_src_maskbits = Integer.parseInt((String) row
+                                    .get(COLUMN_NW_SRC_MASKBITS));
+                        } 
+                        
+                        else if (key.equals(COLUMN_NW_DST_PREFIX)) {
+                            r.nw_dst_prefix = Integer.parseInt((String) row
+                                    .get(COLUMN_NW_DST_PREFIX));
+                        } 
+                        
+                        else if (key.equals(COLUMN_NW_DST_MASKBITS)) {
+                            r.nw_dst_maskbits = Integer.parseInt((String) row
+                                    .get(COLUMN_NW_DST_MASKBITS));
+                        } 
+                        
+                        else if (key.equals(COLUMN_NW_PROTO)) {
+                            r.nw_proto = Short.parseShort((String) row
+                                    .get(COLUMN_NW_PROTO));
+                        } 
+                        
+                        else if (key.equals(COLUMN_TP_SRC)) {
+                            r.tp_src = Short.parseShort((String) row
+                                    .get(COLUMN_TP_SRC));
+                        } 
+                        
+                        else if (key.equals(COLUMN_TP_DST)) {
+                            r.tp_dst = Short.parseShort((String) row
+                                    .get(COLUMN_TP_DST));
+                        } 
+                        
+                        else if (key.equals(COLUMN_WILDCARD_DPID)) {
+                            r.wildcard_dpid = Boolean.parseBoolean((String) row
+                                    .get(COLUMN_WILDCARD_DPID));
+                        } 
+                        
+                        else if (key.equals(COLUMN_WILDCARD_IN_PORT)) {
+                            r.wildcard_in_port = Boolean
+                                    .parseBoolean((String) row
+                                            .get(COLUMN_WILDCARD_IN_PORT));
+                        } 
+                        
+                        else if (key.equals(COLUMN_WILDCARD_DL_SRC)) {
+                            r.wildcard_dl_src = Boolean
+                                    .parseBoolean((String) row
+                                            .get(COLUMN_WILDCARD_DL_SRC));
+                        } 
+                        
+                        else if (key.equals(COLUMN_WILDCARD_DL_DST)) {
+                            r.wildcard_dl_dst = Boolean
+                                    .parseBoolean((String) row
+                                            .get(COLUMN_WILDCARD_DL_DST));
+                        } 
+                        
+                        else if (key.equals(COLUMN_WILDCARD_DL_TYPE)) {
+                            r.wildcard_dl_type = Boolean
+                                    .parseBoolean((String) row
+                                            .get(COLUMN_WILDCARD_DL_TYPE));
+                        } 
+                        
+                        else if (key.equals(COLUMN_WILDCARD_NW_SRC)) {
+                            r.wildcard_nw_src = Boolean
+                                    .parseBoolean((String) row
+                                            .get(COLUMN_WILDCARD_NW_SRC));
+                        } 
+                        
+                        else if (key.equals(COLUMN_WILDCARD_NW_DST)) {
+                            r.wildcard_nw_dst = Boolean
+                                    .parseBoolean((String) row
+                                            .get(COLUMN_WILDCARD_NW_DST));
+                        } 
+                        
+                        else if (key.equals(COLUMN_WILDCARD_NW_PROTO)) {
+                            r.wildcard_nw_proto = Boolean
+                                    .parseBoolean((String) row
+                                            .get(COLUMN_WILDCARD_NW_PROTO));
+                        } 
+                        
+                        else if (key.equals(COLUMN_PRIORITY)) {
+                            r.priority = Integer.parseInt((String) row
+                                    .get(COLUMN_PRIORITY));
+                        } 
+                        
+                        else if (key.equals(COLUMN_ACTION)) {
+                            int tmp = Integer.parseInt((String) row.get(COLUMN_ACTION));
+                            if (tmp == FirewallRule.FirewallAction.DENY.ordinal())
+                                r.action = FirewallRule.FirewallAction.DENY;
+                            else if (tmp == FirewallRule.FirewallAction.ALLOW.ordinal())
+                                r.action = FirewallRule.FirewallAction.ALLOW;
+                            else {
+                                r.action = null;
+                                logger.error("action not recognized");
+                            }
+                        }
+                    }
+                } catch (ClassCastException e) {
+                    logger.error(
+                            "skipping rule {} with bad data : "
+                                    + e.getMessage(), r.ruleid);
+                }
+                if (r.action != null)
+                    l.add(r);
+            }
+        } catch (StorageException e) {
+            logger.error("failed to access storage: {}", e.getMessage());
+            // if the table doesn't exist, then wait to populate later via
+            // setStorageSource()
+        }
+
+        // now, sort the list based on priorities
+        Collections.sort(l);
+
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        floodlightProvider = context
+                .getServiceImpl(IFloodlightProviderService.class);
+        storageSource = context.getServiceImpl(IStorageSourceService.class);
+        restApi = context.getServiceImpl(IRestApiService.class);
+        rules = new ArrayList<FirewallRule>();
+        logger = LoggerFactory.getLogger(Firewall.class);
+
+        // start disabled
+        enabled = false;
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // register REST interface
+        restApi.addRestletRoutable(new FirewallWebRoutable());
+
+        // always place firewall in pipeline at bootup
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+
+        // storage, create table and read rules
+        storageSource.createTable(TABLE_NAME, null);
+        storageSource.setTablePrimaryKeyName(TABLE_NAME, COLUMN_RULEID);
+        synchronized (rules) {
+            this.rules = readRulesFromStorage();
+        }
+    }
+
+    @Override
+    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+        if (!this.enabled)
+            return Command.CONTINUE;
+
+        switch (msg.getType()) {
+        case PACKET_IN:
+            IRoutingDecision decision = null;
+            if (cntx != null) {
+                decision = IRoutingDecision.rtStore.get(cntx,
+                        IRoutingDecision.CONTEXT_DECISION);
+
+                return this.processPacketInMessage(sw, (OFPacketIn) msg,
+                        decision, cntx);
+            }
+            break;
+        default:
+            break;
+        }
+
+        return Command.CONTINUE;
+    }
+
+    @Override
+    public void enableFirewall(boolean enabled) {
+        logger.info("Setting firewall to {}", enabled);
+        this.enabled = enabled;
+    }
+
+    @Override
+    public List<FirewallRule> getRules() {
+        return this.rules;
+    }
+
+    // Only used to serve REST GET
+    // Similar to readRulesFromStorage(), which actually checks and stores
+    // record into FirewallRule list
+    @Override
+    public List<Map<String, Object>> getStorageRules() {
+        ArrayList<Map<String, Object>> l = new ArrayList<Map<String, Object>>();
+        try {
+            // null1=no predicate, null2=no ordering
+            IResultSet resultSet = storageSource.executeQuery(TABLE_NAME,
+                    ColumnNames, null, null);
+            for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) {
+                l.add(it.next().getRow());
+            }
+        } catch (StorageException e) {
+            logger.error("failed to access storage: {}", e.getMessage());
+            // if the table doesn't exist, then wait to populate later via
+            // setStorageSource()
+        }
+        return l;
+    }
+
+    @Override
+    public String getSubnetMask() {
+        return IPv4.fromIPv4Address(this.subnet_mask);
+    }
+
+    @Override
+    public void setSubnetMask(String newMask) {
+        if (newMask.trim().isEmpty())
+            return;
+        this.subnet_mask = IPv4.toIPv4Address(newMask.trim());
+    }
+
+    @Override
+    public synchronized void addRule(FirewallRule rule) {
+        
+        // generate random ruleid for each newly created rule
+        // may want to return to caller if useful
+        // may want to check conflict
+        rule.ruleid = rule.genID();
+        
+        int i = 0;
+        // locate the position of the new rule in the sorted arraylist
+        for (i = 0; i < this.rules.size(); i++) {
+            if (this.rules.get(i).priority >= rule.priority)
+                break;
+        }
+        // now, add rule to the list
+        if (i <= this.rules.size()) {
+            this.rules.add(i, rule);
+        } else {
+            this.rules.add(rule);
+        }
+        // add rule to database
+        Map<String, Object> entry = new HashMap<String, Object>();
+        entry.put(COLUMN_RULEID, Integer.toString(rule.ruleid));
+        entry.put(COLUMN_DPID, Long.toString(rule.dpid));
+        entry.put(COLUMN_IN_PORT, Short.toString(rule.in_port));
+        entry.put(COLUMN_DL_SRC, Long.toString(rule.dl_src));
+        entry.put(COLUMN_DL_DST, Long.toString(rule.dl_dst));
+        entry.put(COLUMN_DL_TYPE, Short.toString(rule.dl_type));
+        entry.put(COLUMN_NW_SRC_PREFIX, Integer.toString(rule.nw_src_prefix));
+        entry.put(COLUMN_NW_SRC_MASKBITS, Integer.toString(rule.nw_src_maskbits));
+        entry.put(COLUMN_NW_DST_PREFIX, Integer.toString(rule.nw_dst_prefix));
+        entry.put(COLUMN_NW_DST_MASKBITS, Integer.toString(rule.nw_dst_maskbits));
+        entry.put(COLUMN_NW_PROTO, Short.toString(rule.nw_proto));
+        entry.put(COLUMN_TP_SRC, Integer.toString(rule.tp_src));
+        entry.put(COLUMN_TP_DST, Integer.toString(rule.tp_dst));
+        entry.put(COLUMN_WILDCARD_DPID,
+                Boolean.toString(rule.wildcard_dpid));
+        entry.put(COLUMN_WILDCARD_IN_PORT,
+                Boolean.toString(rule.wildcard_in_port));
+        entry.put(COLUMN_WILDCARD_DL_SRC,
+                Boolean.toString(rule.wildcard_dl_src));
+        entry.put(COLUMN_WILDCARD_DL_DST,
+                Boolean.toString(rule.wildcard_dl_dst));
+        entry.put(COLUMN_WILDCARD_DL_TYPE,
+                Boolean.toString(rule.wildcard_dl_type));
+        entry.put(COLUMN_WILDCARD_NW_SRC,
+                Boolean.toString(rule.wildcard_nw_src));
+        entry.put(COLUMN_WILDCARD_NW_DST,
+                Boolean.toString(rule.wildcard_nw_dst));
+        entry.put(COLUMN_WILDCARD_NW_PROTO,
+                Boolean.toString(rule.wildcard_nw_proto));
+        entry.put(COLUMN_WILDCARD_TP_SRC,
+                Boolean.toString(rule.wildcard_tp_src));
+        entry.put(COLUMN_WILDCARD_TP_DST,
+                Boolean.toString(rule.wildcard_tp_dst));
+        entry.put(COLUMN_PRIORITY, Integer.toString(rule.priority));
+        entry.put(COLUMN_ACTION, Integer.toString(rule.action.ordinal()));
+        storageSource.insertRow(TABLE_NAME, entry);
+    }
+
+    @Override
+    public synchronized void deleteRule(int ruleid) {
+        Iterator<FirewallRule> iter = this.rules.iterator();
+        while (iter.hasNext()) {
+            FirewallRule r = iter.next();
+            if (r.ruleid == ruleid) {
+                // found the rule, now remove it
+                iter.remove();
+                break;
+            }
+        }
+        // delete from database
+        storageSource.deleteRow(TABLE_NAME, Integer.toString(ruleid));
+    }
+
+    /**
+     * Iterates over the firewall rules and tries to match them with the
+     * incoming packet (flow). Uses the FirewallRule class's matchWithFlow
+     * method to perform matching. It maintains a pair of wildcards (allow and
+     * deny) which are assigned later to the firewall's decision, where 'allow'
+     * wildcards are applied if the matched rule turns out to be an ALLOW rule
+     * and 'deny' wildcards are applied otherwise. Wildcards are applied to
+     * firewall decision to optimize flows in the switch, ensuring least number
+     * of flows per firewall rule. So, if a particular field is not "ANY" (i.e.
+     * not wildcarded) in a higher priority rule, then if a lower priority rule
+     * matches the packet and wildcards it, it can't be wildcarded in the
+     * switch's flow entry, because otherwise some packets matching the higher
+     * priority rule might escape the firewall. The reason for keeping different
+     * two different wildcards is that if a field is not wildcarded in a higher
+     * priority allow rule, the same field shouldn't be wildcarded for packets
+     * matching the lower priority deny rule (non-wildcarded fields in higher
+     * priority rules override the wildcarding of those fields in lower priority
+     * rules of the opposite type). So, to ensure that wildcards are
+     * appropriately set for different types of rules (allow vs. deny), separate
+     * wildcards are maintained. Iteration is performed on the sorted list of
+     * rules (sorted in decreasing order of priority).
+     * 
+     * @param sw
+     *            the switch instance
+     * @param pi
+     *            the incoming packet data structure
+     * @param cntx
+     *            the floodlight context
+     * @return an instance of RuleWildcardsPair that specify rule that matches
+     *         and the wildcards for the firewall decision
+     */
+    protected RuleWildcardsPair matchWithRule(IOFSwitch sw, OFPacketIn pi,
+            FloodlightContext cntx) {
+        FirewallRule matched_rule = null;
+        Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
+                IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+        WildcardsPair wildcards = new WildcardsPair();
+
+        synchronized (rules) {
+            Iterator<FirewallRule> iter = this.rules.iterator();
+            FirewallRule rule = null;
+            // iterate through list to find a matching firewall rule
+            while (iter.hasNext()) {
+                // get next rule from list
+                rule = iter.next();
+
+                // check if rule matches
+                if (rule.matchesFlow(sw.getId(), pi.getInPort(), eth, wildcards) == true) {
+                    matched_rule = rule;
+                    break;
+                }
+            }
+        }
+
+        // make a pair of rule and wildcards, then return it
+        RuleWildcardsPair ret = new RuleWildcardsPair();
+        ret.rule = matched_rule;
+        if (matched_rule == null || matched_rule.action == FirewallRule.FirewallAction.DENY) {
+            ret.wildcards = wildcards.drop;
+        } else {
+            ret.wildcards = wildcards.allow;
+        }
+        return ret;
+    }
+
+    /**
+     * Checks whether an IP address is a broadcast address or not (determines
+     * using subnet mask)
+     * 
+     * @param IPAddress
+     *            the IP address to check
+     * @return true if it is a broadcast address, false otherwise
+     */
+    protected boolean IPIsBroadcast(int IPAddress) {
+        // inverted subnet mask
+        int inv_subnet_mask = ~this.subnet_mask;
+        return ((IPAddress & inv_subnet_mask) == inv_subnet_mask);
+    }
+
+    public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi,
+            IRoutingDecision decision, FloodlightContext cntx) {
+        Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
+                IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+
+        // Allowing L2 broadcast + ARP broadcast request (also deny malformed
+        // broadcasts -> L2 broadcast + L3 unicast)
+        if (eth.isBroadcast() == true) {
+            boolean allowBroadcast = true;
+            // the case to determine if we have L2 broadcast + L3 unicast
+            // don't allow this broadcast packet if such is the case (malformed
+            // packet)
+            if (eth.getEtherType() == Ethernet.TYPE_IPv4
+                    && this.IPIsBroadcast(((IPv4) eth.getPayload())
+                            .getDestinationAddress()) == false) {
+                allowBroadcast = false;
+            }
+            if (allowBroadcast == true) {
+                if (logger.isTraceEnabled())
+                    logger.trace("Allowing broadcast traffic for PacketIn={}",
+                            pi);
+                                        
+                decision = new RoutingDecision(sw.getId(), pi.getInPort()
+                		, IDeviceService.fcStore.
+                        get(cntx, IDeviceService.CONTEXT_SRC_DEVICE),
+                        IRoutingDecision.RoutingAction.MULTICAST);
+                decision.addToContext(cntx);
+            } else {
+                if (logger.isTraceEnabled())
+                    logger.trace(
+                            "Blocking malformed broadcast traffic for PacketIn={}",
+                            pi);
+
+                decision = new RoutingDecision(sw.getId(), pi.getInPort()
+                		, IDeviceService.fcStore.
+                        get(cntx, IDeviceService.CONTEXT_SRC_DEVICE),
+                        IRoutingDecision.RoutingAction.DROP);
+                decision.addToContext(cntx);
+            }
+            return Command.CONTINUE;
+        }
+        /*
+         * ARP response (unicast) can be let through without filtering through
+         * rules by uncommenting the code below
+         */
+        /*
+         * else if (eth.getEtherType() == Ethernet.TYPE_ARP) {
+         * logger.info("allowing ARP traffic"); decision = new
+         * FirewallDecision(IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
+         * decision.addToContext(cntx); return Command.CONTINUE; }
+         */
+
+        // check if we have a matching rule for this packet/flow
+        // and no decision is taken yet
+        if (decision == null) {
+            RuleWildcardsPair match_ret = this.matchWithRule(sw, pi, cntx);
+            FirewallRule rule = match_ret.rule;
+
+            if (rule == null || rule.action == FirewallRule.FirewallAction.DENY) {
+                decision = new RoutingDecision(sw.getId(), pi.getInPort()
+                		, IDeviceService.fcStore.
+                        get(cntx, IDeviceService.CONTEXT_SRC_DEVICE),
+                        IRoutingDecision.RoutingAction.DROP);
+                decision.setWildcards(match_ret.wildcards);
+                decision.addToContext(cntx);
+                if (logger.isTraceEnabled()) {
+                    if (rule == null)
+                        logger.trace(
+                                "No firewall rule found for PacketIn={}, blocking flow",
+                                pi);
+                    else if (rule.action == FirewallRule.FirewallAction.DENY) {
+                        logger.trace("Deny rule={} match for PacketIn={}",
+                                rule, pi);
+                    }
+                }
+            } else {
+                decision = new RoutingDecision(sw.getId(), pi.getInPort()
+                		, IDeviceService.fcStore.
+                        get(cntx, IDeviceService.CONTEXT_SRC_DEVICE),
+                        IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
+                decision.setWildcards(match_ret.wildcards);
+                decision.addToContext(cntx);
+                if (logger.isTraceEnabled())
+                    logger.trace("Allow rule={} match for PacketIn={}", rule,
+                            pi);
+            }
+        }
+
+        return Command.CONTINUE;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java
new file mode 100644
index 0000000..1f4d71a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java
@@ -0,0 +1,125 @@
+package net.floodlightcontroller.firewall;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.restlet.resource.Post;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FirewallResource extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(FirewallResource.class);
+    
+    @Get("json")
+    public Object handleRequest() {
+        IFirewallService firewall = 
+                (IFirewallService)getContext().getAttributes().
+                get(IFirewallService.class.getCanonicalName());
+
+        String op = (String) getRequestAttributes().get("op");
+
+        // REST API check status
+        if (op.equalsIgnoreCase("status")) {
+            if (firewall.isEnabled())
+                return "{\"result\" : \"firewall enabled\"}";
+            else
+                return "{\"result\" : \"firewall disabled\"}";
+        }
+
+        // REST API enable firewall
+        if (op.equalsIgnoreCase("enable")) {
+            firewall.enableFirewall(true);
+            return "{\"status\" : \"success\", \"details\" : \"firewall running\"}";
+        } 
+        
+        // REST API disable firewall
+        if (op.equalsIgnoreCase("disable")) {
+            firewall.enableFirewall(false);
+            return "{\"status\" : \"success\", \"details\" : \"firewall stopped\"}";
+        } 
+        
+        // REST API retrieving rules from storage
+        // currently equivalent to /wm/firewall/rules/json
+        if (op.equalsIgnoreCase("storageRules")) {
+            return firewall.getStorageRules();
+        } 
+        
+        // REST API set local subnet mask -- this only makes sense for one subnet
+        // will remove later
+        if (op.equalsIgnoreCase("subnet-mask")) {
+            return firewall.getSubnetMask();
+        }
+
+        // no known options found
+        return "{\"status\" : \"failure\", \"details\" : \"invalid operation\"}";
+    }
+    
+    /**
+     * Allows setting of subnet mask
+     * @param fmJson The Subnet Mask in JSON format.
+     * @return A string status message
+     */
+    @Post
+    public String handlePost(String fmJson) {
+        IFirewallService firewall = 
+                (IFirewallService)getContext().getAttributes().
+                get(IFirewallService.class.getCanonicalName());
+
+        String newMask;
+        try {
+            newMask = jsonExtractSubnetMask(fmJson);
+        } catch (IOException e) {
+            log.error("Error parsing new subnet mask: " + fmJson, e);
+            e.printStackTrace();
+            return "{\"status\" : \"Error! Could not parse new subnet mask, see log for details.\"}";
+        }
+        firewall.setSubnetMask(newMask);
+        return ("{\"status\" : \"subnet mask set\"}");
+    }
+    
+    /**
+     * Extracts subnet mask from a JSON string
+     * @param fmJson The JSON formatted string
+     * @return The subnet mask
+     * @throws IOException If there was an error parsing the JSON
+     */
+    public static String jsonExtractSubnetMask(String fmJson) throws IOException {
+        String subnet_mask = "";
+        MappingJsonFactory f = new MappingJsonFactory();
+        JsonParser jp;
+
+        try {
+            jp = f.createJsonParser(fmJson);
+        } catch (JsonParseException e) {
+            throw new IOException(e);
+        }
+
+        jp.nextToken();
+        if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
+            throw new IOException("Expected START_OBJECT");
+        }
+
+        while (jp.nextToken() != JsonToken.END_OBJECT) {
+            if (jp.getCurrentToken() != JsonToken.FIELD_NAME) {
+                throw new IOException("Expected FIELD_NAME");
+            }
+
+            String n = jp.getCurrentName();
+            jp.nextToken();
+            if (jp.getText().equals("")) 
+                continue;
+
+            if (n == "subnet-mask") {
+                subnet_mask = jp.getText();
+                break;
+            }
+        }
+
+        return subnet_mask;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java
new file mode 100644
index 0000000..d9b2612
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java
@@ -0,0 +1,392 @@
+package net.floodlightcontroller.firewall;
+
+import org.openflow.protocol.OFMatch;
+
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.TCP;
+import net.floodlightcontroller.packet.UDP;
+
+public class FirewallRule implements Comparable<FirewallRule> {
+    public int ruleid;
+
+    public long dpid; 
+    public short in_port; 
+    public long dl_src; 
+    public long dl_dst; 
+    public short dl_type; 
+    public int nw_src_prefix; 
+    public int nw_src_maskbits;
+    public int nw_dst_prefix;
+    public int nw_dst_maskbits;
+    public short nw_proto;
+    public short tp_src;
+    public short tp_dst;
+
+    public boolean wildcard_dpid;
+    public boolean wildcard_in_port; 
+    public boolean wildcard_dl_src;
+    public boolean wildcard_dl_dst;
+    public boolean wildcard_dl_type;
+    public boolean wildcard_nw_src;
+    public boolean wildcard_nw_dst;
+    public boolean wildcard_nw_proto;
+    public boolean wildcard_tp_src;
+    public boolean wildcard_tp_dst;
+
+    public int priority = 0;
+
+    public FirewallAction action;
+
+    public enum FirewallAction {
+        /*
+         * DENY: Deny rule
+         * ALLOW: Allow rule
+         */
+        DENY, ALLOW
+    }
+
+    public FirewallRule() {
+        this.in_port = 0; 
+        this.dl_src = 0;
+        this.nw_src_prefix = 0;
+        this.nw_src_maskbits = 0; 
+        this.dl_dst = 0;
+        this.nw_proto = 0;
+        this.tp_src = 0;
+        this.tp_dst = 0;
+        this.dl_dst = 0;
+        this.nw_dst_prefix = 0;
+        this.nw_dst_maskbits = 0; 
+        this.dpid = -1;
+        this.wildcard_dpid = true; 
+        this.wildcard_in_port = true; 
+        this.wildcard_dl_src = true; 
+        this.wildcard_dl_dst = true; 
+        this.wildcard_dl_type = true; 
+        this.wildcard_nw_src = true; 
+        this.wildcard_nw_dst = true; 
+        this.wildcard_nw_proto = true; 
+        this.wildcard_tp_src = true; 
+        this.wildcard_tp_dst = true; 
+        this.priority = 0; 
+        this.action = FirewallAction.ALLOW; 
+        this.ruleid = 0; 
+    }
+
+    /**
+     * Generates a unique ID for the instance
+     * 
+     * @return int representing the unique id
+     */
+    public int genID() {
+        int uid = this.hashCode();
+        if (uid < 0) {
+            uid = Math.abs(uid);
+            uid = uid * 15551;
+        }
+        return uid;
+    }
+
+    /**
+     * Comparison method for Collections.sort method
+     * 
+     * @param rule
+     *            the rule to compare with
+     * @return number representing the result of comparison 0 if equal negative
+     *         if less than 'rule' greater than zero if greater priority rule
+     *         than 'rule'
+     */
+    @Override
+    public int compareTo(FirewallRule rule) {
+        return this.priority - rule.priority;
+    }
+
+    /**
+     * Determines if this instance matches an existing rule instance
+     * 
+     * @param r
+     *            : the FirewallRule instance to compare with
+     * @return boolean: true if a match is found
+     **/
+    public boolean isSameAs(FirewallRule r) {
+        if (this.action != r.action
+                || this.wildcard_dl_type != r.wildcard_dl_type
+                || (this.wildcard_dl_type == false && this.dl_type == r.dl_type)
+                || this.wildcard_tp_src != r.wildcard_tp_src
+                || (this.wildcard_tp_src == false && this.tp_src != r.tp_src)
+                || this.wildcard_tp_dst != r.wildcard_tp_dst
+                || (this.wildcard_tp_dst == false &&this.tp_dst != r.tp_dst)
+                || this.wildcard_dpid != r.wildcard_dpid
+                || (this.wildcard_dpid == false && this.dpid != r.dpid)
+                || this.wildcard_in_port != r.wildcard_in_port
+                || (this.wildcard_in_port == false && this.in_port != r.in_port)
+                || this.wildcard_nw_src != r.wildcard_nw_src
+                || (this.wildcard_nw_src == false && (this.nw_src_prefix != r.nw_src_prefix || this.nw_src_maskbits != r.nw_src_maskbits))
+                || this.wildcard_dl_src != r.wildcard_dl_src
+                || (this.wildcard_dl_src == false && this.dl_src != r.dl_src)
+                || this.wildcard_nw_proto != r.wildcard_nw_proto
+                || (this.wildcard_nw_proto == false && this.nw_proto != r.nw_proto)
+                || this.wildcard_nw_dst != r.wildcard_nw_dst
+                || (this.wildcard_nw_dst == false && (this.nw_dst_prefix != r.nw_dst_prefix || this.nw_dst_maskbits != r.nw_dst_maskbits))
+                || this.wildcard_dl_dst != r.wildcard_dl_dst                
+                || (this.wildcard_dl_dst == false && this.dl_dst != r.dl_dst)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Matches this rule to a given flow - incoming packet
+     * 
+     * @param switchDpid
+     *            the Id of the connected switch
+     * @param inPort
+     *            the switch port where the packet originated from
+     * @param packet
+     *            the Ethernet packet that arrives at the switch
+     * @param wildcards
+     *            the pair of wildcards (allow and deny) given by Firewall
+     *            module that is used by the Firewall module's matchWithRule
+     *            method to derive wildcards for the decision to be taken
+     * @return true if the rule matches the given packet-in, false otherwise
+     */
+    public boolean matchesFlow(long switchDpid, short inPort, Ethernet packet,
+            WildcardsPair wildcards) {
+        IPacket pkt = packet.getPayload();
+
+        // dl_type type
+        IPv4 pkt_ip = null;
+
+        // nw_proto types
+        TCP pkt_tcp = null;
+        UDP pkt_udp = null;
+
+        // tp_src and tp_dst (tp port numbers)
+        short pkt_tp_src = 0;
+        short pkt_tp_dst = 0;
+
+        // switchID matches?
+        if (wildcard_dpid == false && dpid != switchDpid)
+            return false;
+
+        // in_port matches?
+        if (wildcard_in_port == false && in_port != inPort)
+            return false;
+        if (action == FirewallRule.FirewallAction.DENY) {
+            wildcards.drop &= ~OFMatch.OFPFW_IN_PORT;
+        } else {
+            wildcards.allow &= ~OFMatch.OFPFW_IN_PORT;
+        }
+
+        // mac address (src and dst) match?
+        if (wildcard_dl_src == false
+                && dl_src != packet.getSourceMAC().toLong())
+            return false;
+        if (action == FirewallRule.FirewallAction.DENY) {
+            wildcards.drop &= ~OFMatch.OFPFW_DL_SRC;
+        } else {
+            wildcards.allow &= ~OFMatch.OFPFW_DL_SRC;
+        }
+
+        if (wildcard_dl_dst == false
+                && dl_dst != packet.getDestinationMAC().toLong())
+            return false;
+        if (action == FirewallRule.FirewallAction.DENY) {
+            wildcards.drop &= ~OFMatch.OFPFW_DL_DST;
+        } else {
+            wildcards.allow &= ~OFMatch.OFPFW_DL_DST;
+        }
+
+        // dl_type check: ARP, IP
+
+        // if this is not an ARP rule but the pkt is ARP,
+        // return false match - no need to continue protocol specific check
+        if (wildcard_dl_type == false) {
+            if (dl_type == Ethernet.TYPE_ARP) {
+                if (packet.getEtherType() != Ethernet.TYPE_ARP)
+                    return false;
+                else {
+                    if (action == FirewallRule.FirewallAction.DENY) {
+                        wildcards.drop &= ~OFMatch.OFPFW_DL_TYPE;
+                    } else {
+                        wildcards.allow &= ~OFMatch.OFPFW_DL_TYPE;
+                    }
+                }
+            } else if (dl_type == Ethernet.TYPE_IPv4) {
+                if (packet.getEtherType() != Ethernet.TYPE_IPv4)
+                    return false;
+                else {
+                    if (action == FirewallRule.FirewallAction.DENY) {
+                        wildcards.drop &= ~OFMatch.OFPFW_NW_PROTO;
+                    } else {
+                        wildcards.allow &= ~OFMatch.OFPFW_NW_PROTO;
+                    }
+                    // IP packets, proceed with ip address check
+                    pkt_ip = (IPv4) pkt;
+
+                    // IP addresses (src and dst) match?
+                    if (wildcard_nw_src == false
+                            && this.matchIPAddress(nw_src_prefix,
+                                    nw_src_maskbits, pkt_ip.getSourceAddress()) == false)
+                        return false;
+                    if (action == FirewallRule.FirewallAction.DENY) {
+                        wildcards.drop &= ~OFMatch.OFPFW_NW_SRC_ALL;
+                        wildcards.drop |= (nw_src_maskbits << OFMatch.OFPFW_NW_SRC_SHIFT);
+                    } else {
+                        wildcards.allow &= ~OFMatch.OFPFW_NW_SRC_ALL;
+                        wildcards.allow |= (nw_src_maskbits << OFMatch.OFPFW_NW_SRC_SHIFT);
+                    }
+
+                    if (wildcard_nw_dst == false
+                            && this.matchIPAddress(nw_dst_prefix,
+                                    nw_dst_maskbits,
+                                    pkt_ip.getDestinationAddress()) == false)
+                        return false;
+                    if (action == FirewallRule.FirewallAction.DENY) {
+                        wildcards.drop &= ~OFMatch.OFPFW_NW_DST_ALL;
+                        wildcards.drop |= (nw_dst_maskbits << OFMatch.OFPFW_NW_DST_SHIFT);
+                    } else {
+                        wildcards.allow &= ~OFMatch.OFPFW_NW_DST_ALL;
+                        wildcards.allow |= (nw_dst_maskbits << OFMatch.OFPFW_NW_DST_SHIFT);
+                    }
+
+                    // nw_proto check
+                    if (wildcard_nw_proto == false) {
+                        if (nw_proto == IPv4.PROTOCOL_TCP) {
+                            if (pkt_ip.getProtocol() != IPv4.PROTOCOL_TCP)
+                                return false;
+                            else {
+                                pkt_tcp = (TCP) pkt_ip.getPayload();
+                                pkt_tp_src = pkt_tcp.getSourcePort();
+                                pkt_tp_dst = pkt_tcp.getDestinationPort();
+                            }
+                        } else if (nw_proto == IPv4.PROTOCOL_UDP) {
+                            if (pkt_ip.getProtocol() != IPv4.PROTOCOL_UDP)
+                                return false;
+                            else {
+                                pkt_udp = (UDP) pkt_ip.getPayload();
+                                pkt_tp_src = pkt_udp.getSourcePort();
+                                pkt_tp_dst = pkt_udp.getDestinationPort();
+                            }
+                        } else if (nw_proto == IPv4.PROTOCOL_ICMP) {
+                            if (pkt_ip.getProtocol() != IPv4.PROTOCOL_ICMP)
+                                return false;
+                            else {
+                                // nothing more needed for ICMP
+                            }
+                        }
+                        if (action == FirewallRule.FirewallAction.DENY) {
+                            wildcards.drop &= ~OFMatch.OFPFW_NW_PROTO;
+                        } else {
+                            wildcards.allow &= ~OFMatch.OFPFW_NW_PROTO;
+                        }
+
+                        // TCP/UDP source and destination ports match?
+                        if (pkt_tcp != null || pkt_udp != null) {
+                            // does the source port match?
+                            if (tp_src != 0 && tp_src != pkt_tp_src)
+                                return false;
+                            if (action == FirewallRule.FirewallAction.DENY) {
+                                wildcards.drop &= ~OFMatch.OFPFW_TP_SRC;
+                            } else {
+                                wildcards.allow &= ~OFMatch.OFPFW_TP_SRC;
+                            }
+
+                            // does the destination port match?
+                            if (tp_dst != 0 && tp_dst != pkt_tp_dst)
+                                return false;
+                            if (action == FirewallRule.FirewallAction.DENY) {
+                                wildcards.drop &= ~OFMatch.OFPFW_TP_DST;
+                            } else {
+                                wildcards.allow &= ~OFMatch.OFPFW_TP_DST;
+                            }
+                        }
+                    }
+
+                }
+            } else {
+                // non-IP packet - not supported - report no match
+                return false;
+            }
+        }
+        if (action == FirewallRule.FirewallAction.DENY) {
+            wildcards.drop &= ~OFMatch.OFPFW_DL_TYPE;
+        } else {
+            wildcards.allow &= ~OFMatch.OFPFW_DL_TYPE;
+        }
+
+        // all applicable checks passed
+        return true;
+    }
+
+    /**
+     * Determines if rule's CIDR address matches IP address of the packet
+     * 
+     * @param rulePrefix
+     *            prefix part of the CIDR address
+     * @param ruleBits
+     *            the size of mask of the CIDR address
+     * @param packetAddress
+     *            the IP address of the incoming packet to match with
+     * @return true if CIDR address matches the packet's IP address, false
+     *         otherwise
+     */
+    protected boolean matchIPAddress(int rulePrefix, int ruleBits,
+            int packetAddress) {
+        boolean matched = true;
+
+        int rule_iprng = 32 - ruleBits;
+        int rule_ipint = rulePrefix;
+        int pkt_ipint = packetAddress;
+        // if there's a subnet range (bits to be wildcarded > 0)
+        if (rule_iprng > 0) {
+            // right shift bits to remove rule_iprng of LSB that are to be
+            // wildcarded
+            rule_ipint = rule_ipint >> rule_iprng;
+            pkt_ipint = pkt_ipint >> rule_iprng;
+            // now left shift to return to normal range, except that the
+            // rule_iprng number of LSB
+            // are now zeroed
+            rule_ipint = rule_ipint << rule_iprng;
+            pkt_ipint = pkt_ipint << rule_iprng;
+        }
+        // check if we have a match
+        if (rule_ipint != pkt_ipint)
+            matched = false;
+
+        return matched;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 2521;
+        int result = super.hashCode();
+        result = prime * result + (int) dpid;
+        result = prime * result + in_port;
+        result = prime * result + (int) dl_src;
+        result = prime * result + (int) dl_dst;
+        result = prime * result + dl_type;
+        result = prime * result + nw_src_prefix;
+        result = prime * result + nw_src_maskbits;
+        result = prime * result + nw_dst_prefix;
+        result = prime * result + nw_dst_maskbits;
+        result = prime * result + nw_proto;
+        result = prime * result + tp_src;
+        result = prime * result + tp_dst;
+        result = prime * result + action.ordinal();
+        result = prime * result + priority;
+        result = prime * result + (new Boolean(wildcard_dpid)).hashCode();
+        result = prime * result + (new Boolean(wildcard_in_port)).hashCode();
+        result = prime * result + (new Boolean(wildcard_dl_src)).hashCode();
+        result = prime * result + (new Boolean(wildcard_dl_dst)).hashCode();
+        result = prime * result + (new Boolean(wildcard_dl_type)).hashCode();
+        result = prime * result + (new Boolean(wildcard_nw_src)).hashCode();
+        result = prime * result + (new Boolean(wildcard_nw_dst)).hashCode();
+        result = prime * result + (new Boolean(wildcard_nw_proto)).hashCode();
+        result = prime * result + (new Boolean(wildcard_tp_src)).hashCode();
+        result = prime * result + (new Boolean(wildcard_tp_dst)).hashCode();
+        return result;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java
new file mode 100644
index 0000000..7a31d38
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java
@@ -0,0 +1,292 @@
+package net.floodlightcontroller.firewall;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.openflow.util.HexString;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Post;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+
+public class FirewallRulesResource extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(FirewallRulesResource.class);
+
+    @Get("json")
+    public Object handleRequest() {
+        IFirewallService firewall = 
+                (IFirewallService)getContext().getAttributes().
+                get(IFirewallService.class.getCanonicalName());
+
+        return firewall.getRules();
+    }
+
+    /**
+     * Takes a Firewall Rule string in JSON format and parses it into
+     * our firewall rule data structure, then adds it to the firewall.
+     * @param fmJson The Firewall rule entry in JSON format.
+     * @return A string status message
+     */
+    @Post
+    public String store(String fmJson) {
+        IFirewallService firewall = 
+                (IFirewallService)getContext().getAttributes().
+                get(IFirewallService.class.getCanonicalName());
+
+        FirewallRule rule;
+        try {
+            rule = jsonToFirewallRule(fmJson);
+        } catch (IOException e) {
+            log.error("Error parsing firewall rule: " + fmJson, e);
+            e.printStackTrace();
+            return "{\"status\" : \"Error! Could not parse firewall rule, see log for details.\"}";
+        }
+        String status = null;
+        if (checkRuleExists(rule, firewall.getRules())) {
+            status = "Error! A similar firewall rule already exists.";
+            log.error(status);
+        } else {
+            // add rule to firewall
+            firewall.addRule(rule);
+            status = "Rule added";
+        }
+        return ("{\"status\" : \"" + status + "\"}");
+    }
+
+    /**
+     * Takes a Firewall Rule string in JSON format and parses it into
+     * our firewall rule data structure, then deletes it from the firewall.
+     * @param fmJson The Firewall rule entry in JSON format.
+     * @return A string status message
+     */
+    
+    @Delete
+    public String remove(String fmJson) {
+        IFirewallService firewall = 
+                (IFirewallService)getContext().getAttributes().
+                get(IFirewallService.class.getCanonicalName());
+
+        FirewallRule rule;
+        try {
+            rule = jsonToFirewallRule(fmJson);
+        } catch (IOException e) {
+            log.error("Error parsing firewall rule: " + fmJson, e);
+            e.printStackTrace();
+            return "{\"status\" : \"Error! Could not parse firewall rule, see log for details.\"}";
+        }
+        String status = null;
+        boolean exists = false;
+        Iterator<FirewallRule> iter = firewall.getRules().iterator();
+        while (iter.hasNext()) {
+            FirewallRule r = iter.next();
+            if (r.ruleid == rule.ruleid) {
+                exists = true;
+                break;
+            }
+        }
+        if (!exists) {
+            status = "Error! Can't delete, a rule with this ID doesn't exist.";
+            log.error(status);
+        } else {
+            // delete rule from firewall
+            firewall.deleteRule(rule.ruleid);
+            status = "Rule deleted";
+        }
+        return ("{\"status\" : \"" + status + "\"}");
+    }
+
+    /**
+     * Turns a JSON formatted Firewall Rule string into a FirewallRule instance
+     * @param fmJson The JSON formatted static firewall rule
+     * @return The FirewallRule instance
+     * @throws IOException If there was an error parsing the JSON
+     */
+     
+    public static FirewallRule jsonToFirewallRule(String fmJson) throws IOException {
+        FirewallRule rule = new FirewallRule();
+        MappingJsonFactory f = new MappingJsonFactory();
+        JsonParser jp;
+
+        try {
+            jp = f.createJsonParser(fmJson);
+        } catch (JsonParseException e) {
+            throw new IOException(e);
+        }
+
+        jp.nextToken();
+        if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
+            throw new IOException("Expected START_OBJECT");
+        }
+
+        while (jp.nextToken() != JsonToken.END_OBJECT) {
+            if (jp.getCurrentToken() != JsonToken.FIELD_NAME) {
+                throw new IOException("Expected FIELD_NAME");
+            }
+
+            String n = jp.getCurrentName();
+            jp.nextToken();
+            if (jp.getText().equals("")) 
+                continue;
+
+            String tmp;
+            
+            // This is currently only applicable for remove().  In store(), ruleid takes a random number
+            if (n == "ruleid") {
+                rule.ruleid = Integer.parseInt((String)jp.getText());
+            }
+            
+            // This assumes user having dpid info for involved switches
+            else if (n == "switchid") {
+                tmp = jp.getText();
+                if (tmp.equalsIgnoreCase("-1") == false) {
+                    // user inputs hex format dpid 
+                    rule.dpid = HexString.toLong(tmp);                    
+                    rule.wildcard_dpid = false;
+                }
+            } 
+            
+            else if (n == "src-inport") {
+                rule.in_port = Short.parseShort(jp.getText());
+                rule.wildcard_in_port = false;
+            } 
+            
+            else if (n == "src-mac") {
+                tmp = jp.getText();
+                if (tmp.equalsIgnoreCase("ANY") == false) {
+                    rule.wildcard_dl_src = false;
+                    rule.dl_src = Ethernet.toLong(Ethernet.toMACAddress(tmp));
+                }
+            } 
+            
+            else if (n == "dst-mac") {
+                tmp = jp.getText();
+                if (tmp.equalsIgnoreCase("ANY") == false) {
+                    rule.wildcard_dl_dst = false;
+                    rule.dl_dst = Ethernet.toLong(Ethernet.toMACAddress(tmp));
+                }
+            } 
+            
+            else if (n == "dl-type") {
+                tmp = jp.getText();
+                if (tmp.equalsIgnoreCase("ARP")) {
+                    rule.wildcard_dl_type = false;
+                    rule.dl_type = Ethernet.TYPE_ARP;
+                }
+            } 
+            
+            else if (n == "src-ip") {
+                tmp = jp.getText();
+                if (tmp.equalsIgnoreCase("ANY") == false) {
+                    rule.wildcard_nw_src = false;
+                    rule.wildcard_dl_type = false;
+                    rule.dl_type = Ethernet.TYPE_IPv4;
+                    int[] cidr = IPCIDRToPrefixBits(tmp);
+                    rule.nw_src_prefix = cidr[0];
+                    rule.nw_src_maskbits = cidr[1];
+                }
+            } 
+            
+            else if (n == "dst-ip") {
+                tmp = jp.getText();
+                if (tmp.equalsIgnoreCase("ANY") == false) {
+                    rule.wildcard_nw_dst = false;
+                    rule.wildcard_dl_type = false;
+                    rule.dl_type = Ethernet.TYPE_IPv4;
+                    int[] cidr = IPCIDRToPrefixBits(tmp);
+                    rule.nw_dst_prefix = cidr[0];
+                    rule.nw_dst_maskbits = cidr[1];
+                }
+            } 
+            
+            else if (n == "nw-proto") {
+                tmp = jp.getText();
+                if (tmp.equalsIgnoreCase("TCP")) {
+                    rule.wildcard_nw_proto = false;
+                    rule.nw_proto = IPv4.PROTOCOL_TCP;
+                    rule.wildcard_dl_type = false;
+                    rule.dl_type = Ethernet.TYPE_IPv4;
+                } else if (tmp.equalsIgnoreCase("UDP")) {
+                    rule.wildcard_nw_proto = false;
+                    rule.nw_proto = IPv4.PROTOCOL_UDP;
+                    rule.wildcard_dl_type = false;
+                    rule.dl_type = Ethernet.TYPE_IPv4;
+                } else if (tmp.equalsIgnoreCase("ICMP")) {
+                    rule.wildcard_nw_proto = false;
+                    rule.nw_proto = IPv4.PROTOCOL_ICMP;
+                    rule.wildcard_dl_type = false;
+                    rule.dl_type = Ethernet.TYPE_IPv4;
+                } 
+            } 
+            
+            else if (n == "tp-src") {
+                rule.wildcard_tp_src = false;
+                rule.tp_src = Short.parseShort(jp.getText());
+            } 
+            
+            else if (n == "tp-dst") {
+                rule.wildcard_tp_dst = false;
+                rule.tp_dst = Short.parseShort(jp.getText());
+            } 
+            
+            else if (n == "priority") {
+                rule.priority = Integer.parseInt(jp.getText());
+            } 
+            
+            else if (n == "action") {
+                if (jp.getText().equalsIgnoreCase("allow") == true) {
+                    rule.action = FirewallRule.FirewallAction.ALLOW;
+                } else if (jp.getText().equalsIgnoreCase("deny") == true) {
+                    rule.action = FirewallRule.FirewallAction.DENY;
+                }
+            }
+        }
+
+        return rule;
+    }
+
+    public static int[] IPCIDRToPrefixBits(String cidr) {
+        int ret[] = new int[2];
+
+        // as IP can also be a prefix rather than an absolute address
+        // split it over "/" to get the bit range
+        String[] parts = cidr.split("/");
+        String cidr_prefix = parts[0].trim();
+        int cidr_bits = 0;
+        if (parts.length == 2) {
+            try {
+                cidr_bits = Integer.parseInt(parts[1].trim());
+            } catch (Exception exp) {
+                cidr_bits = 32;
+            }
+        }
+        ret[0] = IPv4.toIPv4Address(cidr_prefix);
+        ret[1] = cidr_bits;
+
+        return ret;
+    }
+
+    public static boolean checkRuleExists(FirewallRule rule, List<FirewallRule> rules) {
+        Iterator<FirewallRule> iter = rules.iterator();
+        while (iter.hasNext()) {
+            FirewallRule r = iter.next();
+
+            // check if we find a similar rule
+            if (rule.isSameAs(r)) {
+                return true;
+            }
+        }
+
+        // no rule matched, so it doesn't exist in the rules
+        return false;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java
new file mode 100644
index 0000000..3a9beab
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.firewall;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+import org.restlet.Context;
+import org.restlet.routing.Router;
+
+public class FirewallWebRoutable implements RestletRoutable {
+    /**
+     * Create the Restlet router and bind to the proper resources.
+     */
+    @Override
+    public Router getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/module/{op}/json", FirewallResource.class);
+        router.attach("/rules/json", FirewallRulesResource.class);
+        return router;
+    }
+
+    /**
+     * Set the base path for the Firewall
+     */
+    @Override
+    public String basePath() {
+        return "/wm/firewall";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java
new file mode 100644
index 0000000..ae9d89f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java
@@ -0,0 +1,56 @@
+package net.floodlightcontroller.firewall;
+
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IFirewallService extends IFloodlightService {
+
+    /**
+     * Enables/disables the firewall.
+     * @param enable Whether to enable or disable the firewall.
+     */
+    public void enableFirewall(boolean enable);
+
+    /**
+     * Returns operational status of the firewall
+     * @return boolean enabled;
+     */
+    public boolean isEnabled();
+ 
+    /**
+     * Returns all of the firewall rules
+     * @return List of all rules
+     */
+    public List<FirewallRule> getRules();
+    
+    /**
+     * Returns the subnet mask
+     * @return subnet mask
+     */
+    public String getSubnetMask();
+    
+    /**
+     * Sets the subnet mask
+     * @param newMask The new subnet mask
+     */
+    public void setSubnetMask(String newMask);
+
+    /**
+     * Returns all of the firewall rules in storage
+     * for debugging and unit-testing purposes
+     * @return List of all rules in storage
+     */
+    public List<Map<String, Object>> getStorageRules();
+
+    /**
+     * Adds a new Firewall rule
+     */
+    public void addRule(FirewallRule rule);
+
+    /**
+     * Deletes a Firewall rule
+     */
+    public void deleteRule(int ruleid);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/RuleWildcardsPair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/RuleWildcardsPair.java
new file mode 100644
index 0000000..3fab409
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/RuleWildcardsPair.java
@@ -0,0 +1,8 @@
+package net.floodlightcontroller.firewall;
+
+import org.openflow.protocol.OFMatch;
+
+public class RuleWildcardsPair {
+    public FirewallRule rule;
+    public int wildcards = OFMatch.OFPFW_ALL;
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/WildcardsPair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/WildcardsPair.java
new file mode 100644
index 0000000..2e5f123
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/WildcardsPair.java
@@ -0,0 +1,8 @@
+package net.floodlightcontroller.firewall;
+
+import org.openflow.protocol.OFMatch;
+
+public class WildcardsPair {
+    public int allow = OFMatch.OFPFW_ALL;
+    public int drop = OFMatch.OFPFW_ALL;
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FCQueryObj.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FCQueryObj.java
new file mode 100644
index 0000000..cce3401
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FCQueryObj.java
@@ -0,0 +1,117 @@
+package net.floodlightcontroller.flowcache;
+
+import java.util.Arrays;
+
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.flowcache.IFlowCacheService.FCQueryEvType;
+
+
+/**
+ * The Class FCQueryObj.
+ */
+public class FCQueryObj {
+
+    /** The caller of the flow cache query. */
+    public IFlowQueryHandler fcQueryHandler;
+    /** The application instance name. */
+    public String applInstName;
+    /** The vlan Id. */
+    public Short[] vlans;
+    /** The destination device. */
+    public IDevice dstDevice;
+    /** The source device. */
+    public IDevice srcDevice;
+    /** The caller name */
+    public String callerName;
+    /** Event type that triggered this flow query submission */
+    public FCQueryEvType evType;
+    /** The caller opaque data. Returned unchanged in the query response
+     * via the callback. The type of this object could be different for
+     * different callers */
+    public Object callerOpaqueObj;
+
+    /**
+     * Instantiates a new flow cache query object
+     */
+    public FCQueryObj(IFlowQueryHandler fcQueryHandler,
+            String        applInstName,
+            Short         vlan,
+            IDevice       srcDevice,
+            IDevice       dstDevice,
+            String        callerName,
+            FCQueryEvType evType,
+            Object        callerOpaqueObj) {
+        this.fcQueryHandler    = fcQueryHandler;
+        this.applInstName     = applInstName;
+        this.srcDevice        = srcDevice;
+        this.dstDevice        = dstDevice;
+        this.callerName       = callerName;
+        this.evType           = evType;
+        this.callerOpaqueObj  = callerOpaqueObj;
+        
+        if (vlan != null) {
+        	this.vlans = new Short[] { vlan };
+        } else {
+	        if (srcDevice != null) {
+	        	this.vlans = srcDevice.getVlanId();
+	        } else if (dstDevice != null) {
+	            this.vlans = dstDevice.getVlanId();
+	        }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "FCQueryObj [fcQueryCaller=" + fcQueryHandler
+                + ", applInstName="
+                + applInstName + ", vlans=" + Arrays.toString(vlans)
+                + ", dstDevice=" + dstDevice + ", srcDevice="
+                + srcDevice + ", callerName=" + callerName + ", evType="
+                + evType + ", callerOpaqueObj=" + callerOpaqueObj + "]";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        FCQueryObj other = (FCQueryObj) obj;
+        if (applInstName == null) {
+            if (other.applInstName != null)
+                return false;
+        } else if (!applInstName.equals(other.applInstName))
+            return false;
+        if (callerName == null) {
+            if (other.callerName != null)
+                return false;
+        } else if (!callerName.equals(other.callerName))
+            return false;
+        if (callerOpaqueObj == null) {
+            if (other.callerOpaqueObj != null)
+                return false;
+        } else if (!callerOpaqueObj.equals(other.callerOpaqueObj))
+            return false;
+        if (dstDevice == null) {
+            if (other.dstDevice != null)
+                return false;
+        } else if (!dstDevice.equals(other.dstDevice))
+            return false;
+        if (evType != other.evType)
+            return false;
+        if (fcQueryHandler != other.fcQueryHandler)
+            return false;
+        if (srcDevice == null) {
+            if (other.srcDevice != null)
+                return false;
+        } else if (!srcDevice.equals(other.srcDevice))
+            return false;
+        if (!Arrays.equals(vlans, other.vlans))
+            return false;
+        return true;
+    }
+    
+    
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowCacheQueryResp.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowCacheQueryResp.java
new file mode 100644
index 0000000..b01aedf
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowCacheQueryResp.java
@@ -0,0 +1,54 @@
+package net.floodlightcontroller.flowcache;
+
+import java.util.ArrayList;
+
+/**
+ * Object to return flows in response to a query message to BigFlowCache.
+ * This object is passed in the flowQueryRespHandler() callback.
+ */
+public class FlowCacheQueryResp {
+
+    /** query object provided by the caller, returned unchanged. */
+    public FCQueryObj  queryObj;
+    /** 
+     * Set to true if more flows could be returned for this query in
+     * additional callbacks. Set of false in the last callback for the
+     * query. 
+     */
+    public boolean     moreFlag;
+    
+    /**
+     * Set to true if the response has been sent to handler
+     */
+    public boolean     hasSent;
+    
+    /** 
+     * The flow list. If there are large number of flows to be returned
+     * then they may be returned in multiple callbacks.
+     */
+    public ArrayList<QRFlowCacheObj> qrFlowCacheObjList;
+
+    /**
+     * Instantiates a new big flow cache query response.
+     *
+     * @param query the flow cache query object as given by the caller of
+     * flow cache submit query API.
+     */
+    public FlowCacheQueryResp(FCQueryObj query) {
+        qrFlowCacheObjList = new ArrayList<QRFlowCacheObj>();
+        queryObj    = query;
+        moreFlag    = false;
+        hasSent     = false;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        String s = queryObj.toString() + "; moreFlasg=" + moreFlag +
+                   "; hasSent=" + hasSent;
+        s += "; FlowCount=" + Integer.toString(qrFlowCacheObjList.size());
+        return s;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java
new file mode 100644
index 0000000..d5d323d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java
@@ -0,0 +1,440 @@
+package net.floodlightcontroller.flowcache;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.ListenerDispatcher;
+import net.floodlightcontroller.core.util.SingletonTask;
+import net.floodlightcontroller.counter.CounterStore;
+import net.floodlightcontroller.counter.ICounter;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.counter.SimpleCounter;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.flowcache.IFlowCacheService.FCQueryEvType;
+import net.floodlightcontroller.flowcache.IFlowReconcileListener;
+import net.floodlightcontroller.flowcache.OFMatchReconcile;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.openflow.protocol.OFType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FlowReconcileManager 
+        implements IFloodlightModule, IFlowReconcileService {
+
+    /** The logger. */
+    private static Logger logger =
+                        LoggerFactory.getLogger(FlowReconcileManager.class);
+    
+    /** Reference to dependent modules */
+    protected IThreadPoolService threadPool;
+    protected ICounterStoreService counterStore;
+
+    /**
+     * The list of flow reconcile listeners that have registered to get
+     * flow reconcile callbacks. Such callbacks are invoked, for example, when
+     * a switch with existing flow-mods joins this controller and those flows
+     * need to be reconciled with the current configuration of the controller.
+     */
+    protected ListenerDispatcher<OFType, IFlowReconcileListener>
+                                               flowReconcileListeners;
+
+    /** A FIFO queue to keep all outstanding flows for reconciliation */
+    Queue<OFMatchReconcile> flowQueue;
+    
+    /** Asynchronous task to feed the flowReconcile pipeline */
+    protected SingletonTask flowReconcileTask;
+    
+    String controllerPktInCounterName;
+    protected SimpleCounter lastPacketInCounter;
+    
+    protected static int MAX_SYSTEM_LOAD_PER_SECOND = 50000;
+    /** a minimum flow reconcile rate so that it won't stave */
+    protected static int MIN_FLOW_RECONCILE_PER_SECOND = 1000;
+    
+    /** once per second */
+    protected static int FLOW_RECONCILE_DELAY_MILLISEC = 10;
+    protected Date lastReconcileTime;
+    
+    /** Config to enable or disable flowReconcile */
+    protected static final String EnableConfigKey = "enable";
+    protected boolean flowReconcileEnabled;
+    
+    public int flowReconcileThreadRunCount;
+    
+    @Override
+    public synchronized void addFlowReconcileListener(
+                IFlowReconcileListener listener) {
+        flowReconcileListeners.addListener(OFType.FLOW_MOD, listener);
+
+        if (logger.isTraceEnabled()) {
+            StringBuffer sb = new StringBuffer();
+            sb.append("FlowMod listeners: ");
+            for (IFlowReconcileListener l :
+                flowReconcileListeners.getOrderedListeners()) {
+                sb.append(l.getName());
+                sb.append(",");
+            }
+            logger.trace(sb.toString());
+        }
+    }
+
+    @Override
+    public synchronized void removeFlowReconcileListener(
+                IFlowReconcileListener listener) {
+        flowReconcileListeners.removeListener(listener);
+    }
+    
+    @Override
+    public synchronized void clearFlowReconcileListeners() {
+        flowReconcileListeners.clearListeners();
+    }
+    
+    /**
+     * Add to-be-reconciled flow to the queue.
+     *
+     * @param ofmRcIn the ofm rc in
+     */
+    public void reconcileFlow(OFMatchReconcile ofmRcIn) {
+        if (ofmRcIn == null) return;
+        
+        // Make a copy before putting on the queue.
+        OFMatchReconcile myOfmRc = new OFMatchReconcile(ofmRcIn);
+    
+        flowQueue.add(myOfmRc);
+    
+        Date currTime = new Date();
+        long delay = 0;
+
+        /** schedule reconcile task immidiately if it has been more than 1 sec
+         *  since the last run. Otherwise, schedule the reconcile task in
+         *  DELAY_MILLISEC.
+         */
+        if (currTime.after(new Date(lastReconcileTime.getTime() + 1000))) {
+            delay = 0;
+        } else {
+            delay = FLOW_RECONCILE_DELAY_MILLISEC;
+        }
+        flowReconcileTask.reschedule(delay, TimeUnit.MILLISECONDS);
+    
+        if (logger.isTraceEnabled()) {
+            logger.trace("Reconciling flow: {}, total: {}",
+                myOfmRc.toString(), flowQueue.size());
+        }
+    }
+    
+    @Override
+    public void updateFlowForDestinationDevice(IDevice device,
+                                            IFlowQueryHandler handler,
+                                            FCQueryEvType fcEvType) {
+        // NO-OP
+    }
+
+    @Override
+    public void updateFlowForSourceDevice(IDevice device,
+                                          IFlowQueryHandler handler,
+                                          FCQueryEvType fcEvType) {
+        // NO-OP
+    }
+    
+    @Override
+    public void flowQueryGenericHandler(FlowCacheQueryResp flowResp) {
+        if (flowResp.queryObj.evType != FCQueryEvType.GET) {
+            OFMatchReconcile ofmRc = new OFMatchReconcile();;
+            /* Re-provision these flows */
+            for (QRFlowCacheObj entry : flowResp.qrFlowCacheObjList) {
+                /* reconcile the flows in entry */
+                entry.toOFMatchReconcile(ofmRc,
+                        flowResp.queryObj.applInstName,
+                        OFMatchReconcile.ReconcileAction.UPDATE_PATH);
+                reconcileFlow(ofmRc);
+            }
+        }
+        return;
+    }
+    
+    // IFloodlightModule
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+            new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFlowReconcileService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService> 
+                                                            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m = 
+            new HashMap<Class<? extends IFloodlightService>,
+                IFloodlightService>();
+        m.put(IFlowReconcileService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> 
+                                                    getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IThreadPoolService.class);
+        l.add(ICounterStoreService.class);
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        threadPool = context.getServiceImpl(IThreadPoolService.class);
+        counterStore = context.getServiceImpl(ICounterStoreService.class);
+    
+        flowQueue = new ConcurrentLinkedQueue<OFMatchReconcile>();
+        flowReconcileListeners = 
+                new ListenerDispatcher<OFType, IFlowReconcileListener>();
+        
+        Map<String, String> configParam = context.getConfigParams(this);
+        String enableValue = configParam.get(EnableConfigKey);
+        // Set flowReconcile default to true
+        flowReconcileEnabled = true;
+        if (enableValue != null &&
+            enableValue.equalsIgnoreCase("false")) {
+            flowReconcileEnabled = false;
+        }
+        
+        flowReconcileThreadRunCount = 0;
+        lastReconcileTime = new Date(0);
+        logger.debug("FlowReconcile is {}", flowReconcileEnabled);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // thread to do flow reconcile
+        ScheduledExecutorService ses = threadPool.getScheduledExecutor();
+        flowReconcileTask = new SingletonTask(ses, new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    if (doReconcile()) {
+                        flowReconcileTask.reschedule(
+                            FLOW_RECONCILE_DELAY_MILLISEC,
+                            TimeUnit.MILLISECONDS);
+                    }
+                } catch (Exception e) {
+                    logger.warn("Exception in doReconcile(): {}",
+                                e.getMessage());
+                    e.printStackTrace();
+                }
+            }
+        });
+        
+        String packetInName = OFType.PACKET_IN.toClass().getName();
+        packetInName = packetInName.substring(packetInName.lastIndexOf('.')+1); 
+        
+        // Construct controller counter for the packet_in
+        controllerPktInCounterName =
+            CounterStore.createCounterName(ICounterStoreService.CONTROLLER_NAME, 
+                                           -1,
+                                           packetInName);
+    }
+    
+    /**
+     * Feed the flows into the flow reconciliation pipeline.
+     * @return true if more flows to be reconciled
+     *         false if no more flows to be reconciled.
+     */
+    protected boolean doReconcile() {
+        if (!flowReconcileEnabled) {
+            return false;
+        }
+    
+        // Record the execution time.
+        lastReconcileTime = new Date();
+    
+        ArrayList<OFMatchReconcile> ofmRcList =
+                        new ArrayList<OFMatchReconcile>();
+        
+        // Get the maximum number of flows that can be reconciled.
+        int reconcileCapacity = getCurrentCapacity();
+        if (logger.isTraceEnabled()) {
+            logger.trace("Reconcile capacity {} flows", reconcileCapacity);
+        }
+        while (!flowQueue.isEmpty() && reconcileCapacity > 0) {
+            OFMatchReconcile ofmRc = flowQueue.poll();
+            reconcileCapacity--;
+            if (ofmRc != null) {
+                ofmRcList.add(ofmRc);
+                if (logger.isTraceEnabled()) {
+                    logger.trace("Add flow {} to be the reconcileList", ofmRc.cookie);
+                }
+            } else {
+                break;
+            }
+        }
+        
+        // Run the flow through all the flow reconcile listeners
+        IFlowReconcileListener.Command retCmd;
+        if (ofmRcList.size() > 0) {
+            List<IFlowReconcileListener> listeners =
+                flowReconcileListeners.getOrderedListeners();
+            if (listeners == null) {
+                if (logger.isTraceEnabled()) {
+                    logger.trace("No flowReconcile listener");
+                }
+                return false;
+            }
+        
+            for (IFlowReconcileListener flowReconciler :
+                flowReconcileListeners.getOrderedListeners()) {
+                if (logger.isTraceEnabled()) {
+                    logger.trace("Reconciling flow: call listener {}",
+                            flowReconciler.getName());
+                }
+                retCmd = flowReconciler.reconcileFlows(ofmRcList);
+                if (retCmd == IFlowReconcileListener.Command.STOP) {
+                    break;
+                }
+            }
+            flowReconcileThreadRunCount++;
+        } else {
+            if (logger.isTraceEnabled()) {
+                logger.trace("No flow to be reconciled.");
+            }
+        }
+        
+        // Return true if there are more flows to be reconciled
+        if (flowQueue.isEmpty()) {
+            return false;
+        } else {
+            if (logger.isTraceEnabled()) {
+                logger.trace("{} more flows to be reconciled.",
+                            flowQueue.size());
+            }
+            return true;
+        }
+    }
+    
+    /**
+     * Compute the maximum number of flows to be reconciled.
+     * 
+     * It computes the packetIn increment from the counter values in
+     * the counter store;
+     * Then computes the rate based on the elapsed time
+     * from the last query;
+     * Then compute the max flow reconcile rate by subtracting the packetIn
+     * rate from the hard-coded max system rate.
+     * If the system rate is reached or less than MIN_FLOW_RECONCILE_PER_SECOND,
+     * set the maximum flow reconcile rate to the MIN_FLOW_RECONCILE_PER_SECOND
+     * to prevent starvation.
+     * Then convert the rate to an absolute number for the
+     * FLOW_RECONCILE_PERIOD.
+     * @return
+     */
+    protected int getCurrentCapacity() {
+        ICounter pktInCounter =
+            counterStore.getCounter(controllerPktInCounterName);
+        int minFlows = MIN_FLOW_RECONCILE_PER_SECOND *
+                        FLOW_RECONCILE_DELAY_MILLISEC / 1000;
+        
+        // If no packetInCounter, then there shouldn't be any flow.
+        if (pktInCounter == null ||
+            pktInCounter.getCounterDate() == null ||
+            pktInCounter.getCounterValue() == null) {
+            logger.debug("counter {} doesn't exist",
+                        controllerPktInCounterName);
+            return minFlows;
+        }
+        
+        // Haven't get any counter yet.
+        if (lastPacketInCounter == null) {
+            logger.debug("First time get the count for {}",
+                        controllerPktInCounterName);
+            lastPacketInCounter = (SimpleCounter)
+            SimpleCounter.createCounter(pktInCounter);
+            return minFlows;
+        }
+        
+        int pktInRate = getPktInRate(pktInCounter, new Date());
+        
+        // Update the last packetInCounter
+        lastPacketInCounter = (SimpleCounter)
+        SimpleCounter.createCounter(pktInCounter);
+        int capacity = minFlows;
+        if ((pktInRate + MIN_FLOW_RECONCILE_PER_SECOND) <=
+                               MAX_SYSTEM_LOAD_PER_SECOND) {
+            capacity = (MAX_SYSTEM_LOAD_PER_SECOND - pktInRate)
+                    * FLOW_RECONCILE_DELAY_MILLISEC / 1000;
+        }
+        
+        if (logger.isTraceEnabled()) {
+            logger.trace("Capacity is {}", capacity);
+        }
+        return capacity;
+    }
+    
+    protected int getPktInRate(ICounter newCnt, Date currentTime) {
+        if (newCnt == null ||
+            newCnt.getCounterDate() == null ||
+            newCnt.getCounterValue() == null) {
+            return 0;
+        }
+    
+        // Somehow the system time is messed up. return max packetIn rate
+        // to reduce the system load.
+        if (newCnt.getCounterDate().before(
+                lastPacketInCounter.getCounterDate())) {
+            logger.debug("Time is going backward. new {}, old {}",
+                    newCnt.getCounterDate(),
+                    lastPacketInCounter.getCounterDate());
+            return MAX_SYSTEM_LOAD_PER_SECOND;
+        }
+    
+        long elapsedTimeInSecond = (currentTime.getTime() -
+                    lastPacketInCounter.getCounterDate().getTime()) / 1000;
+        if (elapsedTimeInSecond == 0) {
+            // This should never happen. Check to avoid division by zero.
+            return 0;
+        }
+    
+        long diff = 0;
+        switch (newCnt.getCounterValue().getType()) {
+            case LONG:
+                long newLong = newCnt.getCounterValue().getLong();
+                long oldLong = lastPacketInCounter.getCounterValue().getLong();
+                if (newLong < oldLong) {
+                    // Roll over event
+                    diff = Long.MAX_VALUE - oldLong + newLong;
+                } else {
+                    diff = newLong - oldLong;
+                }
+                break;
+    
+            case DOUBLE:
+                double newDouble = newCnt.getCounterValue().getDouble();
+                double oldDouble = lastPacketInCounter.getCounterValue().getDouble();
+                if (newDouble < oldDouble) {
+                    // Roll over event
+                    diff = (long)(Double.MAX_VALUE - oldDouble + newDouble);
+                } else {
+                    diff = (long)(newDouble - oldDouble);
+                }
+                break;
+        }
+    
+        return (int)(diff/elapsedTimeInSecond);
+    }
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowCacheService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowCacheService.java
new file mode 100644
index 0000000..8e44ed3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowCacheService.java
@@ -0,0 +1,185 @@
+package net.floodlightcontroller.flowcache;
+
+import org.openflow.protocol.OFMatchWithSwDpid;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.FloodlightContextStore;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+/**
+ * The Interface IFlowCache.
+ * <p>
+ * public interface APIs to Big Switch Flow-Cache Service. Flow-Cache maintains
+ * the network-level flows that are currently deployed in the underlying 
+ * network. The flow cache can be queried using various filters by using the
+ * corresponding APIs.
+ * 
+ * @author subrata
+ *
+ */
+public interface IFlowCacheService extends IFloodlightService {
+
+    public static final String FLOWCACHE_APP_NAME = 
+        "net.floodlightcontroller.flowcache.appName";
+    public static final String FLOWCACHE_APP_INSTANCE_NAME = 
+        "net.floodlightcontroller.flowcache.appInstanceName";
+
+    /**
+     * The flow cache query event type indicating the event that triggered the
+     * query. The callerOpaqueObj can be keyed based on this event type
+     */
+    public static enum FCQueryEvType {
+        /** The GET query. Flows need not be reconciled for this query type */
+        GET,
+        /** A new App was added. */
+        APP_ADDED,
+        /** An App was deleted. */
+        APP_DELETED,
+        /** Interface rule of an app was modified */
+        APP_INTERFACE_RULE_CHANGED,
+        /** Some App configuration was changed */
+        APP_CONFIG_CHANGED,
+        /** An ACL was added */
+        ACL_ADDED,
+        /** An ACL was deleted */
+        ACL_DELETED,
+        /** An ACL rule was added */
+        ACL_RULE_ADDED,
+        /** An ACL rule was deleted */
+        ACL_RULE_DELETED,
+        /** ACL configuration was changed */
+        ACL_CONFIG_CHANGED,
+        /** device had moved to a different port in the network */
+        DEVICE_MOVED,
+        /** device's property had changed, such as tag assignment */
+        DEVICE_PROPERTY_CHANGED,
+        /** Link down */
+        LINK_DOWN,
+        /** Periodic scan of switch flow table */
+        PERIODIC_SCAN,
+    }
+    
+    /**
+     * A FloodlightContextStore object that can be used to interact with the 
+     * FloodlightContext information about flowCache.
+     */
+    public static final FloodlightContextStore<String> fcStore = 
+        new FloodlightContextStore<String>();
+    
+    /**
+     * Submit a flow cache query with query parameters specified in FCQueryObj
+     * object. The query object can be created using one of the newFCQueryObj 
+     * helper functions in IFlowCache interface. 
+     * <p>
+     * The queried flows are returned via the flowQueryRespHandler() callback 
+     * that the caller must implement. The caller can match the query with
+     * the response using unique callerOpaqueData which remains unchanged
+     * in the request and response callback.
+     *
+     * @see  com.bigswitch.floodlight.flowcache#flowQueryRespHandler
+     * @param query the flow cache query object as input
+     * 
+     */
+    public void submitFlowCacheQuery(FCQueryObj query);
+
+    /**
+     * Deactivates all flows in the flow cache for which the source switch
+     * matches the given switchDpid. Note that the flows are NOT deleted
+     * from the cache.
+     *
+     * @param switchDpid Data-path identifier of the source switch
+     */
+    public void deactivateFlowCacheBySwitch(long switchDpid);
+
+    /**
+     * Deletes all flows in the flow cache for which the source switch
+     * matches the given switchDpid. 
+     * 
+     * @param switchDpid Data-path identifier of the source switch
+     */
+    public void deleteFlowCacheBySwitch(long switchDpid);
+
+    /**
+     * Add a flow to the flow-cache - called when a flow-mod is about to be
+     * written to a set of switches. If it returns false then it should not
+     * be written to the switches. If it returns true then the cookie returned
+     * should be used for the flow mod sent to the switches.
+     *
+     * @param appInstName Application instance name
+     * @param ofm openflow match object
+     * @param cookie openflow-mod cookie
+     * @param swPort SwitchPort object
+     * @param priority openflow match priority
+     * @param action action taken on the matched packets (PERMIT or DENY)
+     * @return true:  flow should be written to the switch(es)
+     *         false: flow should not be written to the switch(es). false is
+     *                returned, for example, when the flow was recently
+     *                written to the flow-cache and hence it is dampened to
+     *                avoid frequent writes of the same flow to the switches
+     *                This case can typically arise for the flows written at the
+     *                internal ports as they are heavily wild-carded.
+     */
+    public boolean addFlow(String appInstName, OFMatchWithSwDpid ofm, 
+                           Long cookie, long srcSwDpid, 
+                           short inPort, short priority, byte action);
+
+    /**
+     * Add a flow to the flow-cache - called when a flow-mod is about to be
+     * written to a set of switches. If it returns false then it should not
+     * be written to the switches. If it returns true then the cookie returned
+     * should be used for the flow mod sent to the switches.
+     *
+     * @param cntx the cntx
+     * @param ofm the ofm
+     * @param cookie the cookie
+     * @param swPort the sw port
+     * @param priority the priority
+     * @param action the action
+     * @return true:  flow should be written to the switch(es)
+     * false: flow should not be written to the switch(es). false is
+     * returned, for example, when the flow was recently
+     * written to the flow-cache and hence it is dampened to
+     * avoid frequent writes of the same flow to the switches
+     * This case can typically arise for the flows written at the
+     * internal ports as they are heavily wild-carded.
+     */
+    public boolean addFlow(FloodlightContext cntx, OFMatchWithSwDpid ofm, 
+                           Long cookie, SwitchPort swPort, 
+                           short priority, byte action);
+
+    /**
+     * Move the specified flow from its current application instance to a 
+     * different application instance. This API can be used when a flow moves
+     * to a different application instance when the application instance
+     * configuration changes or when a device moves to a different part in
+     * the network that belongs to a different application instance.
+     * <p>
+     * Note that, if the flow was not found in the current application 
+     * instance then the flow is not moved to the new application instance.
+     * 
+     * @param ofMRc the object containing the flow match and new application
+     * instance name.
+     * @return true is the flow was found in the flow cache in the current 
+     * application instance; false if the flow was not found in the flow-cache
+     * in the current application instance.
+     */
+    public boolean moveFlowToDifferentApplInstName(OFMatchReconcile ofMRc);
+
+    /**
+     * Delete all flow from the specified switch
+     * @param sw
+     */
+    public void deleteAllFlowsAtASourceSwitch(IOFSwitch sw);
+    
+    /**
+     * Post a request to update flowcache from a switch.
+     * This is an asynchronous operation.
+     * It queries the switch for stats and updates the flowcache asynchronously
+     * with the response.
+     * @param swDpid
+     * @param delay_ms
+     */
+    public void querySwitchFlowTable(long swDpid);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowQueryHandler.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowQueryHandler.java
new file mode 100644
index 0000000..5d1b1a9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowQueryHandler.java
@@ -0,0 +1,21 @@
+package net.floodlightcontroller.flowcache;
+
+public interface IFlowQueryHandler {
+    /**
+     * This callback function is called in response to a flow query request
+     * submitted to the flow cache service. The module handling this callback
+     * can be different from the one that submitted the query. In the flow
+     * query object used for submitting the flow query, the identity of the
+     * callback handler is passed. When flow cache service has all or some
+     * of the flows that needs to be returned then this callback is called
+     * for the appropriate module. The respone contains a boolean more flag 
+     * that indicates if there are additional flows that may be returned
+     * via additional callback calls.
+     *
+     * @param resp the response object containing the original flow query 
+     * object, partial or complete list of flows that we queried and some 
+     * metadata such as the more flag described aboce.
+     *
+     */
+    public void flowQueryRespHandler(FlowCacheQueryResp resp);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java
new file mode 100644
index 0000000..f1100ed
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java
@@ -0,0 +1,40 @@
+package net.floodlightcontroller.flowcache;
+
+import java.util.ArrayList;
+
+import net.floodlightcontroller.core.IListener;
+import org.openflow.protocol.OFType;
+
+/**
+ * The Interface IFlowReconciler.
+ *
+ * @author subrata
+ */
+public interface IFlowReconcileListener extends IListener<OFType> {
+    /**
+     * Given an input OFMatch, this method applies the policy of the reconciler
+     * and returns a the same input OFMatch structure modified. Additional
+     * OFMatches, if needed, are returned in OFMatch-list. All the OFMatches
+     * are assumed to have "PERMIT" action.
+     *
+     * @param ofmRcList  input flow matches, to be updated to be consistent with
+     *                   the policies of this reconciler 
+     *                   Additional OFMatch-es can be added to the "list" as
+     *                   needed. 
+     *                   For example after a new ACL application, one flow-match
+     *                   may result in multiple flow-matches
+     *                   The method must also update the ReconcileAction
+     *                   member in ofmRcList entries to indicate if the
+     *                   flow needs to be modified, deleted or left unchanged
+     *                   OR of a new entry is to be added after flow 
+     *                   reconciliation
+     *
+     *
+     * @return   Command.CONTINUE if the OFMatch should be sent to the
+     *           next flow reconciler. 
+     *           Command.STOP if the OFMatch shouldn't be processed
+     *           further. In this case the no reconciled flow-mods would 
+     *           be programmed
+     */
+    public Command reconcileFlows(ArrayList<OFMatchReconcile> ofmRcList);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileService.java
new file mode 100644
index 0000000..f48c4e0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileService.java
@@ -0,0 +1,75 @@
+/**
+ * Provides Flow Reconcile service to other modules that need to reconcile
+ * flows.
+ */
+package net.floodlightcontroller.flowcache;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.flowcache.IFlowCacheService.FCQueryEvType;
+
+public interface IFlowReconcileService extends IFloodlightService {
+    /**
+     * Add a flow reconcile listener
+     * @param listener The module that can reconcile flows
+     */
+    public void addFlowReconcileListener(IFlowReconcileListener listener);
+
+    /**
+     * Remove a flow reconcile listener
+     * @param listener The module that no longer reconcile flows
+     */
+    public void removeFlowReconcileListener(IFlowReconcileListener listener);
+    
+    /**
+     * Remove all flow reconcile listeners
+     */
+    public void clearFlowReconcileListeners();
+    
+    /**
+     * Reconcile flow. Returns false if no modified flow-mod need to be 
+     * programmed if cluster ID is providced then pnly flows in the given 
+     * cluster are reprogrammed
+     *
+     * @param ofmRcIn the ofm rc in
+     */
+    public void reconcileFlow(OFMatchReconcile ofmRcIn);
+    
+    /**
+     * Updates the flows to a device after the device moved to a new location
+     * <p>
+     * Queries the flow-cache to get all the flows destined to the given device.
+     * Reconciles each of these flows by potentially reprogramming them to its
+     * new attachment point
+     *
+     * @param device      device that has moved
+     * @param handler	  handler to process the flows
+     * @param fcEvType    Event type that triggered the update
+     *
+     */
+    public void updateFlowForDestinationDevice(IDevice device,
+            IFlowQueryHandler handler,
+    		FCQueryEvType fcEvType);
+    
+    /**
+     * Updates the flows from a device
+     * <p>
+     * Queries the flow-cache to get all the flows source from the given device.
+     * Reconciles each of these flows by potentially reprogramming them to its
+     * new attachment point
+     *
+     * @param device      device where the flow originates
+     * @param handler	  handler to process the flows
+     * @param fcEvType    Event type that triggered the update
+     *
+     */
+    public void updateFlowForSourceDevice(IDevice device,
+            IFlowQueryHandler handler,
+    		FCQueryEvType fcEvType);
+
+    /**
+     * Generic flow query handler to insert FlowMods into the reconcile pipeline.
+     * @param flowResp
+     */
+    public void flowQueryGenericHandler(FlowCacheQueryResp flowResp);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java
new file mode 100644
index 0000000..68831f4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java
@@ -0,0 +1,84 @@
+package net.floodlightcontroller.flowcache;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import org.openflow.protocol.OFMatchWithSwDpid;
+
+/**
+ * OFMatchReconcile class to indicate result of a flow-reconciliation.
+ */
+public class OFMatchReconcile  {
+ 
+    /**
+     * The enum ReconcileAction. Specifies the result of reconciliation of a 
+     * flow.
+     */
+    public enum ReconcileAction {
+
+        /** Delete the flow-mod from the switch */
+        DROP,
+        /** Leave the flow-mod as-is. */
+        NO_CHANGE,
+        /** Program this new flow mod. */
+        NEW_ENTRY,
+        /** 
+         * Reprogram the flow mod as the path of the flow might have changed,
+         * for example when a host is moved or when a link goes down. */
+        UPDATE_PATH,
+        /* Flow is now in a different BVS */
+        APP_INSTANCE_CHANGED,
+        /* Delete the flow-mod - used to delete, for example, drop flow-mods
+         * when the source and destination are in the same BVS after a 
+         * configuration change */
+        DELETE
+    }
+
+    /** The open flow match after reconciliation. */
+    public OFMatchWithSwDpid ofmWithSwDpid;
+    /** flow mod. priority */
+    public short priority;
+    /** Action of this flow-mod PERMIT or DENY */
+    public byte action;
+    /** flow mod. cookie */
+    public long cookie;
+    /** The application instance name. */
+    public String appInstName;
+    /**
+     * The new application instance name. This is null unless the flow
+     * has moved to a different BVS due to BVS config change or device
+     * move to a different switch port etc.*/
+    public String newAppInstName;
+    /** The reconcile action. */
+    public ReconcileAction rcAction;
+
+    // The context for the reconcile action
+    public FloodlightContext cntx;
+    
+    /**
+     * Instantiates a new oF match reconcile object.
+     */
+    public OFMatchReconcile() {
+        ofmWithSwDpid      = new OFMatchWithSwDpid();
+        rcAction = ReconcileAction.NO_CHANGE;
+        cntx = new FloodlightContext();
+    }
+    
+    public OFMatchReconcile(OFMatchReconcile copy) {
+        ofmWithSwDpid =
+            new OFMatchWithSwDpid(copy.ofmWithSwDpid.getOfMatch(),
+                    copy.ofmWithSwDpid.getSwitchDataPathId());
+        priority = copy.priority;
+        action = copy.action;
+        cookie = copy.cookie;
+        appInstName = copy.appInstName;
+        newAppInstName = copy.newAppInstName;
+        rcAction = copy.rcAction;
+        cntx = new FloodlightContext();
+    }
+    
+    @Override
+    public String toString() {
+        return "OFMatchReconcile [" + ofmWithSwDpid + " priority=" + priority + " action=" + action + 
+                " cookie=" + cookie + " appInstName=" + appInstName + " newAppInstName=" + newAppInstName + 
+                " ReconcileAction=" + rcAction + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwRespKey.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwRespKey.java
new file mode 100644
index 0000000..767ce94
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwRespKey.java
@@ -0,0 +1,42 @@
+package net.floodlightcontroller.flowcache;
+
+public class PendingSwRespKey {
+    long swDpid;
+    int  transId;
+
+    public PendingSwRespKey(long swDpid, int transId) {
+        this.swDpid  = swDpid;
+        this.transId = transId;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 97;
+        Long dpid   = swDpid;
+        Integer tid = transId;
+        return (tid.hashCode()*prime + dpid.hashCode());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof PendingSwRespKey)) {
+            return false;
+        }
+        PendingSwRespKey other = (PendingSwRespKey) obj;
+        if ((swDpid != other.swDpid) || (transId != other.transId)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return Long.toHexString(swDpid)+","+Integer.toString(transId);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwitchResp.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwitchResp.java
new file mode 100644
index 0000000..d6f264f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwitchResp.java
@@ -0,0 +1,24 @@
+package net.floodlightcontroller.flowcache;
+
+import net.floodlightcontroller.flowcache.IFlowCacheService.FCQueryEvType;
+
+/**
+ * The Class PendingSwitchResp. This object is used to track the pending
+ * responses to switch flow table queries.
+ */
+public class PendingSwitchResp {
+    protected FCQueryEvType evType;
+
+    public PendingSwitchResp(
+            FCQueryEvType evType) {
+        this.evType      = evType;
+    }
+    
+    public FCQueryEvType getEvType() {
+        return evType;
+    }
+
+    public void setEvType(FCQueryEvType evType) {
+        this.evType = evType;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/QRFlowCacheObj.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/QRFlowCacheObj.java
new file mode 100644
index 0000000..5121f8b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/QRFlowCacheObj.java
@@ -0,0 +1,67 @@
+package net.floodlightcontroller.flowcache;
+
+
+import org.openflow.protocol.OFMatchWithSwDpid;
+
+/**
+ * Used in BigFlowCacheQueryResp as query result.
+ * Used to return one flow when queried by one of the big flow cache APIs.
+ * One of these QRFlowCacheObj is returned for each combination of
+ * priority and action.
+ *
+ * @author subrata
+ */
+public class QRFlowCacheObj {
+
+    /** The open flow match object. */
+    public OFMatchWithSwDpid ofmWithSwDpid;
+    /** The flow-mod priority. */
+    public short   priority;
+    /** flow-mod cookie */
+    public long    cookie;
+    /** The action - PERMIT or DENY. */
+    public byte    action;
+    /** The reserved byte to align with 8 bytes. */
+    public byte    reserved;
+
+    /**
+     * Instantiates a new flow cache query object.
+     *
+     * @param priority the priority
+     * @param action the action
+     */
+    public QRFlowCacheObj(short priority, byte action, long cookie) {
+        ofmWithSwDpid = new OFMatchWithSwDpid();
+        this.action   = action;
+        this.priority = priority;
+        this.cookie   = cookie;
+    }
+
+    /**
+     * Populate a given OFMatchReconcile object from the values of this
+     * class.
+     *
+     * @param ofmRc the given OFMatchReconcile object
+     * @param appInstName the application instance name
+     * @param rcAction the reconcile action
+     */
+    public   void toOFMatchReconcile(OFMatchReconcile ofmRc,
+                            String appInstName, OFMatchReconcile.ReconcileAction rcAction) {
+        ofmRc.ofmWithSwDpid   = ofmWithSwDpid; // not copying
+        ofmRc.appInstName     = appInstName;
+        ofmRc.rcAction        = rcAction;
+        ofmRc.priority        = priority;
+        ofmRc.cookie          = cookie;
+        ofmRc.action          = action;
+    }
+    
+    @Override
+    public String toString() {
+        String str = "ofmWithSwDpid: " + this.ofmWithSwDpid.toString() + " ";
+        str += "priority: " + this.priority + " ";
+        str += "cookie: " + this.cookie + " ";
+        str += "action: " + this.action + " ";
+        str += "reserved: " + this.reserved + " ";
+        return str;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java
new file mode 100644
index 0000000..3fc7ae9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java
@@ -0,0 +1,453 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.forwarding;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.AppCookie;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.routing.ForwardingBase;
+import net.floodlightcontroller.routing.IRoutingDecision;
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.topology.ITopologyService;
+
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@LogMessageCategory("Flow Programming")
+public class Forwarding extends ForwardingBase implements IFloodlightModule {
+    protected static Logger log = LoggerFactory.getLogger(Forwarding.class);
+
+    @Override
+    @LogMessageDoc(level="ERROR",
+                   message="Unexpected decision made for this packet-in={}",
+                   explanation="An unsupported PacketIn decision has been " +
+                   		"passed to the flow programming component",
+                   recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, 
+                                          FloodlightContext cntx) {
+        Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, 
+                                   IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+        
+        // If a decision has been made we obey it
+        // otherwise we just forward
+        if (decision != null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Forwaring decision={} was made for PacketIn={}",
+                        decision.getRoutingAction().toString(),
+                        pi);
+            }
+            
+            switch(decision.getRoutingAction()) {
+                case NONE:
+                    // don't do anything
+                    return Command.CONTINUE;
+                case FORWARD_OR_FLOOD:
+                case FORWARD:
+                    doForwardFlow(sw, pi, cntx, false);
+                    return Command.CONTINUE;
+                case MULTICAST:
+                    // treat as broadcast
+                    doFlood(sw, pi, cntx);
+                    return Command.CONTINUE;
+                case DROP:
+                    doDropFlow(sw, pi, decision, cntx);
+                    return Command.CONTINUE;
+                default:
+                    log.error("Unexpected decision made for this packet-in={}",
+                            pi, decision.getRoutingAction());
+                    return Command.CONTINUE;
+            }
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("No decision was made for PacketIn={}, forwarding",
+                        pi);
+            }
+            
+            if (eth.isBroadcast() || eth.isMulticast()) {
+                // For now we treat multicast as broadcast
+                doFlood(sw, pi, cntx);
+            } else {
+                doForwardFlow(sw, pi, cntx, false);
+            }
+        }
+        
+        return Command.CONTINUE;
+    }
+    
+    @LogMessageDoc(level="ERROR",
+            message="Failure writing drop flow mod",
+            explanation="An I/O error occured while trying to write a " +
+            		"drop flow mod to a switch",
+            recommendation=LogMessageDoc.CHECK_SWITCH)
+    protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) {
+        // initialize match structure and populate it using the packet
+        OFMatch match = new OFMatch();
+        match.loadFromPacket(pi.getPacketData(), pi.getInPort());
+        if (decision.getWildcards() != null) {
+            match.setWildcards(decision.getWildcards());
+        }
+        
+        // Create flow-mod based on packet-in and src-switch
+        OFFlowMod fm =
+                (OFFlowMod) floodlightProvider.getOFMessageFactory()
+                                              .getMessage(OFType.FLOW_MOD);
+        List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to
+                                                            // drop
+        long cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0);
+        
+        fm.setCookie(cookie)
+          .setHardTimeout((short) 0)
+          .setIdleTimeout((short) 5)
+          .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+          .setMatch(match)
+          .setActions(actions)
+          .setLengthU(OFFlowMod.MINIMUM_LENGTH); // +OFActionOutput.MINIMUM_LENGTH);
+
+        try {
+            if (log.isDebugEnabled()) {
+                log.debug("write drop flow-mod sw={} match={} flow-mod={}",
+                          new Object[] { sw, match, fm });
+            }
+            messageDamper.write(sw, fm, cntx);
+        } catch (IOException e) {
+            log.error("Failure writing drop flow mod", e);
+        }
+    }
+    
+    protected void doForwardFlow(IOFSwitch sw, OFPacketIn pi, 
+                                 FloodlightContext cntx,
+                                 boolean requestFlowRemovedNotifn) {    
+        OFMatch match = new OFMatch();
+        match.loadFromPacket(pi.getPacketData(), pi.getInPort());
+
+        // Check if we have the location of the destination
+        IDevice dstDevice = 
+                IDeviceService.fcStore.
+                    get(cntx, IDeviceService.CONTEXT_DST_DEVICE);
+        
+        if (dstDevice != null) {
+            IDevice srcDevice =
+                    IDeviceService.fcStore.
+                        get(cntx, IDeviceService.CONTEXT_SRC_DEVICE);
+            Long srcIsland = topology.getL2DomainId(sw.getId());
+            
+            if (srcDevice == null) {
+                log.debug("No device entry found for source device");
+                return;
+            }
+            if (srcIsland == null) {
+                log.debug("No openflow island found for source {}/{}", 
+                          sw.getStringId(), pi.getInPort());
+                return;
+            }
+
+            // Validate that we have a destination known on the same island
+            // Validate that the source and destination are not on the same switchport
+            boolean on_same_island = false;
+            boolean on_same_if = false;
+            for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) {
+                long dstSwDpid = dstDap.getSwitchDPID();
+                Long dstIsland = topology.getL2DomainId(dstSwDpid);
+                if ((dstIsland != null) && dstIsland.equals(srcIsland)) {
+                    on_same_island = true;
+                    if ((sw.getId() == dstSwDpid) &&
+                        (pi.getInPort() == dstDap.getPort())) {
+                        on_same_if = true;
+                    }
+                    break;
+                }
+            }
+            
+            if (!on_same_island) {
+                // Flood since we don't know the dst device
+                if (log.isTraceEnabled()) {
+                    log.trace("No first hop island found for destination " + 
+                              "device {}, Action = flooding", dstDevice);
+                }
+                doFlood(sw, pi, cntx);
+                return;
+            }            
+            
+            if (on_same_if) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Both source and destination are on the same " + 
+                              "switch/port {}/{}, Action = NOP", 
+                              sw.toString(), pi.getInPort());
+                }
+                return;
+            }
+
+            // Install all the routes where both src and dst have attachment
+            // points.  Since the lists are stored in sorted order we can 
+            // traverse the attachment points in O(m+n) time
+            SwitchPort[] srcDaps = srcDevice.getAttachmentPoints();
+            Arrays.sort(srcDaps, clusterIdComparator);
+            SwitchPort[] dstDaps = dstDevice.getAttachmentPoints();
+            Arrays.sort(dstDaps, clusterIdComparator);
+
+            int iSrcDaps = 0, iDstDaps = 0;
+
+            while ((iSrcDaps < srcDaps.length) && (iDstDaps < dstDaps.length)) {
+                SwitchPort srcDap = srcDaps[iSrcDaps];
+                SwitchPort dstDap = dstDaps[iDstDaps];
+                Long srcCluster = 
+                        topology.getL2DomainId(srcDap.getSwitchDPID());
+                Long dstCluster = 
+                        topology.getL2DomainId(dstDap.getSwitchDPID());
+
+                int srcVsDest = srcCluster.compareTo(dstCluster);
+                if (srcVsDest == 0) {
+                    if (!srcDap.equals(dstDap) && 
+                        (srcCluster != null) && 
+                        (dstCluster != null)) {
+                        Route route = 
+                                routingEngine.getRoute(srcDap.getSwitchDPID(),
+                                                       (short)srcDap.getPort(),
+                                                       dstDap.getSwitchDPID(),
+                                                       (short)dstDap.getPort());
+                        if (route != null) {
+                            if (log.isTraceEnabled()) {
+                                log.trace("pushRoute match={} route={} " + 
+                                          "destination={}:{}",
+                                          new Object[] {match, route, 
+                                                        dstDap.getSwitchDPID(),
+                                                        dstDap.getPort()});
+                            }
+                            long cookie = 
+                                    AppCookie.makeCookie(FORWARDING_APP_ID, 0);
+                            
+                         // if there is prior routing decision use wildcard                                                     
+                            Integer wildcard_hints = null;
+                            IRoutingDecision decision = null;
+                            if (cntx != null) {
+                                decision = IRoutingDecision.rtStore
+                                        .get(cntx,
+                                                IRoutingDecision.CONTEXT_DECISION);
+                            }
+                            if (decision != null) {
+                                wildcard_hints = decision.getWildcards();
+                            } else {
+                            	// L2 only wildcard if there is no prior route decision
+                                wildcard_hints = ((Integer) sw
+                                        .getAttribute(IOFSwitch.PROP_FASTWILDCARDS))
+                                        .intValue()
+                                        & ~OFMatch.OFPFW_IN_PORT
+                                        & ~OFMatch.OFPFW_DL_VLAN
+                                        & ~OFMatch.OFPFW_DL_SRC
+                                        & ~OFMatch.OFPFW_DL_DST
+                                        & ~OFMatch.OFPFW_NW_SRC_MASK
+                                        & ~OFMatch.OFPFW_NW_DST_MASK;
+                            }
+
+                            pushRoute(route, match, wildcard_hints, pi, sw.getId(), cookie, 
+                                      cntx, requestFlowRemovedNotifn, false,
+                                      OFFlowMod.OFPFC_ADD);
+                        }
+                    }
+                    iSrcDaps++;
+                    iDstDaps++;
+                } else if (srcVsDest < 0) {
+                    iSrcDaps++;
+                } else {
+                    iDstDaps++;
+                }
+            }
+        } else {
+            // Flood since we don't know the dst device
+            doFlood(sw, pi, cntx);
+        }
+    }
+
+    /**
+     * Creates a OFPacketOut with the OFPacketIn data that is flooded on all ports unless 
+     * the port is blocked, in which case the packet will be dropped.
+     * @param sw The switch that receives the OFPacketIn
+     * @param pi The OFPacketIn that came to the switch
+     * @param cntx The FloodlightContext associated with this OFPacketIn
+     */
+    @LogMessageDoc(level="ERROR",
+                   message="Failure writing PacketOut " +
+                   		"switch={switch} packet-in={packet-in} " +
+                   		"packet-out={packet-out}",
+                   explanation="An I/O error occured while writing a packet " +
+                   		"out message to the switch",
+                   recommendation=LogMessageDoc.CHECK_SWITCH)
+    protected void doFlood(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
+        if (topology.isIncomingBroadcastAllowed(sw.getId(),
+                                                pi.getInPort()) == false) {
+            if (log.isTraceEnabled()) {
+                log.trace("doFlood, drop broadcast packet, pi={}, " + 
+                          "from a blocked port, srcSwitch=[{},{}], linkInfo={}",
+                          new Object[] {pi, sw.getId(),pi.getInPort()});
+            }
+            return;
+        }
+
+        // Set Action to flood
+        OFPacketOut po = 
+            (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
+        List<OFAction> actions = new ArrayList<OFAction>();
+        if (sw.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD)) {
+            actions.add(new OFActionOutput(OFPort.OFPP_FLOOD.getValue(), 
+                                           (short)0xFFFF));
+        } else {
+            actions.add(new OFActionOutput(OFPort.OFPP_ALL.getValue(), 
+                                           (short)0xFFFF));
+        }
+        po.setActions(actions);
+        po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
+
+        // set buffer-id, in-port and packet-data based on packet-in
+        short poLength = (short)(po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH);
+        po.setBufferId(pi.getBufferId());
+        po.setInPort(pi.getInPort());
+        if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
+            byte[] packetData = pi.getPacketData();
+            poLength += packetData.length;
+            po.setPacketData(packetData);
+        }
+        po.setLength(poLength);
+        
+        try {
+            if (log.isTraceEnabled()) {
+                log.trace("Writing flood PacketOut switch={} packet-in={} packet-out={}",
+                          new Object[] {sw, pi, po});
+            }
+            messageDamper.write(sw, po, cntx);
+        } catch (IOException e) {
+            log.error("Failure writing PacketOut switch={} packet-in={} packet-out={}",
+                    new Object[] {sw, pi, po}, e);
+        }            
+
+        return;
+    }
+    
+    // IFloodlightModule methods
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        // We don't export any services
+        return null;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        // We don't have any services
+        return null;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        l.add(IDeviceService.class);
+        l.add(IRoutingService.class);
+        l.add(ITopologyService.class);
+        l.add(ICounterStoreService.class);
+        return l;
+    }
+
+    @Override
+    @LogMessageDocs({
+        @LogMessageDoc(level="WARN",
+                message="Error parsing flow idle timeout, " +
+                        "using default of {number} seconds",
+                explanation="The properties file contains an invalid " +
+                        "flow idle timeout",
+                recommendation="Correct the idle timeout in the " +
+                        "properties file."),
+        @LogMessageDoc(level="WARN",
+                message="Error parsing flow hard timeout, " +
+                        "using default of {number} seconds",
+                explanation="The properties file contains an invalid " +
+                            "flow hard timeout",
+                recommendation="Correct the hard timeout in the " +
+                                "properties file.")
+    })
+    public void init(FloodlightModuleContext context) throws FloodlightModuleException {
+        super.init();
+        this.floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
+        this.deviceManager = context.getServiceImpl(IDeviceService.class);
+        this.routingEngine = context.getServiceImpl(IRoutingService.class);
+        this.topology = context.getServiceImpl(ITopologyService.class);
+        this.counterStore = context.getServiceImpl(ICounterStoreService.class);
+        
+        // read our config options
+        Map<String, String> configOptions = context.getConfigParams(this);
+        try {
+            String idleTimeout = configOptions.get("idletimeout");
+            if (idleTimeout != null) {
+                FLOWMOD_DEFAULT_IDLE_TIMEOUT = Short.parseShort(idleTimeout);
+            }
+        } catch (NumberFormatException e) {
+            log.warn("Error parsing flow idle timeout, " +
+            		 "using default of {} seconds",
+                     FLOWMOD_DEFAULT_IDLE_TIMEOUT);
+        }
+        try {
+            String hardTimeout = configOptions.get("hardtimeout");
+            if (hardTimeout != null) {
+                FLOWMOD_DEFAULT_HARD_TIMEOUT = Short.parseShort(hardTimeout);
+            }
+        } catch (NumberFormatException e) {
+            log.warn("Error parsing flow hard timeout, " +
+            		 "using default of {} seconds",
+                     FLOWMOD_DEFAULT_HARD_TIMEOUT);
+        }
+        log.debug("FlowMod idle timeout set to {} seconds", 
+                  FLOWMOD_DEFAULT_IDLE_TIMEOUT);
+        log.debug("FlowMod hard timeout set to {} seconds", 
+                  FLOWMOD_DEFAULT_HARD_TIMEOUT);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        super.startUp();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/hub/Hub.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/hub/Hub.java
new file mode 100644
index 0000000..3618351
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/hub/Hub.java
@@ -0,0 +1,144 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.hub;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.U16;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - 04/04/10
+ */
+public class Hub implements IFloodlightModule, IOFMessageListener {
+    protected static Logger log = LoggerFactory.getLogger(Hub.class);
+
+    protected IFloodlightProviderService floodlightProvider;
+
+    /**
+     * @param floodlightProvider the floodlightProvider to set
+     */
+    public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
+        this.floodlightProvider = floodlightProvider;
+    }
+
+    @Override
+    public String getName() {
+        return Hub.class.getPackage().getName();
+    }
+
+    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+        OFPacketIn pi = (OFPacketIn) msg;
+        OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory()
+                .getMessage(OFType.PACKET_OUT);
+        po.setBufferId(pi.getBufferId())
+            .setInPort(pi.getInPort());
+
+        // set actions
+        OFActionOutput action = new OFActionOutput()
+            .setPort((short) OFPort.OFPP_FLOOD.getValue());
+        po.setActions(Collections.singletonList((OFAction)action));
+        po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
+
+        // set data if is is included in the packetin
+        if (pi.getBufferId() == 0xffffffff) {
+            byte[] packetData = pi.getPacketData();
+            po.setLength(U16.t(OFPacketOut.MINIMUM_LENGTH
+                    + po.getActionsLength() + packetData.length));
+            po.setPacketData(packetData);
+        } else {
+            po.setLength(U16.t(OFPacketOut.MINIMUM_LENGTH
+                    + po.getActionsLength()));
+        }
+        try {
+            sw.write(po, cntx);
+        } catch (IOException e) {
+            log.error("Failure writing PacketOut", e);
+        }
+
+        return Command.CONTINUE;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        return false;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return false;
+    }
+
+    // IFloodlightModule
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        // We don't provide any services, return null
+        return null;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        // We don't provide any services, return null
+        return null;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        floodlightProvider =
+                context.getServiceImpl(IFloodlightProviderService.class);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonDebugInterface.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonDebugInterface.java
new file mode 100644
index 0000000..19a97b5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonDebugInterface.java
@@ -0,0 +1,68 @@
+package net.floodlightcontroller.jython;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public class JythonDebugInterface implements IFloodlightModule {
+    protected static Logger log = LoggerFactory.getLogger(JythonDebugInterface.class);
+    protected JythonServer debug_server;
+    protected static int JYTHON_PORT = 6655;
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        // We don't export services
+        return null;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        // We don't export services
+        return null;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        // We don't have any dependencies
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+             throws FloodlightModuleException {
+        // no-op
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        Map<String, Object> locals = new HashMap<String, Object>();     
+        // add all existing module references to the debug server
+        for (Class<? extends IFloodlightService> s : context.getAllServices()) {
+            // Put only the last part of the name
+            String[] bits = s.getCanonicalName().split("\\.");
+            String name = bits[bits.length-1];
+            locals.put(name, context.getServiceImpl(s));
+        }
+        
+        // read our config options
+        Map<String, String> configOptions = context.getConfigParams(this);
+        int port = JYTHON_PORT;
+        String portNum = configOptions.get("port");
+        if (portNum != null) {
+            port = Integer.parseInt(portNum);
+        }
+        
+        JythonServer debug_server = new JythonServer(port, locals);
+        debug_server.start();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonServer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonServer.java
new file mode 100644
index 0000000..fc35b15
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonServer.java
@@ -0,0 +1,63 @@
+package net.floodlightcontroller.jython;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.python.util.PythonInterpreter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class starts a thread that runs a jython interpreter that
+ * can be used for debug (or even development).
+ *
+ * @author mandeepdhami
+ *
+ */
+public class JythonServer extends Thread {
+    protected static Logger log = LoggerFactory.getLogger(JythonServer.class);
+
+	int port;
+	Map<String, Object> locals;
+	
+	/**
+	 * @param port_ Port to use for jython server
+	 * @param locals_ Locals to add to the interpreters top level name space
+	 */
+	public JythonServer(int port_, Map<String, Object> locals_) {
+		this.port = port_ ;
+		this.locals = locals_;
+		if (this.locals == null) {
+			this.locals = new HashMap<String, Object>();
+		}
+		this.locals.put("log", JythonServer.log);
+		this.setName("debugserver");
+	}
+
+    /**
+     * The main thread for this class invoked by Thread.run()
+     *
+     * @see java.lang.Thread#run()
+     */
+    public void run() {
+        PythonInterpreter p = new PythonInterpreter();
+        for (String name : this.locals.keySet()) {
+            p.set(name, this.locals.get(name));
+        }
+
+        URL jarUrl = JythonServer.class.getProtectionDomain().getCodeSource().getLocation();
+        String jarPath = jarUrl.getPath();
+        if (jarUrl.getProtocol().equals("file")) {
+            // If URL is of type file, assume that we are in dev env and set path to python dir.
+            // else use the jar file as is
+            jarPath = jarPath + "../../src/main/python/";
+        }
+
+        p.exec("import sys");
+        p.exec("sys.path.append('" + jarPath + "')");
+        p.exec("from debugserver import run_server");
+        p.exec("run_server(" + this.port + ", '0.0.0.0', locals())");
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java
new file mode 100644
index 0000000..71f6625
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java
@@ -0,0 +1,15 @@
+package net.floodlightcontroller.learningswitch;
+
+import java.util.Map;
+
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.types.MacVlanPair;
+
+public interface ILearningSwitchService extends IFloodlightService {
+    /**
+     * Returns the LearningSwitch's learned host table
+     * @return The learned host table
+     */
+    public Map<IOFSwitch, Map<MacVlanPair,Short>> getTable();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java
new file mode 100644
index 0000000..005708d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java
@@ -0,0 +1,508 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * Floodlight
+ * A BSD licensed, Java based OpenFlow controller
+ *
+ * Floodlight is a Java based OpenFlow controller originally written by David Erickson at Stanford
+ * University. It is available under the BSD license.
+ *
+ * For documentation, forums, issue tracking and more visit:
+ *
+ * http://www.openflowhub.org/display/Floodlight/Floodlight+Home
+ **/
+
+package net.floodlightcontroller.learningswitch;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.types.MacVlanPair;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.restserver.IRestApiService;
+
+import org.openflow.protocol.OFError;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFFlowRemoved;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+import org.openflow.util.LRULinkedHashMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LearningSwitch 
+    implements IFloodlightModule, ILearningSwitchService, IOFMessageListener {
+    protected static Logger log = LoggerFactory.getLogger(LearningSwitch.class);
+    
+    // Module dependencies
+    protected IFloodlightProviderService floodlightProvider;
+    protected ICounterStoreService counterStore;
+    protected IRestApiService restApi;
+    
+    // Stores the learned state for each switch
+    protected Map<IOFSwitch, Map<MacVlanPair,Short>> macVlanToSwitchPortMap;
+
+    // flow-mod - for use in the cookie
+    public static final int LEARNING_SWITCH_APP_ID = 1;
+    // LOOK! This should probably go in some class that encapsulates
+    // the app cookie management
+    public static final int APP_ID_BITS = 12;
+    public static final int APP_ID_SHIFT = (64 - APP_ID_BITS);
+    public static final long LEARNING_SWITCH_COOKIE = (long) (LEARNING_SWITCH_APP_ID & ((1 << APP_ID_BITS) - 1)) << APP_ID_SHIFT;
+    
+    // more flow-mod defaults 
+    protected static final short IDLE_TIMEOUT_DEFAULT = 5;
+    protected static final short HARD_TIMEOUT_DEFAULT = 0;
+    protected static final short PRIORITY_DEFAULT = 100;
+    
+    // for managing our map sizes
+    protected static final int MAX_MACS_PER_SWITCH  = 1000;    
+
+    // normally, setup reverse flow as well. Disable only for using cbench for comparison with NOX etc.
+    protected static final boolean LEARNING_SWITCH_REVERSE_FLOW = true;
+    
+    /**
+     * @param floodlightProvider the floodlightProvider to set
+     */
+    public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
+        this.floodlightProvider = floodlightProvider;
+    }
+    
+    @Override
+    public String getName() {
+        return "learningswitch";
+    }
+
+    /**
+     * Adds a host to the MAC/VLAN->SwitchPort mapping
+     * @param sw The switch to add the mapping to
+     * @param mac The MAC address of the host to add
+     * @param vlan The VLAN that the host is on
+     * @param portVal The switchport that the host is on
+     */
+    protected void addToPortMap(IOFSwitch sw, long mac, short vlan, short portVal) {
+        Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
+        
+        if (vlan == (short) 0xffff) {
+            // OFMatch.loadFromPacket sets VLAN ID to 0xffff if the packet contains no VLAN tag;
+            // for our purposes that is equivalent to the default VLAN ID 0
+            vlan = 0;
+        }
+        
+        if (swMap == null) {
+            // May be accessed by REST API so we need to make it thread safe
+            swMap = Collections.synchronizedMap(new LRULinkedHashMap<MacVlanPair,Short>(MAX_MACS_PER_SWITCH));
+            macVlanToSwitchPortMap.put(sw, swMap);
+        }
+        swMap.put(new MacVlanPair(mac, vlan), portVal);
+    }
+    
+    /**
+     * Removes a host from the MAC/VLAN->SwitchPort mapping
+     * @param sw The switch to remove the mapping from
+     * @param mac The MAC address of the host to remove
+     * @param vlan The VLAN that the host is on
+     */
+    protected void removeFromPortMap(IOFSwitch sw, long mac, short vlan) {
+        if (vlan == (short) 0xffff) {
+            vlan = 0;
+        }
+        Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
+        if (swMap != null)
+            swMap.remove(new MacVlanPair(mac, vlan));
+    }
+
+    /**
+     * Get the port that a MAC/VLAN pair is associated with
+     * @param sw The switch to get the mapping from
+     * @param mac The MAC address to get
+     * @param vlan The VLAN number to get
+     * @return The port the host is on
+     */
+    public Short getFromPortMap(IOFSwitch sw, long mac, short vlan) {
+        if (vlan == (short) 0xffff) {
+            vlan = 0;
+        }
+        Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
+        if (swMap != null)
+            return swMap.get(new MacVlanPair(mac, vlan));
+        
+        // if none found
+        return null;
+    }
+    
+    /**
+     * Clears the MAC/VLAN -> SwitchPort map for all switches
+     */
+    public void clearLearnedTable() {
+        macVlanToSwitchPortMap.clear();
+    }
+    
+    /**
+     * Clears the MAC/VLAN -> SwitchPort map for a single switch
+     * @param sw The switch to clear the mapping for
+     */
+    public void clearLearnedTable(IOFSwitch sw) {
+        Map<MacVlanPair, Short> swMap = macVlanToSwitchPortMap.get(sw);
+        if (swMap != null)
+            swMap.clear();
+    }
+    
+    @Override
+    public synchronized Map<IOFSwitch, Map<MacVlanPair,Short>> getTable() {
+        return macVlanToSwitchPortMap;
+    }
+    
+    /**
+     * Writes a OFFlowMod to a switch.
+     * @param sw The switch tow rite the flowmod to.
+     * @param command The FlowMod actions (add, delete, etc).
+     * @param bufferId The buffer ID if the switch has buffered the packet.
+     * @param match The OFMatch structure to write.
+     * @param outPort The switch port to output it to.
+     */
+    private void writeFlowMod(IOFSwitch sw, short command, int bufferId,
+            OFMatch match, short outPort) {
+        // from openflow 1.0 spec - need to set these on a struct ofp_flow_mod:
+        // struct ofp_flow_mod {
+        //    struct ofp_header header;
+        //    struct ofp_match match; /* Fields to match */
+        //    uint64_t cookie; /* Opaque controller-issued identifier. */
+        //
+        //    /* Flow actions. */
+        //    uint16_t command; /* One of OFPFC_*. */
+        //    uint16_t idle_timeout; /* Idle time before discarding (seconds). */
+        //    uint16_t hard_timeout; /* Max time before discarding (seconds). */
+        //    uint16_t priority; /* Priority level of flow entry. */
+        //    uint32_t buffer_id; /* Buffered packet to apply to (or -1).
+        //                           Not meaningful for OFPFC_DELETE*. */
+        //    uint16_t out_port; /* For OFPFC_DELETE* commands, require
+        //                          matching entries to include this as an
+        //                          output port. A value of OFPP_NONE
+        //                          indicates no restriction. */
+        //    uint16_t flags; /* One of OFPFF_*. */
+        //    struct ofp_action_header actions[0]; /* The action length is inferred
+        //                                            from the length field in the
+        //                                            header. */
+        //    };
+           
+        OFFlowMod flowMod = (OFFlowMod) floodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD);
+        flowMod.setMatch(match);
+        flowMod.setCookie(LearningSwitch.LEARNING_SWITCH_COOKIE);
+        flowMod.setCommand(command);
+        flowMod.setIdleTimeout(LearningSwitch.IDLE_TIMEOUT_DEFAULT);
+        flowMod.setHardTimeout(LearningSwitch.HARD_TIMEOUT_DEFAULT);
+        flowMod.setPriority(LearningSwitch.PRIORITY_DEFAULT);
+        flowMod.setBufferId(bufferId);
+        flowMod.setOutPort((command == OFFlowMod.OFPFC_DELETE) ? outPort : OFPort.OFPP_NONE.getValue());
+        flowMod.setFlags((command == OFFlowMod.OFPFC_DELETE) ? 0 : (short) (1 << 0)); // OFPFF_SEND_FLOW_REM
+
+        // set the ofp_action_header/out actions:
+        // from the openflow 1.0 spec: need to set these on a struct ofp_action_output:
+        // uint16_t type; /* OFPAT_OUTPUT. */
+        // uint16_t len; /* Length is 8. */
+        // uint16_t port; /* Output port. */
+        // uint16_t max_len; /* Max length to send to controller. */
+        // type/len are set because it is OFActionOutput,
+        // and port, max_len are arguments to this constructor
+        flowMod.setActions(Arrays.asList((OFAction) new OFActionOutput(outPort, (short) 0xffff)));
+        flowMod.setLength((short) (OFFlowMod.MINIMUM_LENGTH + OFActionOutput.MINIMUM_LENGTH));
+
+        if (log.isTraceEnabled()) {
+            log.trace("{} {} flow mod {}", 
+                      new Object[]{ sw, (command == OFFlowMod.OFPFC_DELETE) ? "deleting" : "adding", flowMod });
+        }
+
+        counterStore.updatePktOutFMCounterStore(sw, flowMod);
+        
+        // and write it out
+        try {
+            sw.write(flowMod, null);
+        } catch (IOException e) {
+            log.error("Failed to write {} to switch {}", new Object[]{ flowMod, sw }, e);
+        }
+    }
+    
+    /**
+     * Writes an OFPacketOut message to a switch.
+     * @param sw The switch to write the PacketOut to.
+     * @param packetInMessage The corresponding PacketIn.
+     * @param egressPort The switchport to output the PacketOut.
+     */
+    private void writePacketOutForPacketIn(IOFSwitch sw, 
+                                          OFPacketIn packetInMessage, 
+                                          short egressPort) {
+        // from openflow 1.0 spec - need to set these on a struct ofp_packet_out:
+        // uint32_t buffer_id; /* ID assigned by datapath (-1 if none). */
+        // uint16_t in_port; /* Packet's input port (OFPP_NONE if none). */
+        // uint16_t actions_len; /* Size of action array in bytes. */
+        // struct ofp_action_header actions[0]; /* Actions. */
+        /* uint8_t data[0]; */ /* Packet data. The length is inferred
+                                  from the length field in the header.
+                                  (Only meaningful if buffer_id == -1.) */
+        
+        OFPacketOut packetOutMessage = (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
+        short packetOutLength = (short)OFPacketOut.MINIMUM_LENGTH; // starting length
+
+        // Set buffer_id, in_port, actions_len
+        packetOutMessage.setBufferId(packetInMessage.getBufferId());
+        packetOutMessage.setInPort(packetInMessage.getInPort());
+        packetOutMessage.setActionsLength((short)OFActionOutput.MINIMUM_LENGTH);
+        packetOutLength += OFActionOutput.MINIMUM_LENGTH;
+        
+        // set actions
+        List<OFAction> actions = new ArrayList<OFAction>(1);      
+        actions.add(new OFActionOutput(egressPort, (short) 0));
+        packetOutMessage.setActions(actions);
+
+        // set data - only if buffer_id == -1
+        if (packetInMessage.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
+            byte[] packetData = packetInMessage.getPacketData();
+            packetOutMessage.setPacketData(packetData); 
+            packetOutLength += (short)packetData.length;
+        }
+        
+        // finally, set the total length
+        packetOutMessage.setLength(packetOutLength);              
+            
+        // and write it out
+        try {
+        	counterStore.updatePktOutFMCounterStore(sw, packetOutMessage);
+            sw.write(packetOutMessage, null);
+        } catch (IOException e) {
+            log.error("Failed to write {} to switch {}: {}", new Object[]{ packetOutMessage, sw, e });
+        }
+    }
+    
+    /**
+     * Processes a OFPacketIn message. If the switch has learned the MAC/VLAN to port mapping
+     * for the pair it will write a FlowMod for. If the mapping has not been learned the 
+     * we will flood the packet.
+     * @param sw
+     * @param pi
+     * @param cntx
+     * @return
+     */
+    private Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
+        // Read in packet data headers by using OFMatch
+        OFMatch match = new OFMatch();
+        match.loadFromPacket(pi.getPacketData(), pi.getInPort());
+        Long sourceMac = Ethernet.toLong(match.getDataLayerSource());
+        Long destMac = Ethernet.toLong(match.getDataLayerDestination());
+        Short vlan = match.getDataLayerVirtualLan();
+        if ((destMac & 0xfffffffffff0L) == 0x0180c2000000L) {
+            if (log.isTraceEnabled()) {
+                log.trace("ignoring packet addressed to 802.1D/Q reserved addr: switch {} vlan {} dest MAC {}",
+                          new Object[]{ sw, vlan, HexString.toHexString(destMac) });
+            }
+            return Command.STOP;
+        }
+        if ((sourceMac & 0x010000000000L) == 0) {
+            // If source MAC is a unicast address, learn the port for this MAC/VLAN
+            this.addToPortMap(sw, sourceMac, vlan, pi.getInPort());
+        }
+        
+        // Now output flow-mod and/or packet
+        Short outPort = getFromPortMap(sw, destMac, vlan);
+        if (outPort == null) {
+            // If we haven't learned the port for the dest MAC/VLAN, flood it
+            // Don't flood broadcast packets if the broadcast is disabled.
+            // XXX For LearningSwitch this doesn't do much. The sourceMac is removed
+            //     from port map whenever a flow expires, so you would still see
+            //     a lot of floods.
+            this.writePacketOutForPacketIn(sw, pi, OFPort.OFPP_FLOOD.getValue());
+        } else if (outPort == match.getInputPort()) {
+            log.trace("ignoring packet that arrived on same port as learned destination:"
+                    + " switch {} vlan {} dest MAC {} port {}",
+                    new Object[]{ sw, vlan, HexString.toHexString(destMac), outPort });
+        } else {
+            // Add flow table entry matching source MAC, dest MAC, VLAN and input port
+            // that sends to the port we previously learned for the dest MAC/VLAN.  Also
+            // add a flow table entry with source and destination MACs reversed, and
+            // input and output ports reversed.  When either entry expires due to idle
+            // timeout, remove the other one.  This ensures that if a device moves to
+            // a different port, a constant stream of packets headed to the device at
+            // its former location does not keep the stale entry alive forever.
+            // FIXME: current HP switches ignore DL_SRC and DL_DST fields, so we have to match on
+            // NW_SRC and NW_DST as well
+            match.setWildcards(((Integer)sw.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).intValue()
+                    & ~OFMatch.OFPFW_IN_PORT
+                    & ~OFMatch.OFPFW_DL_VLAN & ~OFMatch.OFPFW_DL_SRC & ~OFMatch.OFPFW_DL_DST
+                    & ~OFMatch.OFPFW_NW_SRC_MASK & ~OFMatch.OFPFW_NW_DST_MASK);
+            this.writeFlowMod(sw, OFFlowMod.OFPFC_ADD, pi.getBufferId(), match, outPort);
+            if (LEARNING_SWITCH_REVERSE_FLOW) {
+                this.writeFlowMod(sw, OFFlowMod.OFPFC_ADD, -1, match.clone()
+                    .setDataLayerSource(match.getDataLayerDestination())
+                    .setDataLayerDestination(match.getDataLayerSource())
+                    .setNetworkSource(match.getNetworkDestination())
+                    .setNetworkDestination(match.getNetworkSource())
+                    .setTransportSource(match.getTransportDestination())
+                    .setTransportDestination(match.getTransportSource())
+                    .setInputPort(outPort),
+                    match.getInputPort());
+            }
+        }
+        return Command.CONTINUE;
+    }
+
+    /**
+     * Processes a flow removed message. We will delete the learned MAC/VLAN mapping from
+     * the switch's table.
+     * @param sw The switch that sent the flow removed message.
+     * @param flowRemovedMessage The flow removed message.
+     * @return Whether to continue processing this message or stop.
+     */
+    private Command processFlowRemovedMessage(IOFSwitch sw, OFFlowRemoved flowRemovedMessage) {
+        if (flowRemovedMessage.getCookie() != LearningSwitch.LEARNING_SWITCH_COOKIE) {
+            return Command.CONTINUE;
+        }
+        if (log.isTraceEnabled()) {
+            log.trace("{} flow entry removed {}", sw, flowRemovedMessage);
+        }
+        OFMatch match = flowRemovedMessage.getMatch();
+        // When a flow entry expires, it means the device with the matching source
+        // MAC address and VLAN either stopped sending packets or moved to a different
+        // port.  If the device moved, we can't know where it went until it sends
+        // another packet, allowing us to re-learn its port.  Meanwhile we remove
+        // it from the macVlanToPortMap to revert to flooding packets to this device.
+        this.removeFromPortMap(sw, Ethernet.toLong(match.getDataLayerSource()),
+            match.getDataLayerVirtualLan());
+        
+        // Also, if packets keep coming from another device (e.g. from ping), the
+        // corresponding reverse flow entry will never expire on its own and will
+        // send the packets to the wrong port (the matching input port of the
+        // expired flow entry), so we must delete the reverse entry explicitly.
+        this.writeFlowMod(sw, OFFlowMod.OFPFC_DELETE, -1, match.clone()
+                .setWildcards(((Integer)sw.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).intValue()
+                        & ~OFMatch.OFPFW_DL_VLAN & ~OFMatch.OFPFW_DL_SRC & ~OFMatch.OFPFW_DL_DST
+                        & ~OFMatch.OFPFW_NW_SRC_MASK & ~OFMatch.OFPFW_NW_DST_MASK)
+                .setDataLayerSource(match.getDataLayerDestination())
+                .setDataLayerDestination(match.getDataLayerSource())
+                .setNetworkSource(match.getNetworkDestination())
+                .setNetworkDestination(match.getNetworkSource())
+                .setTransportSource(match.getTransportDestination())
+                .setTransportDestination(match.getTransportSource()),
+                match.getInputPort());
+        return Command.CONTINUE;
+    }
+    
+    // IOFMessageListener
+    
+    @Override
+    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+        switch (msg.getType()) {
+            case PACKET_IN:
+                return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx);
+            case FLOW_REMOVED:
+                return this.processFlowRemovedMessage(sw, (OFFlowRemoved) msg);
+            case ERROR:
+                log.info("received an error {} from switch {}", (OFError) msg, sw);
+                return Command.CONTINUE;
+            default:
+            	break;
+        }
+        log.error("received an unexpected message {} from switch {}", msg, sw);
+        return Command.CONTINUE;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        return false;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return false;
+    }
+
+    // IFloodlightModule
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(ILearningSwitchService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                    IFloodlightService>();
+        m.put(ILearningSwitchService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        l.add(ICounterStoreService.class);
+        l.add(IRestApiService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        macVlanToSwitchPortMap = 
+                new ConcurrentHashMap<IOFSwitch, Map<MacVlanPair,Short>>();
+        floodlightProvider =
+                context.getServiceImpl(IFloodlightProviderService.class);
+        counterStore =
+                context.getServiceImpl(ICounterStoreService.class);
+        restApi =
+                context.getServiceImpl(IRestApiService.class);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+        floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this);
+        floodlightProvider.addOFMessageListener(OFType.ERROR, this);
+        restApi.addRestletRoutable(new LearningSwitchWebRoutable());
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java
new file mode 100644
index 0000000..19f8bf5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java
@@ -0,0 +1,69 @@
+package net.floodlightcontroller.learningswitch;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.types.MacVlanPair;
+
+import org.openflow.util.HexString;
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LearningSwitchTable extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(LearningSwitchTable.class);
+    
+    protected Map<String, Object> formatTableEntry(MacVlanPair key, short port) {
+        Map<String, Object> entry = new HashMap<String, Object>();
+        entry.put("mac", HexString.toHexString(key.mac));
+        entry.put("vlan", key.vlan);
+        entry.put("port", port);
+        return entry;
+    }
+    
+    protected List<Map<String, Object>> getOneSwitchTable(Map<MacVlanPair, Short> switchMap) {
+        List<Map<String, Object>> switchTable = new ArrayList<Map<String, Object>>();
+        for (Entry<MacVlanPair, Short> entry : switchMap.entrySet()) {
+            switchTable.add(formatTableEntry(entry.getKey(), entry.getValue()));
+        }
+        return switchTable;
+    }
+    
+    @Get("json")
+    public Map<String, List<Map<String, Object>>> getSwitchTableJson() {
+        ILearningSwitchService lsp = 
+                (ILearningSwitchService)getContext().getAttributes().
+                    get(ILearningSwitchService.class.getCanonicalName());
+
+        Map<IOFSwitch, Map<MacVlanPair,Short>> table = lsp.getTable();
+        Map<String, List<Map<String, Object>>> allSwitchTableJson = new HashMap<String, List<Map<String, Object>>>();
+        
+        String switchId = (String) getRequestAttributes().get("switch");
+        if (switchId.toLowerCase().equals("all")) {
+            for (IOFSwitch sw : table.keySet()) {
+                allSwitchTableJson.put(HexString.toHexString(sw.getId()), getOneSwitchTable(table.get(sw)));
+            }
+        } else {
+            try {
+                IFloodlightProviderService floodlightProvider = 
+                        (IFloodlightProviderService)getContext().getAttributes().
+                            get(IFloodlightProviderService.class.getCanonicalName());
+                long dpid = HexString.toLong(switchId);
+                IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+                allSwitchTableJson.put(HexString.toHexString(sw.getId()), getOneSwitchTable(table.get(sw)));
+            } catch (NumberFormatException e) {
+                log.error("Could not decode switch ID = " + switchId);
+                setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+            }
+        }
+            
+        return allSwitchTableJson;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchWebRoutable.java
new file mode 100644
index 0000000..76c30cb
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchWebRoutable.java
@@ -0,0 +1,22 @@
+package net.floodlightcontroller.learningswitch;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class LearningSwitchWebRoutable implements RestletRoutable {
+
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/table/{switch}/json", LearningSwitchTable.class);
+        return router;
+    }
+
+    @Override
+    public String basePath() {
+        return "/wm/learningswitch";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java
new file mode 100644
index 0000000..f172f63
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java
@@ -0,0 +1,162 @@
+package net.floodlightcontroller.linkdiscovery;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.ser.ToStringSerializer;
+import org.openflow.util.HexString;
+
+public interface ILinkDiscovery {
+
+    @JsonSerialize(using=ToStringSerializer.class)
+    public enum UpdateOperation {
+        LINK_UPDATED("Link Updated"),
+        LINK_REMOVED("Link Removed"),
+        SWITCH_UPDATED("Switch Updated"),
+        SWITCH_REMOVED("Switch Removed"),
+        PORT_UP("Port Up"),
+        PORT_DOWN("Port Down");
+        
+        private String value;
+        UpdateOperation(String v) {
+            value = v;
+        }
+        
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
+    public class LDUpdate {
+        protected long src;
+        protected short srcPort;
+        protected long dst;
+        protected short dstPort;
+        protected SwitchType srcType;
+        protected LinkType type;
+        protected UpdateOperation operation;
+
+        public LDUpdate(long src, short srcPort,
+                      long dst, short dstPort,
+                      ILinkDiscovery.LinkType type,
+                      UpdateOperation operation) {
+            this.src = src;
+            this.srcPort = srcPort;
+            this.dst = dst;
+            this.dstPort = dstPort;
+            this.type = type;
+            this.operation = operation;
+        }
+
+        public LDUpdate(LDUpdate old) {
+            this.src = old.src;
+            this.srcPort = old.srcPort;
+            this.dst = old.dst;
+            this.dstPort = old.dstPort;
+            this.srcType = old.srcType;
+            this.type = old.type;
+            this.operation = old.operation;
+        }
+
+        // For updtedSwitch(sw)
+        public LDUpdate(long switchId, SwitchType stype, UpdateOperation oper ){
+            this.operation = oper;
+            this.src = switchId;
+            this.srcType = stype;
+        }
+
+        // For port up or port down.
+        public LDUpdate(long sw, short port, UpdateOperation operation) {
+            this.src = sw;
+            this.srcPort = port;
+            this.operation = operation;
+        }
+
+        public long getSrc() {
+            return src;
+        }
+
+        public short getSrcPort() {
+            return srcPort;
+        }
+
+        public long getDst() {
+            return dst;
+        }
+
+        public short getDstPort() {
+            return dstPort;
+        }
+
+        public SwitchType getSrcType() {
+            return srcType;
+        }
+
+        public LinkType getType() {
+            return type;
+        }
+
+        public UpdateOperation getOperation() {
+            return operation;
+        }
+
+        public void setOperation(UpdateOperation operation) {
+            this.operation = operation;
+        }
+        
+        @Override
+        public String toString() {
+            switch (operation) {
+            case LINK_REMOVED:
+            case LINK_UPDATED:
+                return "LDUpdate [operation=" + operation +
+                        ", src=" + HexString.toHexString(src)
+                        + ", srcPort=" + srcPort
+                        + ", dst=" + HexString.toHexString(dst) 
+                        + ", dstPort=" + dstPort
+                        + ", type=" + type + "]";
+            case PORT_DOWN:
+            case PORT_UP:
+                return "LDUpdate [operation=" + operation +
+                        ", src=" + HexString.toHexString(src)
+                        + ", srcPort=" + srcPort + "]";
+            case SWITCH_REMOVED:
+            case SWITCH_UPDATED:
+                return "LDUpdate [operation=" + operation +
+                        ", src=" + HexString.toHexString(src) + "]";
+            default:
+                return "LDUpdate: Unknown update.";
+            }
+        }
+    }
+
+    public enum SwitchType {
+        BASIC_SWITCH, CORE_SWITCH
+    };
+
+    public enum LinkType {
+        INVALID_LINK {
+        	@Override
+        	public String toString() {
+        		return "invalid";
+        	}
+        }, 
+        DIRECT_LINK{
+        	@Override
+        	public String toString() {
+        		return "internal";
+        	}
+        }, 
+        MULTIHOP_LINK {
+        	@Override
+        	public String toString() {
+        		return "external";
+        	}
+        }, 
+        TUNNEL {
+        	@Override
+        	public String toString() {
+        		return "tunnel";
+        	}
+        }
+    };
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryListener.java
new file mode 100644
index 0000000..35779a2
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryListener.java
@@ -0,0 +1,23 @@
+/**
+ *    Copyright 2011, Big Switch Networks, Inc. 
+ *    Originally created by David Erickson, Stanford University
+ * 
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.linkdiscovery;
+
+public interface ILinkDiscoveryListener extends ILinkDiscovery{
+
+    public void linkDiscoveryUpdate(LDUpdate update);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java
new file mode 100644
index 0000000..4145592
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java
@@ -0,0 +1,84 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.linkdiscovery;
+
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+
+public interface ILinkDiscoveryService extends IFloodlightService {
+    /**
+     * Retrieves a map of all known link connections between OpenFlow switches
+     * and the associated info (valid time, port states) for the link.
+     */
+    public Map<Link, LinkInfo> getLinks();
+
+    /**
+     * Returns link type of a given link
+     * @param info
+     * @return
+     */
+    public ILinkDiscovery.LinkType getLinkType(Link lt, LinkInfo info);
+
+    /**
+     * Returns an unmodifiable map from switch id to a set of all links with it 
+     * as an endpoint.
+     */
+    public Map<Long, Set<Link>> getSwitchLinks();
+
+    /**
+     * Adds a listener to listen for ILinkDiscoveryService messages
+     * @param listener The listener that wants the notifications
+     */
+    public void addListener(ILinkDiscoveryListener listener);
+
+    /**
+     * Retrieves a set of all switch ports on which lldps are suppressed.
+     */
+    public Set<NodePortTuple> getSuppressLLDPsInfo();
+
+    /**
+     * Adds a switch port to suppress lldp set
+     */
+    public void AddToSuppressLLDPs(long sw, short port);
+
+    /**
+     * Removes a switch port from suppress lldp set
+     */
+    public void RemoveFromSuppressLLDPs(long sw, short port);
+
+    /**
+     * Get the set of quarantined ports on a switch
+     */
+    public Set<Short> getQuarantinedPorts(long sw);
+
+    /**
+     * Get the status of auto port fast feature.
+     */
+    public boolean isAutoPortFastFeature();
+
+    /**
+     * Set the state for auto port fast feature.
+     * @param autoPortFastFeature
+     */
+    public void setAutoPortFastFeature(boolean autoPortFastFeature);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java
new file mode 100644
index 0000000..9c0dd1a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java
@@ -0,0 +1,182 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc.*    Originally created by David Erickson, Stanford University
+**    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.linkdiscovery;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkType;
+
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+
+public class LinkInfo {
+
+    public LinkInfo(Long firstSeenTime,
+                    Long lastLldpReceivedTime,
+                    Long lastBddpReceivedTime,
+                    int srcPortState,
+                    int dstPortState) {
+        super();
+        this.srcPortState = srcPortState;
+        this.dstPortState = dstPortState;
+        this.firstSeenTime = firstSeenTime;
+        this.lastLldpReceivedTime = lastLldpReceivedTime;
+        this.lastBddpReceivedTime = lastBddpReceivedTime;
+    }
+
+    protected Integer srcPortState;
+    protected Integer dstPortState;
+    protected Long firstSeenTime;
+    protected Long lastLldpReceivedTime; /* Standard LLLDP received time */
+    protected Long lastBddpReceivedTime; /* Modified LLDP received time  */
+
+    /** The port states stored here are topology's last knowledge of
+     * the state of the port. This mostly mirrors the state
+     * maintained in the port list in IOFSwitch (i.e. the one returned
+     * from getPort), except that during a port status message the
+     * IOFSwitch port state will already have been updated with the
+     * new port state, so topology needs to keep its own copy so that
+     * it can determine if the port state has changed and therefore
+     * requires the new state to be written to storage.
+     */
+
+
+
+    public boolean linkStpBlocked() {
+        return ((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue()) ||
+            ((dstPortState & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue());
+    }
+
+    public Long getFirstSeenTime() {
+        return firstSeenTime;
+    }
+
+    public void setFirstSeenTime(Long firstSeenTime) {
+        this.firstSeenTime = firstSeenTime;
+    }
+
+    public Long getUnicastValidTime() {
+        return lastLldpReceivedTime;
+    }
+
+    public void setUnicastValidTime(Long unicastValidTime) {
+        this.lastLldpReceivedTime = unicastValidTime;
+    }
+
+    public Long getMulticastValidTime() {
+        return lastBddpReceivedTime;
+    }
+
+    public void setMulticastValidTime(Long multicastValidTime) {
+        this.lastBddpReceivedTime = multicastValidTime;
+    }
+
+    public Integer getSrcPortState() {
+        return srcPortState;
+    }
+
+    public void setSrcPortState(Integer srcPortState) {
+        this.srcPortState = srcPortState;
+    }
+
+    public Integer getDstPortState() {
+        return dstPortState;
+    }
+
+    public void setDstPortState(int dstPortState) {
+        this.dstPortState = dstPortState;
+    }
+
+    public LinkType getLinkType() {
+        if (lastLldpReceivedTime != null) {
+            return LinkType.DIRECT_LINK;
+        } else if (lastBddpReceivedTime != null) {
+            return LinkType.MULTIHOP_LINK;
+        }
+        return LinkType.INVALID_LINK;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 5557;
+        int result = 1;
+        result = prime * result + ((firstSeenTime == null) ? 0 : firstSeenTime.hashCode());
+        result = prime * result + ((lastLldpReceivedTime == null) ? 0 : lastLldpReceivedTime.hashCode());
+        result = prime * result + ((lastBddpReceivedTime == null) ? 0 : lastBddpReceivedTime.hashCode());
+        result = prime * result + ((srcPortState == null) ? 0 : srcPortState.hashCode());
+        result = prime * result + ((dstPortState == null) ? 0 : dstPortState.hashCode());
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (!(obj instanceof LinkInfo))
+            return false;
+        LinkInfo other = (LinkInfo) obj;
+
+        if (firstSeenTime == null) {
+            if (other.firstSeenTime != null)
+                return false;
+        } else if (!firstSeenTime.equals(other.firstSeenTime))
+            return false;
+
+        if (lastLldpReceivedTime == null) {
+            if (other.lastLldpReceivedTime != null)
+                return false;
+        } else if (!lastLldpReceivedTime.equals(other.lastLldpReceivedTime))
+            return false;
+
+        if (lastBddpReceivedTime == null) {
+            if (other.lastBddpReceivedTime != null)
+                return false;
+        } else if (!lastBddpReceivedTime.equals(other.lastBddpReceivedTime))
+            return false;
+
+        if (srcPortState == null) {
+            if (other.srcPortState != null)
+                return false;
+        } else if (!srcPortState.equals(other.srcPortState))
+            return false;
+
+        if (dstPortState == null) {
+            if (other.dstPortState != null)
+                return false;
+        } else if (!dstPortState.equals(other.dstPortState))
+            return false;
+
+        return true;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "LinkInfo [unicastValidTime=" + ((lastLldpReceivedTime == null) ? "null" : lastLldpReceivedTime)
+                + ", multicastValidTime=" + ((lastBddpReceivedTime == null) ? "null" : lastBddpReceivedTime)
+                + ", srcPortState=" + ((srcPortState == null) ? "null" : srcPortState)
+                + ", dstPortState=" + ((dstPortState == null) ? "null" : srcPortState)
+                + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java
new file mode 100644
index 0000000..d57e987
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java
@@ -0,0 +1,43 @@
+package net.floodlightcontroller.linkdiscovery.internal;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/***
+ * Topology Cluster merge/split event history related classes and members
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyCluster {
+    // The following fields are not stored as String to save memory
+    // They should be converted to appropriate human-readable strings by 
+    // the front end (e.g. in cli in Python)
+    public long     dpid;
+    public long     clusterIdOld; // Switch with dpid moved from cluster x to y
+    public long     clusterIdNew;
+    public String   reason;
+    
+    @JsonProperty("Switch")
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getDpid() {
+        return dpid;
+    }
+    @JsonProperty("OldClusterId")
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getClusterIdOld() {
+        return clusterIdOld;
+    }
+    @JsonProperty("NewClusterId")
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getClusterIdNew() {
+        return clusterIdNew;
+    }
+    @JsonProperty("Reason")
+    public String getReason() {
+        return reason;
+    }
+    
+    
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java
new file mode 100644
index 0000000..98796ed
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java
@@ -0,0 +1,62 @@
+package net.floodlightcontroller.linkdiscovery.internal;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/***
+ * Topology link up/down event history related classes and members
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyLink {
+    // The following fields are not stored as String to save memory
+    // They should be converted to appropriate human-readable strings by 
+    // the front end (e.g. in cli in Python)
+    public long     srcSwDpid;
+    public long     dstSwDpid;
+    public int      srcPortState;
+    public int      dstPortState;
+    public int      srcSwport;
+    public int      dstSwport;
+    public String   linkType;
+    public String   reason;
+    
+    @JsonProperty("Source-Switch")
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getSrcSwDpid() {
+        return srcSwDpid;
+    }
+    @JsonProperty("Dest-Switch")
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getDstSwDpid() {
+        return dstSwDpid;
+    }
+    @JsonProperty("SrcPortState")
+    public int getSrcPortState() {
+        return srcPortState;
+    }
+    @JsonProperty("DstPortState")
+    public int getDstPortState() {
+        return dstPortState;
+    }
+    @JsonProperty("SrcPort")
+    public int getSrcSwport() {
+        return srcSwport;
+    }
+    @JsonProperty("DstPort")
+    public int getDstSwport() {
+        return dstSwport;
+    }
+    @JsonProperty("LinkType")
+    public String getLinkType() {
+        return linkType;
+    }
+    @JsonProperty("Reason")
+    public String getReason() {
+        return reason;
+    }
+    
+    
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java
new file mode 100644
index 0000000..001942b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java
@@ -0,0 +1,43 @@
+package net.floodlightcontroller.linkdiscovery.internal;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.core.web.serializers.IPv4Serializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/***
+ * Topology Switch event history related classes and members
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologySwitch {
+    // The following fields are not stored as String to save memory
+    // They should be converted to appropriate human-readable strings by 
+    // the front end (e.g. in cli in Python)
+    public long     dpid;
+    public int  ipv4Addr;
+    public int    l4Port;
+    public String   reason;
+    
+    @JsonProperty("Switch")
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getDpid() {
+        return dpid;
+    }
+    @JsonProperty("IpAddr")
+    @JsonSerialize(using=IPv4Serializer.class)
+    public int getIpv4Addr() {
+        return ipv4Addr;
+    }
+    @JsonProperty("Port")
+    public int getL4Port() {
+        return l4Port;
+    }
+    @JsonProperty("Reason")
+    public String getReason() {
+        return reason;
+    }
+    
+    
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java
new file mode 100644
index 0000000..bcb8b35
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java
@@ -0,0 +1,2062 @@
+/**
+ *    Copyright 2011, Big Switch Networks, Inc.
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.linkdiscovery.internal;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IInfoProvider;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.SingletonTask;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkType;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.SwitchType;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.UpdateOperation;
+import net.floodlightcontroller.linkdiscovery.web.LinkDiscoveryWebRoutable;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.LinkInfo;
+import net.floodlightcontroller.packet.BSN;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.LLDP;
+import net.floodlightcontroller.packet.LLDPTLV;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.IStorageSourceListener;
+import net.floodlightcontroller.storage.OperatorPredicate;
+import net.floodlightcontroller.storage.StorageException;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.util.EventHistory;
+import net.floodlightcontroller.util.EventHistory.EvAction;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFPhysicalPort.OFPortConfig;
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFPortStatus;
+import org.openflow.protocol.OFPortStatus.OFPortReason;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class sends out LLDP messages containing the sending switch's datapath
+ * id as well as the outgoing port number.  Received LLrescDP messages that
+ * match a known switch cause a new LinkTuple to be created according to the
+ * invariant rules listed below.  This new LinkTuple is also passed to routing
+ * if it exists to trigger updates.
+ *
+ * This class also handles removing links that are associated to switch ports
+ * that go down, and switches that are disconnected.
+ *
+ * Invariants:
+ *  -portLinks and switchLinks will not contain empty Sets outside of
+ *   critical sections
+ *  -portLinks contains LinkTuples where one of the src or dst
+ *   SwitchPortTuple matches the map key
+ *  -switchLinks contains LinkTuples where one of the src or dst
+ *   SwitchPortTuple's id matches the switch id
+ *  -Each LinkTuple will be indexed into switchLinks for both
+ *   src.id and dst.id, and portLinks for each src and dst
+ *  -The updates queue is only added to from within a held write lock
+ */
+@LogMessageCategory("Network Topology")
+public class LinkDiscoveryManager
+implements IOFMessageListener, IOFSwitchListener, 
+IStorageSourceListener, ILinkDiscoveryService,
+IFloodlightModule, IInfoProvider, IHAListener {
+    protected static Logger log = LoggerFactory.getLogger(LinkDiscoveryManager.class);
+
+    // Names of table/fields for links in the storage API
+    private static final String LINK_TABLE_NAME = "controller_link";
+    private static final String LINK_ID = "id";
+    private static final String LINK_SRC_SWITCH = "src_switch_id";
+    private static final String LINK_SRC_PORT = "src_port";
+    private static final String LINK_SRC_PORT_STATE = "src_port_state";
+    private static final String LINK_DST_SWITCH = "dst_switch_id";
+    private static final String LINK_DST_PORT = "dst_port";
+    private static final String LINK_DST_PORT_STATE = "dst_port_state";
+    private static final String LINK_VALID_TIME = "valid_time";
+    private static final String LINK_TYPE = "link_type";
+    private static final String SWITCH_CONFIG_TABLE_NAME = "controller_switchconfig";
+    private static final String SWITCH_CONFIG_CORE_SWITCH = "core_switch";
+
+    protected IFloodlightProviderService floodlightProvider;
+    protected IStorageSourceService storageSource;
+    protected IThreadPoolService threadPool;
+    protected IRestApiService restApi;
+
+
+    // LLDP and BDDP fields
+    private static final byte[] LLDP_STANDARD_DST_MAC_STRING = 
+            HexString.fromHexString("01:80:c2:00:00:0e");
+    private static final long LINK_LOCAL_MASK  = 0xfffffffffff0L;
+    private static final long LINK_LOCAL_VALUE = 0x0180c2000000L;
+
+    // BigSwitch OUI is 5C:16:C7, so 5D:16:C7 is the multicast version
+    // private static final String LLDP_BSN_DST_MAC_STRING = "5d:16:c7:00:00:01";
+    private static final String LLDP_BSN_DST_MAC_STRING = "ff:ff:ff:ff:ff:ff";
+
+
+    // Direction TLVs are used to indicate if the LLDPs were sent 
+    // periodically or in response to a recieved LLDP
+    private static final byte TLV_DIRECTION_TYPE = 0x73;
+    private static final short TLV_DIRECTION_LENGTH = 1;  // 1 byte
+    private static final byte TLV_DIRECTION_VALUE_FORWARD[] = {0x01};
+    private static final byte TLV_DIRECTION_VALUE_REVERSE[] = {0x02};
+    private static final LLDPTLV forwardTLV 
+    = new LLDPTLV().
+    setType((byte)TLV_DIRECTION_TYPE).
+    setLength((short)TLV_DIRECTION_LENGTH).
+    setValue(TLV_DIRECTION_VALUE_FORWARD);
+
+    private static final LLDPTLV reverseTLV 
+    = new LLDPTLV().
+    setType((byte)TLV_DIRECTION_TYPE).
+    setLength((short)TLV_DIRECTION_LENGTH).
+    setValue(TLV_DIRECTION_VALUE_REVERSE);
+
+    // Link discovery task details.
+    protected SingletonTask discoveryTask;
+    protected final int DISCOVERY_TASK_INTERVAL = 1; 
+    protected final int LINK_TIMEOUT = 35; // timeout as part of LLDP process.
+    protected final int LLDP_TO_ALL_INTERVAL = 15 ; //15 seconds.
+    protected long lldpClock = 0;
+    // This value is intentionally kept higher than LLDP_TO_ALL_INTERVAL.
+    // If we want to identify link failures faster, we could decrease this
+    // value to a small number, say 1 or 2 sec.
+    protected final int LLDP_TO_KNOWN_INTERVAL= 20; // LLDP frequency for known links
+
+    protected LLDPTLV controllerTLV;
+    protected ReentrantReadWriteLock lock;
+    int lldpTimeCount = 0;
+
+    /**
+     * Flag to indicate if automatic port fast is enabled or not.
+     * Default is set to false -- Initialized in the init method as well.
+     */
+    boolean autoPortFastFeature = false;
+
+    /**
+     * Map from link to the most recent time it was verified functioning
+     */
+    protected Map<Link, LinkInfo> links;
+
+    /**
+     * Map from switch id to a set of all links with it as an endpoint
+     */
+    protected Map<Long, Set<Link>> switchLinks;
+
+    /**
+     * Map from a id:port to the set of links containing it as an endpoint
+     */
+    protected Map<NodePortTuple, Set<Link>> portLinks;
+
+    /**
+     * Set of link tuples over which multicast LLDPs are received
+     * and unicast LLDPs are not received.
+     */
+    protected Map<NodePortTuple, Set<Link>> portBroadcastDomainLinks;
+
+    protected volatile boolean shuttingDown = false;
+
+    /* topology aware components are called in the order they were added to the
+     * the array */
+    protected ArrayList<ILinkDiscoveryListener> linkDiscoveryAware;
+    protected BlockingQueue<LDUpdate> updates;
+    protected Thread updatesThread;
+
+    /**
+     * List of ports through which LLDP/BDDPs are not sent.
+     */
+    protected Set<NodePortTuple> suppressLinkDiscovery;
+
+    /** A list of ports that are quarantined for discovering links through
+     * them.  Data traffic from these ports are not allowed until the ports
+     * are released from quarantine.
+     */
+    protected LinkedBlockingQueue<NodePortTuple> quarantineQueue;
+    protected LinkedBlockingQueue<NodePortTuple> maintenanceQueue;
+    /**
+     * Quarantine task
+     */
+    protected SingletonTask bddpTask;
+    protected final int BDDP_TASK_INTERVAL = 100; // 100 ms.
+    protected final int BDDP_TASK_SIZE = 5;       // # of ports per iteration
+
+    /**
+     * Map of broadcast domain ports and the last time a BDDP was either
+     * sent or received on that port.
+     */
+    protected Map<NodePortTuple, Long> broadcastDomainPortTimeMap;
+
+    /** 
+     * Get the LLDP sending period in seconds.
+     * @return LLDP sending period in seconds.
+     */
+    public int getLldpFrequency() {
+        return LLDP_TO_KNOWN_INTERVAL;
+    }
+
+    /**
+     * Get the LLDP timeout value in seconds
+     * @return LLDP timeout value in seconds
+     */
+    public int getLldpTimeout() {
+        return LINK_TIMEOUT;
+    }
+
+    public Map<NodePortTuple, Set<Link>> getPortLinks() {
+        return portLinks;
+    }
+
+    public Set<NodePortTuple> getSuppressLLDPsInfo() {
+        return suppressLinkDiscovery;
+    }
+
+    /**
+     * Add a switch port to the suppressed LLDP list.
+     * Remove any known links on the switch port.
+     */
+    public void AddToSuppressLLDPs(long sw, short port)
+    {
+        NodePortTuple npt = new NodePortTuple(sw, port);
+        this.suppressLinkDiscovery.add(npt);
+        deleteLinksOnPort(npt, "LLDP suppressed.");
+    }
+
+    /**
+     * Remove a switch port from the suppressed LLDP list.
+     * Discover links on that switchport.
+     */
+    public void RemoveFromSuppressLLDPs(long sw, short port) 
+    {
+        NodePortTuple npt = new NodePortTuple(sw, port);
+        this.suppressLinkDiscovery.remove(npt);
+        discover(npt);
+    }
+
+    public boolean isShuttingDown() {
+        return shuttingDown;
+    }
+
+    public boolean isFastPort(long sw, short port) {
+        return false;
+    }
+
+    public ILinkDiscovery.LinkType getLinkType(Link lt, LinkInfo info) {
+        if (info.getUnicastValidTime() != null) {
+            return ILinkDiscovery.LinkType.DIRECT_LINK;
+        } else if (info.getMulticastValidTime() != null) {
+            return ILinkDiscovery.LinkType.MULTIHOP_LINK;
+        }
+        return ILinkDiscovery.LinkType.INVALID_LINK;
+    }
+
+    @LogMessageDoc(level="ERROR",
+            message="Error in link discovery updates loop",
+            explanation="An unknown error occured while dispatching " +
+            		"link update notifications",
+            recommendation=LogMessageDoc.GENERIC_ACTION)
+    private void doUpdatesThread() throws InterruptedException {
+        do {
+            LDUpdate update = updates.take();
+
+            if (linkDiscoveryAware != null) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Dispatching link discovery update {} {} {} {} {} for {}",
+                              new Object[]{update.getOperation(),
+                                           HexString.toHexString(update.getSrc()), update.getSrcPort(),
+                                           HexString.toHexString(update.getDst()), update.getDstPort(),
+                                           linkDiscoveryAware});
+                }
+                try {
+                    for (ILinkDiscoveryListener lda : linkDiscoveryAware) { // order maintained
+                        lda.linkDiscoveryUpdate(update);
+                    }
+                }
+                catch (Exception e) {
+                    log.error("Error in link discovery updates loop", e);
+                }
+            }
+        } while (updates.peek() != null);
+    }
+    private boolean isLinkDiscoverySuppressed(long sw, short portNumber) {
+        return this.suppressLinkDiscovery.contains(new NodePortTuple(sw, portNumber));
+    }
+
+    protected void discoverLinks() {
+
+        // timeout known links.
+        timeoutLinks();
+
+        //increment LLDP clock
+        lldpClock = (lldpClock + 1)% LLDP_TO_ALL_INTERVAL;
+
+        if (lldpClock == 0) {
+            log.debug("Sending LLDP out on all ports.");
+            discoverOnAllPorts();
+        }
+    }
+
+
+    /**
+     *  Quarantine Ports.
+     */
+    protected class QuarantineWorker implements Runnable {
+        @Override
+        public void run() {
+            try {
+                processBDDPLists();
+            }
+            catch (Exception e) {
+                log.error("Error in quarantine worker thread", e);
+            } finally {
+                    bddpTask.reschedule(BDDP_TASK_INTERVAL,
+                                              TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+    /**
+     * Add a switch port to the quarantine queue. Schedule the
+     * quarantine task if the quarantine queue was empty before adding
+     * this switch port.
+     * @param npt
+     */
+    protected void addToQuarantineQueue(NodePortTuple npt) {
+        if (quarantineQueue.contains(npt) == false)
+            quarantineQueue.add(npt);
+    }
+
+    /**
+     * Remove a switch port from the quarantine queue.
+     */
+    protected void removeFromQuarantineQueue(NodePortTuple npt) {
+        // Remove all occurrences of the node port tuple from the list.
+        while (quarantineQueue.remove(npt));
+    }
+
+    /**
+     * Add a switch port to maintenance queue.
+     * @param npt
+     */
+    protected void addToMaintenanceQueue(NodePortTuple npt) {
+        // TODO We are not checking if the switch port tuple is already
+        // in the maintenance list or not.  This will be an issue for
+        // really large number of switch ports in the network.
+        if (maintenanceQueue.contains(npt) == false)
+            maintenanceQueue.add(npt);
+    }
+
+    /**
+     * Remove a switch port from maintenance queue.
+     * @param npt
+     */
+    protected void removeFromMaintenanceQueue(NodePortTuple npt) {
+        // Remove all occurrences of the node port tuple from the queue.
+        while (maintenanceQueue.remove(npt));
+    }
+
+    /**
+    * This method processes the quarantine list in bursts.  The task is
+    * at most once per BDDP_TASK_INTERVAL.
+    * One each call, BDDP_TASK_SIZE number of switch ports are processed.
+    * Once the BDDP packets are sent out through the switch ports, the ports
+    * are removed from the quarantine list.
+    */
+
+    protected void processBDDPLists() {
+        int count = 0;
+        Set<NodePortTuple> nptList = new HashSet<NodePortTuple>();
+
+        while(count < BDDP_TASK_SIZE && quarantineQueue.peek() !=null) {
+            NodePortTuple npt;
+            npt = quarantineQueue.remove();
+            sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false);
+            nptList.add(npt);
+            count++;
+        }
+
+        count = 0;
+        while (count < BDDP_TASK_SIZE && maintenanceQueue.peek() != null) {
+            NodePortTuple npt;
+            npt = maintenanceQueue.remove();
+            sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false);
+            count++;
+        }
+
+        for(NodePortTuple npt:nptList) {
+            generateSwitchPortStatusUpdate(npt.getNodeId(), npt.getPortId());
+        }
+    }
+
+    public Set<Short> getQuarantinedPorts(long sw) {
+        Set<Short> qPorts = new HashSet<Short>();
+
+        Iterator<NodePortTuple> iter = quarantineQueue.iterator();
+        while (iter.hasNext()) {
+            NodePortTuple npt = iter.next();
+            if (npt.getNodeId() == sw) {
+                qPorts.add(npt.getPortId());
+            }
+        }
+        return qPorts;
+    }
+
+    private void generateSwitchPortStatusUpdate(long sw, short port) {
+        UpdateOperation operation;
+
+        IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+        if (iofSwitch == null) return;
+
+        OFPhysicalPort ofp = iofSwitch.getPort(port);
+        if (ofp == null) return;
+
+        int srcPortState = ofp.getState();
+        boolean portUp = ((srcPortState &
+                OFPortState.OFPPS_STP_MASK.getValue()) !=
+                OFPortState.OFPPS_STP_BLOCK.getValue());
+
+        if (portUp) operation = UpdateOperation.PORT_UP;
+        else operation = UpdateOperation.PORT_DOWN;
+
+        updates.add(new LDUpdate(sw, port, operation));
+    }
+
+    /** 
+     * Send LLDP on known ports
+     */
+    protected void discoverOnKnownLinkPorts() {
+        // Copy the port set.
+        Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>();
+        nptSet.addAll(portLinks.keySet());
+
+        // Send LLDP from each of them.
+        for(NodePortTuple npt: nptSet) {
+            discover(npt);
+        }
+    }
+
+    protected void discover(NodePortTuple npt) {
+        discover(npt.getNodeId(), npt.getPortId());
+    }
+
+    protected void discover(long sw, short port) {
+        sendDiscoveryMessage(sw, port, true, false);
+    }
+
+    /**
+     * Send link discovery message out of a given switch port.
+     * The discovery message may be a standard LLDP or a modified
+     * LLDP, where the dst mac address is set to :ff.  
+     * 
+     * TODO: The modified LLDP will updated in the future and may
+     * use a different eth-type.
+     * @param sw
+     * @param port
+     * @param isStandard   indicates standard or modified LLDP
+     * @param isReverse    indicates whether the LLDP was sent as a response
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Failure sending LLDP out port {port} on switch {switch}",
+            explanation="An I/O error occured while sending LLDP message " +
+            		"to the switch.",
+            recommendation=LogMessageDoc.CHECK_SWITCH)
+    protected void sendDiscoveryMessage(long sw, short port,
+                             boolean isStandard,
+                             boolean isReverse) {
+
+        IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+        if (iofSwitch == null) {
+            return;
+        }
+
+        if (port == OFPort.OFPP_LOCAL.getValue())
+            return;
+
+        OFPhysicalPort ofpPort = iofSwitch.getPort(port);
+
+        if (ofpPort == null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Null physical port. sw={}, port={}", sw, port);
+            }
+            return;
+        }
+
+        if (isLinkDiscoverySuppressed(sw, port)) {
+            /* Dont send LLDPs out of this port as suppressLLDPs set
+             * 
+             */
+            return;
+        }
+
+        // For fast ports, do not send forward LLDPs or BDDPs.
+        if (!isReverse && autoPortFastFeature && isFastPort(sw, port))
+            return;
+
+        if (log.isTraceEnabled()) {
+            log.trace("Sending LLDP packet out of swich: {}, port: {}",
+                      sw, port);
+        }
+
+        // using "nearest customer bridge" MAC address for broadest possible propagation
+        // through provider and TPMR bridges (see IEEE 802.1AB-2009 and 802.1Q-2011),
+        // in particular the Linux bridge which behaves mostly like a provider bridge
+        byte[] chassisId = new byte[] {4, 0, 0, 0, 0, 0, 0}; // filled in later
+        byte[] portId = new byte[] {2, 0, 0}; // filled in later
+        byte[] ttlValue = new byte[] {0, 0x78};
+        // OpenFlow OUI - 00-26-E1
+        byte[] dpidTLVValue = new byte[] {0x0, 0x26, (byte) 0xe1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        LLDPTLV dpidTLV = new LLDPTLV().setType((byte) 127).setLength((short) dpidTLVValue.length).setValue(dpidTLVValue);
+
+        byte[] dpidArray = new byte[8];
+        ByteBuffer dpidBB = ByteBuffer.wrap(dpidArray);
+        ByteBuffer portBB = ByteBuffer.wrap(portId, 1, 2);
+
+        Long dpid = sw;
+        dpidBB.putLong(dpid);
+        // set the ethernet source mac to last 6 bytes of dpid
+        System.arraycopy(dpidArray, 2, ofpPort.getHardwareAddress(), 0, 6);
+        // set the chassis id's value to last 6 bytes of dpid
+        System.arraycopy(dpidArray, 2, chassisId, 1, 6);
+        // set the optional tlv to the full dpid
+        System.arraycopy(dpidArray, 0, dpidTLVValue, 4, 8);
+
+
+        // set the portId to the outgoing port
+        portBB.putShort(port);
+        if (log.isTraceEnabled()) {
+            log.trace("Sending LLDP out of interface: {}/{}",
+                      HexString.toHexString(sw), port);
+        }
+
+        LLDP lldp = new LLDP();
+        lldp.setChassisId(new LLDPTLV().setType((byte) 1).setLength((short) chassisId.length).setValue(chassisId));
+        lldp.setPortId(new LLDPTLV().setType((byte) 2).setLength((short) portId.length).setValue(portId));
+        lldp.setTtl(new LLDPTLV().setType((byte) 3).setLength((short) ttlValue.length).setValue(ttlValue));
+        lldp.getOptionalTLVList().add(dpidTLV);
+
+        // Add the controller identifier to the TLV value.
+        lldp.getOptionalTLVList().add(controllerTLV);
+        if (isReverse) {
+            lldp.getOptionalTLVList().add(reverseTLV);
+        }else {
+            lldp.getOptionalTLVList().add(forwardTLV);
+        }
+
+        Ethernet ethernet;
+        if (isStandard) {
+            ethernet = new Ethernet()
+            .setSourceMACAddress(ofpPort.getHardwareAddress())
+            .setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING)
+            .setEtherType(Ethernet.TYPE_LLDP);
+            ethernet.setPayload(lldp);
+        } else {
+            BSN bsn = new BSN(BSN.BSN_TYPE_BDDP);
+            bsn.setPayload(lldp);
+
+            ethernet = new Ethernet()
+            .setSourceMACAddress(ofpPort.getHardwareAddress())
+            .setDestinationMACAddress(LLDP_BSN_DST_MAC_STRING)
+            .setEtherType(Ethernet.TYPE_BSN);
+            ethernet.setPayload(bsn);
+        }
+
+
+        // serialize and wrap in a packet out
+        byte[] data = ethernet.serialize();
+        OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
+        po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
+        po.setInPort(OFPort.OFPP_NONE);
+
+        // set actions
+        List<OFAction> actions = new ArrayList<OFAction>();
+        actions.add(new OFActionOutput(port, (short) 0));
+        po.setActions(actions);
+        po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
+
+        // set data
+        po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLength() + data.length);
+        po.setPacketData(data);
+
+        // send
+        try {
+            iofSwitch.write(po, null);
+            iofSwitch.flush();
+        } catch (IOException e) {
+            log.error("Failure sending LLDP out port {} on switch {}",
+                      new Object[]{ port, iofSwitch.getStringId() }, e);
+        }
+
+    }
+
+    /**
+     * Send LLDPs to all switch-ports
+     */
+    protected void discoverOnAllPorts() {
+        if (log.isTraceEnabled()) {
+            log.trace("Sending LLDP packets out of all the enabled ports on switch {}");
+        }
+        Set<Long> switches = floodlightProvider.getSwitches().keySet();
+        // Send standard LLDPs
+        for (long sw: switches) {
+            IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+            if (iofSwitch == null) continue;
+            if (iofSwitch.getEnabledPorts() != null) {
+                for (OFPhysicalPort ofp: iofSwitch.getEnabledPorts()) {
+                    if (isLinkDiscoverySuppressed(sw, ofp.getPortNumber()))
+                        continue;
+                    if (autoPortFastFeature && isFastPort(sw, ofp.getPortNumber()))
+                        continue;
+
+                    // sends forward LLDP only non-fastports.
+                    sendDiscoveryMessage(sw, ofp.getPortNumber(), true, false);
+
+                    // If the switch port is not alreayd in the maintenance
+                    // queue, add it.
+                    NodePortTuple npt = new NodePortTuple(sw, ofp.getPortNumber());
+                    addToMaintenanceQueue(npt);
+                }
+            }
+        }
+    }
+
+    protected void setControllerTLV() {
+        //Setting the controllerTLVValue based on current nano time,
+        //controller's IP address, and the network interface object hash
+        //the corresponding IP address.
+
+        final int prime = 7867;
+        InetAddress localIPAddress = null;
+        NetworkInterface localInterface = null;
+
+        byte[] controllerTLVValue = new byte[] {0, 0, 0, 0, 0, 0, 0, 0};  // 8 byte value.
+        ByteBuffer bb = ByteBuffer.allocate(10);
+
+        try{
+            localIPAddress = java.net.InetAddress.getLocalHost();
+            localInterface = NetworkInterface.getByInetAddress(localIPAddress);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        long result = System.nanoTime();
+        if (localIPAddress != null)
+            result = result * prime + IPv4.toIPv4Address(localIPAddress.getHostAddress());
+        if (localInterface != null)
+            result = result * prime + localInterface.hashCode();
+        // set the first 4 bits to 0.
+        result = result & (0x0fffffffffffffffL);
+
+        bb.putLong(result);
+
+        bb.rewind();
+        bb.get(controllerTLVValue, 0, 8);
+
+        this.controllerTLV = new LLDPTLV().setType((byte) 0x0c).setLength((short) controllerTLVValue.length).setValue(controllerTLVValue);
+    }
+
+    @Override
+    public String getName() {
+        return "linkdiscovery";
+    }
+
+    @Override
+    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+        switch (msg.getType()) {
+            case PACKET_IN:
+                return this.handlePacketIn(sw.getId(), (OFPacketIn) msg, cntx);
+            case PORT_STATUS:
+                return this.handlePortStatus(sw.getId(), (OFPortStatus) msg);
+            default:
+                break;
+        }
+        return Command.CONTINUE;
+    }
+
+    private Command handleLldp(LLDP lldp, long sw, OFPacketIn pi, boolean isStandard, FloodlightContext cntx) {
+        // If LLDP is suppressed on this port, ignore received packet as well
+        IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+        if (iofSwitch == null) {
+            return Command.STOP;
+        }
+
+        if (isLinkDiscoverySuppressed(sw, pi.getInPort()))
+            return Command.STOP;
+
+        // If this is a malformed LLDP, or not from us, exit
+        if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3)
+            return Command.CONTINUE;
+
+        long myId = ByteBuffer.wrap(controllerTLV.getValue()).getLong();
+        long otherId = 0;
+        boolean myLLDP = false;
+        Boolean isReverse = null;
+
+        ByteBuffer portBB = ByteBuffer.wrap(lldp.getPortId().getValue());
+        portBB.position(1);
+
+        Short remotePort = portBB.getShort();
+        IOFSwitch remoteSwitch = null;
+
+        // Verify this LLDP packet matches what we're looking for
+        for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) {
+            if (lldptlv.getType() == 127 && lldptlv.getLength() == 12 &&
+                    lldptlv.getValue()[0] == 0x0 && lldptlv.getValue()[1] == 0x26 &&
+                    lldptlv.getValue()[2] == (byte)0xe1 && lldptlv.getValue()[3] == 0x0) {
+                ByteBuffer dpidBB = ByteBuffer.wrap(lldptlv.getValue());
+                remoteSwitch = floodlightProvider.getSwitches().get(dpidBB.getLong(4));
+            } else if (lldptlv.getType() == 12 && lldptlv.getLength() == 8){
+                otherId = ByteBuffer.wrap(lldptlv.getValue()).getLong();
+                if (myId == otherId)
+                    myLLDP = true;
+            } else if (lldptlv.getType() == TLV_DIRECTION_TYPE &&
+                    lldptlv.getLength() == TLV_DIRECTION_LENGTH) {
+                if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_FORWARD[0])
+                    isReverse = false;
+                else if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_REVERSE[0])
+                    isReverse = true;
+            }
+        }
+
+        if (myLLDP == false) {
+            // This is not the LLDP sent by this controller.
+            // If the LLDP message has multicast bit set, then we need to broadcast
+            // the packet as a regular packet.
+            if (isStandard) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Getting standard LLDP from a different controller and quelching it.");
+                }
+                return Command.STOP;
+            }
+            else if (myId < otherId)  {
+                if (log.isTraceEnabled()) {
+                    log.trace("Getting BDDP packets from a different controller" +
+                            "and letting it go through normal processing chain.");
+                }
+                return Command.CONTINUE;
+            }
+        }
+
+
+        if (remoteSwitch == null) {
+            // Ignore LLDPs not generated by Floodlight, or from a switch that has recently
+            // disconnected, or from a switch connected to another Floodlight instance
+            if (log.isTraceEnabled()) {
+                log.trace("Received LLDP from remote switch not connected to the controller");
+            }
+            return Command.STOP;
+        }
+
+        if (!remoteSwitch.portEnabled(remotePort)) {
+            if (log.isTraceEnabled()) {
+                log.trace("Ignoring link with disabled source port: switch {} port {}", remoteSwitch, remotePort);
+            }
+            return Command.STOP;
+        }
+        if (suppressLinkDiscovery.contains(new NodePortTuple(remoteSwitch.getId(),
+                                                     remotePort))) {
+            if (log.isTraceEnabled()) {
+                log.trace("Ignoring link with suppressed src port: switch {} port {}",
+                      remoteSwitch, remotePort);
+            }
+            return Command.STOP;
+        }
+        if (!iofSwitch.portEnabled(pi.getInPort())) {
+            if (log.isTraceEnabled()) {
+                log.trace("Ignoring link with disabled dest port: switch {} port {}", sw, pi.getInPort());
+            }
+            return Command.STOP;
+        }
+
+        OFPhysicalPort physicalPort = remoteSwitch.getPort(remotePort);
+        int srcPortState = (physicalPort != null) ? physicalPort.getState() : 0;
+        physicalPort = iofSwitch.getPort(pi.getInPort());
+        int dstPortState = (physicalPort != null) ? physicalPort.getState() : 0;
+
+        // Store the time of update to this link, and push it out to routingEngine
+        Link lt = new Link(remoteSwitch.getId(), remotePort, iofSwitch.getId(), pi.getInPort());
+
+
+        Long lastLldpTime = null;
+        Long lastBddpTime = null;
+
+        Long firstSeenTime = System.currentTimeMillis();
+
+        if (isStandard)
+            lastLldpTime = System.currentTimeMillis();
+        else
+            lastBddpTime = System.currentTimeMillis();
+
+        LinkInfo newLinkInfo =
+                new LinkInfo(firstSeenTime, lastLldpTime, lastBddpTime,
+                             srcPortState, dstPortState);
+
+        addOrUpdateLink(lt, newLinkInfo);
+
+        // Check if reverse link exists. 
+        // If it doesn't exist and if the forward link was seen 
+        // first seen within a small interval, send probe on the 
+        // reverse link.
+
+        newLinkInfo = links.get(lt);
+        if (newLinkInfo != null && isStandard && isReverse == false) {
+            Link reverseLink = new Link(lt.getDst(), lt.getDstPort(),
+                                        lt.getSrc(), lt.getSrcPort());
+            LinkInfo reverseInfo = links.get(reverseLink);
+            if (reverseInfo == null) {
+                // the reverse link does not exist.
+                if (newLinkInfo.getFirstSeenTime() > System.currentTimeMillis() - LINK_TIMEOUT) {
+                    this.sendDiscoveryMessage(lt.getDst(), lt.getDstPort(), isStandard, true);
+                }
+            }
+        }
+
+        // If the received packet is a BDDP packet, then create a reverse BDDP
+        // link as well.
+        if (!isStandard) {
+            Link reverseLink = new Link(lt.getDst(), lt.getDstPort(),
+                                        lt.getSrc(), lt.getSrcPort());
+
+            // srcPortState and dstPort state are reversed.
+            LinkInfo reverseInfo =
+                    new LinkInfo(firstSeenTime, lastLldpTime, lastBddpTime,
+                                 dstPortState, srcPortState);
+
+            addOrUpdateLink(reverseLink, reverseInfo);
+        }
+
+        // Remove the node ports from the quarantine and maintenance queues.
+        NodePortTuple nptSrc = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+        NodePortTuple nptDst = new NodePortTuple(lt.getDst(), lt.getDstPort());
+        removeFromQuarantineQueue(nptSrc);
+        removeFromMaintenanceQueue(nptSrc);
+        removeFromQuarantineQueue(nptDst);
+        removeFromMaintenanceQueue(nptDst);
+
+        // Consume this message
+        return Command.STOP;
+    }
+
+    protected Command handlePacketIn(long sw, OFPacketIn pi,
+                                     FloodlightContext cntx) {
+        Ethernet eth = 
+                IFloodlightProviderService.bcStore.get(cntx, 
+                                                       IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+
+        if(eth.getEtherType() == Ethernet.TYPE_BSN) {
+            BSN bsn = (BSN) eth.getPayload();
+            if (bsn == null) return Command.STOP;
+            if (bsn.getPayload() == null) return Command.STOP;
+            // It could be a packet other than BSN LLDP, therefore
+            // continue with the regular processing.
+            if (bsn.getPayload() instanceof LLDP == false)
+                return Command.CONTINUE;
+            return handleLldp((LLDP) bsn.getPayload(), sw, pi, false, cntx);
+        } else if (eth.getEtherType() == Ethernet.TYPE_LLDP)  {
+            return handleLldp((LLDP) eth.getPayload(), sw, pi, true, cntx);
+        } else if (eth.getEtherType() < 1500) {
+            long destMac = eth.getDestinationMAC().toLong();
+            if ((destMac & LINK_LOCAL_MASK) == LINK_LOCAL_VALUE){
+                if (log.isTraceEnabled()) {
+                    log.trace("Ignoring packet addressed to 802.1D/Q " +
+                            "reserved address.");
+                }
+                return Command.STOP;
+            }
+        }
+
+        // If packet-in is from a quarantine port, stop processing.
+        NodePortTuple npt = new NodePortTuple(sw, pi.getInPort());
+        if (quarantineQueue.contains(npt)) return Command.STOP;
+
+        return Command.CONTINUE;
+    }
+
+    protected UpdateOperation getUpdateOperation(int srcPortState,
+                                                 int dstPortState) {
+        boolean added =
+                (((srcPortState &
+                   OFPortState.OFPPS_STP_MASK.getValue()) !=
+                   OFPortState.OFPPS_STP_BLOCK.getValue()) &&
+                ((dstPortState &
+                  OFPortState.OFPPS_STP_MASK.getValue()) !=
+                  OFPortState.OFPPS_STP_BLOCK.getValue()));
+
+        if (added) return UpdateOperation.LINK_UPDATED;
+        return UpdateOperation.LINK_REMOVED;
+    }
+
+
+
+    protected UpdateOperation getUpdateOperation(int srcPortState) {
+        boolean portUp = ((srcPortState &
+                OFPortState.OFPPS_STP_MASK.getValue()) !=
+                OFPortState.OFPPS_STP_BLOCK.getValue());
+
+        if (portUp) return UpdateOperation.PORT_UP;
+        else return UpdateOperation.PORT_DOWN;
+    }
+
+    protected boolean addOrUpdateLink(Link lt, LinkInfo newInfo) {
+
+        NodePortTuple srcNpt, dstNpt;
+        boolean linkChanged = false;
+
+        lock.writeLock().lock();
+        try {
+            // put the new info.  if an old info exists, it will be returned.
+            LinkInfo oldInfo = links.put(lt, newInfo);
+            if (oldInfo != null &&
+                    oldInfo.getFirstSeenTime() < newInfo.getFirstSeenTime())
+                newInfo.setFirstSeenTime(oldInfo.getFirstSeenTime());
+
+            if (log.isTraceEnabled()) {
+                log.trace("addOrUpdateLink: {} {}", 
+                          lt, 
+                          (newInfo.getMulticastValidTime()!=null) ? "multicast" : "unicast");
+            }
+
+            UpdateOperation updateOperation = null;
+            linkChanged = false;
+
+            srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+            dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+            if (oldInfo == null) {
+                // index it by switch source
+                if (!switchLinks.containsKey(lt.getSrc()))
+                    switchLinks.put(lt.getSrc(), new HashSet<Link>());
+                switchLinks.get(lt.getSrc()).add(lt);
+
+                // index it by switch dest
+                if (!switchLinks.containsKey(lt.getDst()))
+                    switchLinks.put(lt.getDst(), new HashSet<Link>());
+                switchLinks.get(lt.getDst()).add(lt);
+
+                // index both ends by switch:port
+                if (!portLinks.containsKey(srcNpt))
+                    portLinks.put(srcNpt, new HashSet<Link>());
+                portLinks.get(srcNpt).add(lt);
+
+                if (!portLinks.containsKey(dstNpt))
+                    portLinks.put(dstNpt, new HashSet<Link>());
+                portLinks.get(dstNpt).add(lt);
+
+                // Add to portNOFLinks if the unicast valid time is null
+                if (newInfo.getUnicastValidTime() == null)
+                    addLinkToBroadcastDomain(lt);
+
+                writeLinkToStorage(lt, newInfo);
+                updateOperation = UpdateOperation.LINK_UPDATED;
+                linkChanged = true;
+
+                // Add to event history
+                evHistTopoLink(lt.getSrc(),
+                               lt.getDst(),
+                               lt.getSrcPort(),
+                               lt.getDstPort(),
+                               newInfo.getSrcPortState(), newInfo.getDstPortState(),
+                               getLinkType(lt, newInfo),
+                               EvAction.LINK_ADDED, "LLDP Recvd");
+            } else {
+                // Since the link info is already there, we need to
+                // update the right fields.
+                if (newInfo.getUnicastValidTime() == null) {
+                    // This is due to a multicast LLDP, so copy the old unicast
+                    // value.
+                    if (oldInfo.getUnicastValidTime() != null) {
+                        newInfo.setUnicastValidTime(oldInfo.getUnicastValidTime());
+                    }
+                } else if (newInfo.getMulticastValidTime() == null) {
+                    // This is due to a unicast LLDP, so copy the old multicast
+                    // value.
+                    if (oldInfo.getMulticastValidTime() != null) {
+                        newInfo.setMulticastValidTime(oldInfo.getMulticastValidTime());
+                    }
+                }
+
+                Long oldTime = oldInfo.getUnicastValidTime();
+                Long newTime = newInfo.getUnicastValidTime();
+                // the link has changed its state between openflow and non-openflow
+                // if the unicastValidTimes are null or not null
+                if (oldTime != null & newTime == null) {
+                    // openflow -> non-openflow transition
+                    // we need to add the link tuple to the portNOFLinks
+                    addLinkToBroadcastDomain(lt);
+                    linkChanged = true;
+                } else if (oldTime == null & newTime != null) {
+                    // non-openflow -> openflow transition
+                    // we need to remove the link from the portNOFLinks
+                    removeLinkFromBroadcastDomain(lt);
+                    linkChanged = true;
+                }
+
+                // Only update the port states if they've changed
+                if (newInfo.getSrcPortState().intValue() !=
+                        oldInfo.getSrcPortState().intValue() ||
+                        newInfo.getDstPortState().intValue() !=
+                        oldInfo.getDstPortState().intValue())
+                    linkChanged = true;
+
+                // Write changes to storage. This will always write the updated
+                // valid time, plus the port states if they've changed (i.e. if
+                // they weren't set to null in the previous block of code.
+                writeLinkToStorage(lt, newInfo);
+
+                if (linkChanged) {
+                    updateOperation = getUpdateOperation(newInfo.getSrcPortState(),
+                                                         newInfo.getDstPortState());
+                    if (log.isTraceEnabled()) {
+                        log.trace("Updated link {}", lt);
+                    }
+                    // Add to event history
+                    evHistTopoLink(lt.getSrc(),
+                                   lt.getDst(),
+                                   lt.getSrcPort(),
+                                   lt.getDstPort(),
+                                   newInfo.getSrcPortState(), newInfo.getDstPortState(),
+                                   getLinkType(lt, newInfo),
+                                   EvAction.LINK_PORT_STATE_UPDATED,
+                                   "LLDP Recvd");
+                }
+            }
+
+            if (linkChanged) {
+                // find out if the link was added or removed here.
+                updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+                                         lt.getDst(), lt.getDstPort(),
+                                         getLinkType(lt, newInfo),
+                                         updateOperation));
+            }
+        } finally {
+            lock.writeLock().unlock();
+        }
+
+        return linkChanged;
+    }
+
+    public Map<Long, Set<Link>> getSwitchLinks() {
+        return this.switchLinks;
+    }
+
+    /**
+     * Removes links from memory and storage.
+     * @param links The List of @LinkTuple to delete.
+     */
+    protected void deleteLinks(List<Link> links, String reason) {
+        NodePortTuple srcNpt, dstNpt;
+
+        lock.writeLock().lock();
+        try {
+            for (Link lt : links) {
+                srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+                dstNpt  =new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+                switchLinks.get(lt.getSrc()).remove(lt);
+                switchLinks.get(lt.getDst()).remove(lt);
+                if (switchLinks.containsKey(lt.getSrc()) &&
+                        switchLinks.get(lt.getSrc()).isEmpty())
+                    this.switchLinks.remove(lt.getSrc());
+                if (this.switchLinks.containsKey(lt.getDst()) &&
+                        this.switchLinks.get(lt.getDst()).isEmpty())
+                    this.switchLinks.remove(lt.getDst());
+
+                if (this.portLinks.get(srcNpt) != null) {
+                    this.portLinks.get(srcNpt).remove(lt);
+                    if (this.portLinks.get(srcNpt).isEmpty())
+                        this.portLinks.remove(srcNpt);
+                }
+                if (this.portLinks.get(dstNpt) != null) {
+                    this.portLinks.get(dstNpt).remove(lt);
+                    if (this.portLinks.get(dstNpt).isEmpty())
+                        this.portLinks.remove(dstNpt);
+                }
+
+                LinkInfo info = this.links.remove(lt);
+                updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+                                         lt.getDst(), lt.getDstPort(),
+                                         getLinkType(lt, info),
+                                         UpdateOperation.LINK_REMOVED));
+
+                // Update Event History
+                evHistTopoLink(lt.getSrc(),
+                               lt.getDst(),
+                               lt.getSrcPort(),
+                               lt.getDstPort(),
+                               0, 0, // Port states
+                               ILinkDiscovery.LinkType.INVALID_LINK,
+                               EvAction.LINK_DELETED, reason);
+
+                // remove link from storage.
+                removeLinkFromStorage(lt);
+
+                // TODO  Whenever link is removed, it has to checked if
+                // the switchports must be added to quarantine.
+
+                if (log.isTraceEnabled()) {
+                    log.trace("Deleted link {}", lt);
+                }
+            }
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Handles an OFPortStatus message from a switch. We will add or
+     * delete LinkTupes as well re-compute the topology if needed.
+     * @param sw The IOFSwitch that sent the port status message
+     * @param ps The OFPortStatus message
+     * @return The Command to continue or stop after we process this message
+     */
+    protected Command handlePortStatus(long sw, OFPortStatus ps) {
+
+        IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+        if (iofSwitch == null) return Command.CONTINUE;
+
+        if (log.isTraceEnabled()) {
+            log.trace("handlePortStatus: Switch {} port #{} reason {}; " +
+                    "config is {} state is {}",
+                    new Object[] {iofSwitch.getStringId(),
+                                  ps.getDesc().getPortNumber(),
+                                  ps.getReason(),
+                                  ps.getDesc().getConfig(),
+                                  ps.getDesc().getState()});
+        }
+
+        short port = ps.getDesc().getPortNumber();
+        NodePortTuple npt = new NodePortTuple(sw, port);
+        boolean linkDeleted  = false;
+        boolean linkInfoChanged = false;
+
+        lock.writeLock().lock();
+        try {
+            // if ps is a delete, or a modify where the port is down or
+            // configured down
+            if ((byte)OFPortReason.OFPPR_DELETE.ordinal() == ps.getReason() ||
+                    ((byte)OFPortReason.OFPPR_MODIFY.ordinal() ==
+                    ps.getReason() && !portEnabled(ps.getDesc()))) {
+                deleteLinksOnPort(npt, "Port Status Changed");
+                LDUpdate update = new LDUpdate(sw, port, UpdateOperation.PORT_DOWN);
+                updates.add(update);
+                linkDeleted = true;
+                } 
+            else if (ps.getReason() ==
+                    (byte)OFPortReason.OFPPR_MODIFY.ordinal()) {
+                // If ps is a port modification and the port state has changed
+                // that affects links in the topology
+
+                if (this.portLinks.containsKey(npt)) {
+                    for (Link lt: this.portLinks.get(npt)) {
+                        LinkInfo linkInfo = links.get(lt);
+                        assert(linkInfo != null);
+                        Integer updatedSrcPortState = null;
+                        Integer updatedDstPortState = null;
+                        if (lt.getSrc() == npt.getNodeId() && 
+                                lt.getSrcPort() == npt.getPortId() &&
+                                (linkInfo.getSrcPortState() !=
+                                ps.getDesc().getState())) {
+                            updatedSrcPortState = ps.getDesc().getState();
+                            linkInfo.setSrcPortState(updatedSrcPortState);
+                        }
+                        if (lt.getDst() == npt.getNodeId() &&
+                                lt.getDstPort() == npt.getPortId() &&
+                                (linkInfo.getDstPortState() !=
+                                ps.getDesc().getState())) {
+                            updatedDstPortState = ps.getDesc().getState();
+                            linkInfo.setDstPortState(updatedDstPortState);
+                        }
+                        if ((updatedSrcPortState != null) ||
+                                (updatedDstPortState != null)) {
+                            // The link is already known to link discovery
+                            // manager and the status has changed, therefore
+                            // send an LDUpdate.
+                            UpdateOperation operation =
+                                    getUpdateOperation(linkInfo.getSrcPortState(),
+                                                       linkInfo.getDstPortState());
+                            updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+                                                     lt.getDst(), lt.getDstPort(),
+                                                     getLinkType(lt, linkInfo),
+                                                     operation));
+                            writeLinkToStorage(lt, linkInfo);
+                            linkInfoChanged = true;
+                        }
+                    }
+                }
+
+                UpdateOperation operation =
+                        getUpdateOperation(ps.getDesc().getState());
+                updates.add(new LDUpdate(sw, port, operation));
+            }
+
+            if (!linkDeleted && !linkInfoChanged){
+                if (log.isTraceEnabled()) {
+                    log.trace("handlePortStatus: Switch {} port #{} reason {};"+
+                            " no links to update/remove",
+                            new Object[] {HexString.toHexString(sw),
+                                          ps.getDesc().getPortNumber(),
+                                          ps.getReason()});
+                }
+            }
+        } finally {
+            lock.writeLock().unlock();
+        }
+
+        if (!linkDeleted) {
+            // Send LLDP right away when port state is changed for faster
+            // cluster-merge. If it is a link delete then there is not need
+            // to send the LLDPs right away and instead we wait for the LLDPs
+            // to be sent on the timer as it is normally done
+            // do it outside the write-lock
+            // sendLLDPTask.reschedule(1000, TimeUnit.MILLISECONDS);
+            processNewPort(npt.getNodeId(), npt.getPortId());
+        }
+        return Command.CONTINUE;
+    }
+
+    /**
+     * Process a new port.
+     * If link discovery is disabled on the port, then do nothing.
+     * If autoportfast feature is enabled and the port is a fast port, then
+     * do nothing.
+     * Otherwise, send LLDP message.  Add the port to quarantine.
+     * @param sw
+     * @param p
+     */
+    private void processNewPort(long sw, short p) {
+        if (isLinkDiscoverySuppressed(sw, p)) {
+            // Do nothing as link discovery is suppressed.
+        }
+        else if (autoPortFastFeature && isFastPort(sw, p)) {
+            // Do nothing as the port is a fast port.
+        }
+        else {
+            NodePortTuple npt = new NodePortTuple(sw, p);
+            discover(sw, p);
+            // if it is not a fast port, add it to quarantine.
+            if (!isFastPort(sw, p)) {
+                addToQuarantineQueue(npt);
+            } else {
+                // Add to maintenance queue to ensure that BDDP packets
+                // are sent out.
+                addToMaintenanceQueue(npt);
+            }
+        }
+    }
+
+    /**
+     * We send out LLDP messages when a switch is added to discover the topology
+     * @param sw The IOFSwitch that connected to the controller
+     */
+    @Override
+    public void addedSwitch(IOFSwitch sw) {
+
+        if (sw.getEnabledPorts() != null) {
+            for (Short p : sw.getEnabledPortNumbers()) {
+                processNewPort(sw.getId(), p);
+            }
+        }
+        // Update event history
+        evHistTopoSwitch(sw, EvAction.SWITCH_CONNECTED, "None");
+        LDUpdate update = new LDUpdate(sw.getId(), null,
+                                       UpdateOperation.SWITCH_UPDATED);
+        updates.add(update);
+    }
+
+    /**
+     * When a switch disconnects we remove any links from our map and notify.
+     * @param The id of the switch
+     */
+    @Override
+    public void removedSwitch(IOFSwitch iofSwitch) {
+        // Update event history
+        long sw = iofSwitch.getId();
+        evHistTopoSwitch(iofSwitch, EvAction.SWITCH_DISCONNECTED, "None");
+        List<Link> eraseList = new ArrayList<Link>();
+        lock.writeLock().lock();
+        try {
+            if (switchLinks.containsKey(sw)) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Handle switchRemoved. Switch {}; removing links {}",
+                              HexString.toHexString(sw), switchLinks.get(sw));
+                }
+                // add all tuples with an endpoint on this switch to erase list
+                eraseList.addAll(switchLinks.get(sw));
+                deleteLinks(eraseList, "Switch Removed");
+
+                // Send a switch removed update
+                LDUpdate update = new LDUpdate(sw, null, UpdateOperation.SWITCH_REMOVED);
+                updates.add(update);
+            }
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+    
+    /**
+     * We don't react the port changed notifications here. we listen for 
+     * OFPortStatus messages directly. Might consider using this notifier
+     * instead
+     */
+    @Override
+    public void switchPortChanged(Long switchId) {
+        // no-op
+    }
+
+    /** 
+     * Delete links incident on a given switch port.
+     * @param npt
+     * @param reason
+     */
+    protected void deleteLinksOnPort(NodePortTuple npt, String reason) {
+        List<Link> eraseList = new ArrayList<Link>();
+        if (this.portLinks.containsKey(npt)) {
+            if (log.isTraceEnabled()) {
+                log.trace("handlePortStatus: Switch {} port #{} " +
+                        "removing links {}",
+                        new Object[] {HexString.toHexString(npt.getNodeId()),
+                                      npt.getPortId(),
+                                      this.portLinks.get(npt)});
+            }
+            eraseList.addAll(this.portLinks.get(npt));
+            deleteLinks(eraseList, reason);
+        }
+    }
+
+    /** 
+     * Iterates through the list of links and deletes if the
+     * last discovery message reception time exceeds timeout values.
+     */
+    protected void timeoutLinks() {
+        List<Link> eraseList = new ArrayList<Link>();
+        Long curTime = System.currentTimeMillis();
+        boolean linkChanged = false;
+
+        // reentrant required here because deleteLink also write locks
+        lock.writeLock().lock();
+        try {
+            Iterator<Entry<Link, LinkInfo>> it =
+                    this.links.entrySet().iterator();
+            while (it.hasNext()) {
+                Entry<Link, LinkInfo> entry = it.next();
+                Link lt = entry.getKey();
+                LinkInfo info = entry.getValue();
+
+                // Timeout the unicast and multicast LLDP valid times
+                // independently.
+                if ((info.getUnicastValidTime() != null) && 
+                        (info.getUnicastValidTime() + (this.LINK_TIMEOUT * 1000) < curTime)){
+                    info.setUnicastValidTime(null);
+
+                    if (info.getMulticastValidTime() != null)
+                        addLinkToBroadcastDomain(lt);
+                    // Note that even if mTime becomes null later on,
+                    // the link would be deleted, which would trigger updateClusters().
+                    linkChanged = true;
+                }
+                if ((info.getMulticastValidTime()!= null) && 
+                        (info.getMulticastValidTime()+ (this.LINK_TIMEOUT * 1000) < curTime)) {
+                    info.setMulticastValidTime(null);
+                    // if uTime is not null, then link will remain as openflow
+                    // link. If uTime is null, it will be deleted.  So, we
+                    // don't care about linkChanged flag here.
+                    removeLinkFromBroadcastDomain(lt);
+                    linkChanged = true;
+                }
+                // Add to the erase list only if the unicast
+                // time is null.
+                if (info.getUnicastValidTime() == null && 
+                        info.getMulticastValidTime() == null){
+                    eraseList.add(entry.getKey());
+                } else if (linkChanged) {
+                    UpdateOperation operation;
+                    operation = getUpdateOperation(info.getSrcPortState(),
+                                                   info.getDstPortState());
+                    updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+                                             lt.getDst(), lt.getDstPort(),
+                                             getLinkType(lt, info),
+                                             operation));
+                }
+            }
+
+            // if any link was deleted or any link was changed.
+            if ((eraseList.size() > 0) || linkChanged) {
+                deleteLinks(eraseList, "LLDP timeout");
+            }
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    private boolean portEnabled(OFPhysicalPort port) {
+        if (port == null)
+            return false;
+        if ((OFPortConfig.OFPPC_PORT_DOWN.getValue() & port.getConfig()) > 0)
+            return false;
+        if ((OFPortState.OFPPS_LINK_DOWN.getValue() & port.getState()) > 0)
+            return false;
+        // Port STP state doesn't work with multiple VLANs, so ignore it for now
+        // if ((port.getState() & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue())
+        //    return false;
+        return true;
+    }
+
+    public Map<NodePortTuple, Set<Link>> getPortBroadcastDomainLinks() {
+        return portBroadcastDomainLinks;
+    }
+
+    @Override
+    public Map<Link, LinkInfo> getLinks() {
+        lock.readLock().lock();
+        Map<Link, LinkInfo> result;
+        try {
+            result = new HashMap<Link, LinkInfo>(links);
+        } finally {
+            lock.readLock().unlock();
+        }
+        return result;
+    }
+
+    protected void addLinkToBroadcastDomain(Link lt) {
+
+        NodePortTuple srcNpt, dstNpt;
+        srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+        dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+        if (!portBroadcastDomainLinks.containsKey(lt.getSrc()))
+            portBroadcastDomainLinks.put(srcNpt, new HashSet<Link>());
+        portBroadcastDomainLinks.get(srcNpt).add(lt);
+
+        if (!portBroadcastDomainLinks.containsKey(lt.getDst()))
+            portBroadcastDomainLinks.put(dstNpt, new HashSet<Link>());
+        portBroadcastDomainLinks.get(dstNpt).add(lt);
+    }
+
+    protected void removeLinkFromBroadcastDomain(Link lt) {
+
+        NodePortTuple srcNpt, dstNpt;
+        srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+        dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+        if (portBroadcastDomainLinks.containsKey(srcNpt)) {
+            portBroadcastDomainLinks.get(srcNpt).remove(lt);
+            if (portBroadcastDomainLinks.get(srcNpt).isEmpty())
+                portBroadcastDomainLinks.remove(srcNpt);
+        }
+
+        if (portBroadcastDomainLinks.containsKey(dstNpt)) {
+            portBroadcastDomainLinks.get(dstNpt).remove(lt);
+            if (portBroadcastDomainLinks.get(dstNpt).isEmpty())
+                portBroadcastDomainLinks.remove(dstNpt);
+        }
+    }
+
+    // STORAGE METHODS
+    /**
+     * Deletes all links from storage
+     */
+    void clearAllLinks() {
+        storageSource.deleteRowsAsync(LINK_TABLE_NAME, null);
+    }
+
+    /**
+     * Gets the storage key for a LinkTuple
+     * @param lt The LinkTuple to get
+     * @return The storage key as a String
+     */
+    private String getLinkId(Link lt) {
+        return HexString.toHexString(lt.getSrc()) +
+                "-" + lt.getSrcPort() + "-" +
+                HexString.toHexString(lt.getDst())+
+                "-" + lt.getDstPort();
+    }
+
+    /**
+     * Writes a LinkTuple and corresponding LinkInfo to storage
+     * @param lt The LinkTuple to write
+     * @param linkInfo The LinkInfo to write
+     */
+    protected void writeLinkToStorage(Link lt, LinkInfo linkInfo) {
+        LinkType type = getLinkType(lt, linkInfo);
+
+        // Write only direct links.  Do not write links to external
+        // L2 network.
+        // if (type != LinkType.DIRECT_LINK && type != LinkType.TUNNEL) {
+        //    return;
+        // }
+
+        Map<String, Object> rowValues = new HashMap<String, Object>();
+        String id = getLinkId(lt);
+        rowValues.put(LINK_ID, id);
+        rowValues.put(LINK_VALID_TIME, linkInfo.getUnicastValidTime());
+        String srcDpid = HexString.toHexString(lt.getSrc());
+        rowValues.put(LINK_SRC_SWITCH, srcDpid);
+        rowValues.put(LINK_SRC_PORT, lt.getSrcPort());
+
+        if (type == LinkType.DIRECT_LINK)
+            rowValues.put(LINK_TYPE, "internal");
+        else if (type == LinkType.MULTIHOP_LINK) 
+            rowValues.put(LINK_TYPE, "external");
+        else if (type == LinkType.TUNNEL) 
+            rowValues.put(LINK_TYPE, "tunnel"); 
+        else rowValues.put(LINK_TYPE, "invalid");
+
+        if (linkInfo.linkStpBlocked()) {
+            if (log.isTraceEnabled()) {
+                log.trace("writeLink, link {}, info {}, srcPortState Blocked",
+                          lt, linkInfo);
+            }
+            rowValues.put(LINK_SRC_PORT_STATE,
+                          OFPhysicalPort.OFPortState.OFPPS_STP_BLOCK.getValue());
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("writeLink, link {}, info {}, srcPortState {}",
+                          new Object[]{ lt, linkInfo, linkInfo.getSrcPortState() });
+            }
+            rowValues.put(LINK_SRC_PORT_STATE, linkInfo.getSrcPortState());
+        }
+        String dstDpid = HexString.toHexString(lt.getDst());
+        rowValues.put(LINK_DST_SWITCH, dstDpid);
+        rowValues.put(LINK_DST_PORT, lt.getDstPort());
+        if (linkInfo.linkStpBlocked()) {
+            if (log.isTraceEnabled()) {
+                log.trace("writeLink, link {}, info {}, dstPortState Blocked",
+                          lt, linkInfo);
+            }
+            rowValues.put(LINK_DST_PORT_STATE,
+                          OFPhysicalPort.OFPortState.OFPPS_STP_BLOCK.getValue());
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("writeLink, link {}, info {}, dstPortState {}",
+                          new Object[]{ lt, linkInfo, linkInfo.getDstPortState() });
+            }
+            rowValues.put(LINK_DST_PORT_STATE, linkInfo.getDstPortState());
+        }
+        storageSource.updateRowAsync(LINK_TABLE_NAME, rowValues);
+    }
+
+    public Long readLinkValidTime(Link lt) {
+        // FIXME: We're not currently using this right now, but if we start
+        // to use this again, we probably shouldn't use it in its current
+        // form, because it's doing synchronous storage calls. Depending
+        // on the context this may still be OK, but if it's being called
+        // on the packet in processing thread it should be reworked to
+        // use asynchronous storage calls.
+        Long validTime = null;
+        IResultSet resultSet = null;
+        try {
+            String[] columns = { LINK_VALID_TIME };
+            String id = getLinkId(lt);
+            resultSet = storageSource.executeQuery(LINK_TABLE_NAME, columns,
+                                                   new OperatorPredicate(LINK_ID, OperatorPredicate.Operator.EQ, id), null);
+            if (resultSet.next())
+                validTime = resultSet.getLong(LINK_VALID_TIME);
+        }
+        finally {
+            if (resultSet != null)
+                resultSet.close();
+        }
+        return validTime;
+    }
+
+    /**
+     * Removes a link from storage using an asynchronous call.
+     * @param lt The LinkTuple to delete.
+     */
+    protected void removeLinkFromStorage(Link lt) {
+        String id = getLinkId(lt);
+        storageSource.deleteRowAsync(LINK_TABLE_NAME, id);
+    }
+
+    @Override
+    public void addListener(ILinkDiscoveryListener listener) {
+        linkDiscoveryAware.add(listener);
+    }
+
+    /**
+     * Register a link discovery aware component
+     * @param linkDiscoveryAwareComponent
+     */
+    public void addLinkDiscoveryAware(ILinkDiscoveryListener linkDiscoveryAwareComponent) {
+        // TODO make this a copy on write set or lock it somehow
+        this.linkDiscoveryAware.add(linkDiscoveryAwareComponent);
+    }
+
+    /**
+     * Deregister a link discovery aware component
+     * @param linkDiscoveryAwareComponent
+     */
+    public void removeLinkDiscoveryAware(ILinkDiscoveryListener linkDiscoveryAwareComponent) {
+        // TODO make this a copy on write set or lock it somehow
+        this.linkDiscoveryAware.remove(linkDiscoveryAwareComponent);
+    }
+
+    /**
+     * Sets the IStorageSource to use for ITology
+     * @param storageSource the storage source to use
+     */
+    public void setStorageSource(IStorageSourceService storageSource) {
+        this.storageSource = storageSource;
+    }
+
+    /**
+     * Gets the storage source for this ITopology
+     * @return The IStorageSource ITopology is writing to
+     */
+    public IStorageSourceService getStorageSource() {
+        return storageSource;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        return false;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return false;
+    }
+
+    @Override
+    public void rowsModified(String tableName, Set<Object> rowKeys) {
+        Map<Long, IOFSwitch> switches = floodlightProvider.getSwitches();
+        ArrayList<IOFSwitch> updated_switches = new ArrayList<IOFSwitch>();
+        for(Object key: rowKeys) {
+            Long swId = new Long(HexString.toLong((String)key));
+            if (switches.containsKey(swId)) {
+                IOFSwitch sw = switches.get(swId);
+                boolean curr_status = sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH);
+                boolean new_status =  false;
+                IResultSet resultSet = null;
+
+                try {
+                    resultSet = storageSource.getRow(tableName, key);
+                    for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) {
+                        // In case of multiple rows, use the status in last row?
+                        Map<String, Object> row = it.next().getRow();
+                        if (row.containsKey(SWITCH_CONFIG_CORE_SWITCH)) {
+                            new_status = ((String)row.get(SWITCH_CONFIG_CORE_SWITCH)).equals("true");
+                        }
+                    }
+                }
+                finally {
+                    if (resultSet != null)
+                        resultSet.close();
+                }
+
+                if (curr_status != new_status) {
+                    updated_switches.add(sw);
+                }
+            } else {
+                if (log.isTraceEnabled()) {
+                    log.trace("Update for switch which has no entry in switch " +
+                            "list (dpid={}), a delete action.", (String)key);
+                }
+            }
+        }
+
+        for (IOFSwitch sw : updated_switches) {
+            // Set SWITCH_IS_CORE_SWITCH to it's inverse value
+            if (sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH)) {
+                sw.removeAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH);
+                if (log.isTraceEnabled()) {
+                    log.trace("SWITCH_IS_CORE_SWITCH set to False for {}", sw);
+                }
+                updates.add(new LDUpdate(sw.getId(), SwitchType.BASIC_SWITCH,
+                                         UpdateOperation.SWITCH_UPDATED));
+            }
+            else {
+                sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, new Boolean(true));
+                if (log.isTraceEnabled()) {
+                    log.trace("SWITCH_IS_CORE_SWITCH set to True for {}", sw);
+                }
+                updates.add(new LDUpdate(sw.getId(), SwitchType.CORE_SWITCH,
+                                         UpdateOperation.SWITCH_UPDATED));
+            }
+        }
+    }
+
+    @Override
+    public void rowsDeleted(String tableName, Set<Object> rowKeys) {
+        // Ignore delete events, the switch delete will do the right thing on it's own
+    }
+
+    // IFloodlightModule classes
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(ILinkDiscoveryService.class);
+        //l.add(ITopologyService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+    getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m = 
+        new HashMap<Class<? extends IFloodlightService>,
+        IFloodlightService>();
+        // We are the class that implements the service
+        m.put(ILinkDiscoveryService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        l.add(IStorageSourceService.class);
+        l.add(IThreadPoolService.class);
+        l.add(IRestApiService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
+        storageSource = context.getServiceImpl(IStorageSourceService.class);
+        threadPool = context.getServiceImpl(IThreadPoolService.class);
+        restApi = context.getServiceImpl(IRestApiService.class);
+
+        // Set the autoportfast feature to false.
+        this.autoPortFastFeature = false;
+
+        // We create this here because there is no ordering guarantee
+        this.linkDiscoveryAware = new ArrayList<ILinkDiscoveryListener>();
+        this.lock = new ReentrantReadWriteLock();
+        this.updates = new LinkedBlockingQueue<LDUpdate>();
+        this.links = new HashMap<Link, LinkInfo>();
+        this.portLinks = new HashMap<NodePortTuple, Set<Link>>();
+        this.suppressLinkDiscovery =
+                Collections.synchronizedSet(new HashSet<NodePortTuple>());
+        this.portBroadcastDomainLinks = new HashMap<NodePortTuple, Set<Link>>();
+        this.switchLinks = new HashMap<Long, Set<Link>>();
+        this.quarantineQueue = new LinkedBlockingQueue<NodePortTuple>();
+        this.maintenanceQueue = new LinkedBlockingQueue<NodePortTuple>();
+
+        this.evHistTopologySwitch =
+                new EventHistory<EventHistoryTopologySwitch>("Topology: Switch");
+        this.evHistTopologyLink =
+                new EventHistory<EventHistoryTopologyLink>("Topology: Link");
+        this.evHistTopologyCluster =
+                new EventHistory<EventHistoryTopologyCluster>("Topology: Cluster");
+    }
+
+    @Override
+    @LogMessageDocs({
+        @LogMessageDoc(level="ERROR",
+                message="No storage source found.",
+                explanation="Storage source was not initialized; cannot initialize " +
+                "link discovery.",
+                recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG),
+        @LogMessageDoc(level="ERROR",
+                message="Error in installing listener for " +
+                        "switch config table {table}",
+                explanation="Failed to install storage notification for the " +
+                		"switch config table",
+                recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG),
+        @LogMessageDoc(level="ERROR",
+                message="No storage source found.",
+                explanation="Storage source was not initialized; cannot initialize " +
+                "link discovery.",
+                recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG),
+        @LogMessageDoc(level="ERROR",
+                message="Exception in LLDP send timer.",
+                explanation="An unknown error occured while sending LLDP " +
+                		"messages to switches.",
+                recommendation=LogMessageDoc.CHECK_SWITCH)
+    })
+    public void startUp(FloodlightModuleContext context) {
+        // Create our storage tables
+        if (storageSource == null) {
+            log.error("No storage source found.");
+            return;
+        }
+
+        storageSource.createTable(LINK_TABLE_NAME, null);
+        storageSource.setTablePrimaryKeyName(LINK_TABLE_NAME, LINK_ID);
+        storageSource.deleteMatchingRows(LINK_TABLE_NAME, null);
+        // Register for storage updates for the switch table
+        try {
+            storageSource.addListener(SWITCH_CONFIG_TABLE_NAME, this);
+        } catch (StorageException ex) {
+            log.error("Error in installing listener for " +
+            		  "switch table {}", SWITCH_CONFIG_TABLE_NAME);
+        }
+
+        ScheduledExecutorService ses = threadPool.getScheduledExecutor();
+
+        // To be started by the first switch connection
+        discoveryTask = new SingletonTask(ses, new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    discoverLinks();
+                } catch (StorageException e) {
+                    log.error("Storage exception in LLDP send timer; " + 
+                            "terminating process", e);
+                    floodlightProvider.terminate();
+                } catch (Exception e) {
+                    log.error("Exception in LLDP send timer.", e);
+                } finally {
+                    if (!shuttingDown) {
+                        // null role implies HA mode is not enabled.
+                         Role role = floodlightProvider.getRole();
+                         if (role == null || role == Role.MASTER) {
+                             log.trace("Rescheduling discovery task as role = {}", role);
+                             discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL,
+                                                TimeUnit.SECONDS);
+                         } else {
+                             log.trace("Stopped LLDP rescheduling due to role = {}.", role);
+                         }
+                    }
+                }
+            }
+        });
+
+        // null role implies HA mode is not enabled.
+        Role role = floodlightProvider.getRole();
+        if (role == null || role == Role.MASTER) {
+            log.trace("Setup: Rescheduling discovery task. role = {}", role);
+            discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, TimeUnit.SECONDS);
+        } else {
+                log.trace("Setup: Not scheduling LLDP as role = {}.", role);
+        }
+
+        // Setup the BDDP task.  It is invoked whenever switch port tuples
+        // are added to the quarantine list.
+        bddpTask = new SingletonTask(ses, new QuarantineWorker());
+        bddpTask.reschedule(BDDP_TASK_INTERVAL, TimeUnit.MILLISECONDS);
+
+        updatesThread = new Thread(new Runnable () {
+            @Override
+            public void run() {
+                while (true) {
+                    try {
+                        doUpdatesThread();
+                    } catch (InterruptedException e) {
+                        return;
+                    }
+                }
+            }}, "Topology Updates");
+        updatesThread.start();
+
+
+
+        // Register for the OpenFlow messages we want to receive
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+        floodlightProvider.addOFMessageListener(OFType.PORT_STATUS, this);
+        // Register for switch updates
+        floodlightProvider.addOFSwitchListener(this);
+        floodlightProvider.addHAListener(this);
+        floodlightProvider.addInfoProvider("summary", this);
+        if (restApi != null)
+            restApi.addRestletRoutable(new LinkDiscoveryWebRoutable());
+        setControllerTLV();
+    }
+
+    // ****************************************************
+    // Topology Manager's Event History members and methods
+    // ****************************************************
+
+    // Topology Manager event history
+    public EventHistory<EventHistoryTopologySwitch>  evHistTopologySwitch;
+    public EventHistory<EventHistoryTopologyLink>    evHistTopologyLink;
+    public EventHistory<EventHistoryTopologyCluster> evHistTopologyCluster;
+    public EventHistoryTopologySwitch  evTopoSwitch;
+    public EventHistoryTopologyLink    evTopoLink;
+    public EventHistoryTopologyCluster evTopoCluster;
+
+    // Switch Added/Deleted
+    private void evHistTopoSwitch(IOFSwitch sw, EvAction actn, String reason) {
+        if (evTopoSwitch == null) {
+            evTopoSwitch = new EventHistoryTopologySwitch();
+        }
+        evTopoSwitch.dpid     = sw.getId();
+        if ((sw.getChannel() != null) &&
+                (SocketAddress.class.isInstance(
+                                                sw.getChannel().getRemoteAddress()))) {
+            evTopoSwitch.ipv4Addr = 
+                    IPv4.toIPv4Address(((InetSocketAddress)(sw.getChannel().
+                            getRemoteAddress())).getAddress().getAddress());
+            evTopoSwitch.l4Port   =
+                    ((InetSocketAddress)(sw.getChannel().
+                            getRemoteAddress())).getPort();
+        } else {
+            evTopoSwitch.ipv4Addr = 0;
+            evTopoSwitch.l4Port = 0;
+        }
+        evTopoSwitch.reason   = reason;
+        evTopoSwitch = evHistTopologySwitch.put(evTopoSwitch, actn);
+    }
+
+    private void evHistTopoLink(long srcDpid, long dstDpid, short srcPort,
+                                short dstPort, int srcPortState, int dstPortState,
+                                ILinkDiscovery.LinkType linkType,
+                                EvAction actn, String reason) {
+        if (evTopoLink == null) {
+            evTopoLink = new EventHistoryTopologyLink();
+        }
+        evTopoLink.srcSwDpid = srcDpid;
+        evTopoLink.dstSwDpid = dstDpid;
+        evTopoLink.srcSwport = srcPort & 0xffff;
+        evTopoLink.dstSwport = dstPort & 0xffff;
+        evTopoLink.srcPortState = srcPortState;
+        evTopoLink.dstPortState = dstPortState;
+        evTopoLink.reason    = reason;
+        switch (linkType) {
+            case DIRECT_LINK:
+                evTopoLink.linkType = "DIRECT_LINK";
+                break;
+            case MULTIHOP_LINK:
+                evTopoLink.linkType = "MULTIHOP_LINK";
+                break;
+            case TUNNEL:
+                evTopoLink.linkType = "TUNNEL";
+                break;
+            case INVALID_LINK:
+            default:
+                evTopoLink.linkType = "Unknown";
+                break;
+        }
+        evTopoLink = evHistTopologyLink.put(evTopoLink, actn);
+    }
+
+    public void evHistTopoCluster(long dpid, long clusterIdOld,
+                                  long clusterIdNew, EvAction action, String reason) {
+        if (evTopoCluster == null) {
+            evTopoCluster = new EventHistoryTopologyCluster();
+        }
+        evTopoCluster.dpid         = dpid;
+        evTopoCluster.clusterIdOld = clusterIdOld;
+        evTopoCluster.clusterIdNew = clusterIdNew;
+        evTopoCluster.reason       = reason;
+        evTopoCluster = evHistTopologyCluster.put(evTopoCluster, action);
+    }
+
+    @Override
+    public Map<String, Object> getInfo(String type) {
+        if (!"summary".equals(type)) return null;
+
+        Map<String, Object> info = new HashMap<String, Object>();
+
+        int num_links = 0;
+        for (Set<Link> links : switchLinks.values())
+            num_links += links.size();
+        info.put("# inter-switch links", num_links / 2);
+
+        return info;
+    }
+
+    // IHARoleListener
+    @Override
+    public void roleChanged(Role oldRole, Role newRole) {
+        switch(newRole) {
+            case MASTER:
+                if (oldRole == Role.SLAVE) {
+                    if (log.isTraceEnabled()) {
+                        log.trace("Sending LLDPs " +
+                                "to HA change from SLAVE->MASTER");
+                    }
+                    clearAllLinks();
+                    log.debug("Role Change to Master: Rescheduling discovery task.");
+                    discoveryTask.reschedule(1, TimeUnit.MICROSECONDS);
+                }
+                break;
+            case SLAVE:
+                if (log.isTraceEnabled()) {
+                    log.trace("Clearing links due to " +
+                            "HA change to SLAVE");
+                }
+                switchLinks.clear();
+                links.clear();
+                portLinks.clear();
+                portBroadcastDomainLinks.clear();
+                discoverOnAllPorts();
+                break;
+            default:
+                break;
+        }
+    }
+
+    @Override
+    public void controllerNodeIPsChanged(
+                                         Map<String, String> curControllerNodeIPs,
+                                         Map<String, String> addedControllerNodeIPs,
+                                         Map<String, String> removedControllerNodeIPs) {
+        // ignore
+    }
+
+    public boolean isAutoPortFastFeature() {
+        return autoPortFastFeature;
+    }
+
+    public void setAutoPortFastFeature(boolean autoPortFastFeature) {
+        this.autoPortFastFeature = autoPortFastFeature;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/AutoPortFast.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/AutoPortFast.java
new file mode 100644
index 0000000..8f4f4ad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/AutoPortFast.java
@@ -0,0 +1,31 @@
+package net.floodlightcontroller.linkdiscovery.web;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AutoPortFast extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(AutoPortFast.class);
+
+    @Get("json")
+    public String retrieve() {
+        ILinkDiscoveryService linkDiscovery;
+        linkDiscovery = (ILinkDiscoveryService)getContext().getAttributes().
+                get(ILinkDiscoveryService.class.getCanonicalName());
+
+        String param = ((String)getRequestAttributes().get("state")).toLowerCase();
+        if (param.equals("enable") || param.equals("true")) {
+            linkDiscovery.setAutoPortFastFeature(true);
+        } else if (param.equals("disable") || param.equals("false")) {
+            linkDiscovery.setAutoPortFastFeature(false);
+        }
+        setStatus(Status.SUCCESS_OK, "OK");
+        if (linkDiscovery.isAutoPortFastFeature())
+            return "enabled";
+        else return "disabled";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkDiscoveryWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkDiscoveryWebRoutable.java
new file mode 100644
index 0000000..3990fba
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkDiscoveryWebRoutable.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.linkdiscovery.web;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.routing.Router;
+
+public class LinkDiscoveryWebRoutable implements RestletRoutable {
+    /**
+     * Create the Restlet router and bind to the proper resources.
+     */
+    @Override
+    public Router getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/autoportfast/{state}/json", AutoPortFast.class); // enable/true or disable/false
+        return router;
+    }
+
+    /**
+     * Set the base path for the Topology
+     */
+    @Override
+    public String basePath() {
+        return "/wm/linkdiscovery";
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java
new file mode 100644
index 0000000..893e4ad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java
@@ -0,0 +1,65 @@
+package net.floodlightcontroller.linkdiscovery.web;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkType;
+import net.floodlightcontroller.routing.Link;
+
+/**
+ * This class is both the datastructure and the serializer
+ * for a link with the corresponding type of link.
+ * @author alexreimers
+ */
+@JsonSerialize(using=LinkWithType.class)
+public class LinkWithType extends JsonSerializer<LinkWithType> {
+    public long srcSwDpid;
+    public short srcPort;
+    public int srcPortState;
+    public long dstSwDpid;
+    public short dstPort;
+    public int dstPortState;
+    public LinkType type;
+
+    // Do NOT delete this, it's required for the serializer
+    public LinkWithType() {}
+    
+    public LinkWithType(Link link,
+                        int srcPortState,
+                        int dstPortState,
+                        LinkType type) {
+        this.srcSwDpid = link.getSrc();
+        this.srcPort = link.getSrcPort();
+        this.srcPortState = srcPortState;
+        this.dstSwDpid = link.getDst();
+        this.dstPort = link.getDstPort();
+        this.dstPortState = dstPortState;
+        this.type = type;
+    }
+
+	@Override
+	public void serialize(LinkWithType lwt, JsonGenerator jgen, SerializerProvider arg2) 
+			throws IOException, JsonProcessingException {
+		// You ****MUST*** use lwt for the fields as it's actually a different object.
+		jgen.writeStartObject();
+		jgen.writeStringField("src-switch", HexString.toHexString(lwt.srcSwDpid));
+		jgen.writeNumberField("src-port", lwt.srcPort);
+		jgen.writeNumberField("src-port-state", lwt.srcPortState);
+		jgen.writeStringField("dst-switch", HexString.toHexString(lwt.dstSwDpid));
+		jgen.writeNumberField("dst-port", lwt.dstPort);
+		jgen.writeNumberField("dst-port-state", lwt.dstPortState);
+		jgen.writeStringField("type", lwt.type.toString());
+		jgen.writeEndObject();
+	}
+	
+	@Override
+	public Class<LinkWithType> handledType() {
+		return LinkWithType.class;
+	}
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java
new file mode 100644
index 0000000..4cad18e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java
@@ -0,0 +1,37 @@
+package net.floodlightcontroller.linkdiscovery.web;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.LinkInfo;
+import net.floodlightcontroller.routing.Link;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class LinksResource extends ServerResource {
+
+    @Get("json")
+    public Set<LinkWithType> retrieve() {
+        ILinkDiscoveryService ld = (ILinkDiscoveryService)getContext().getAttributes().
+                get(ILinkDiscoveryService.class.getCanonicalName());
+        Map<Link, LinkInfo> links = new HashMap<Link, LinkInfo>();
+        Set<LinkWithType> returnLinkSet = new HashSet<LinkWithType>();
+
+        if (ld != null) {
+            links.putAll(ld.getLinks());
+            for (Link link: links.keySet()) {
+                LinkInfo info = links.get(link);
+                LinkWithType lwt = new LinkWithType(link,
+                                                    info.getSrcPortState(),
+                                                    info.getDstPortState(),
+                                                    ld.getLinkType(link, info));
+                returnLinkSet.add(lwt);
+            }
+        }
+        return returnLinkSet;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ARP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ARP.java
new file mode 100644
index 0000000..e8428ea
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ARP.java
@@ -0,0 +1,316 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class ARP extends BasePacket {
+    public static short HW_TYPE_ETHERNET = 0x1;
+
+    public static short PROTO_TYPE_IP = 0x800;
+
+    public static short OP_REQUEST = 0x1;
+    public static short OP_REPLY = 0x2;
+    public static short OP_RARP_REQUEST = 0x3;
+    public static short OP_RARP_REPLY = 0x4;
+
+    protected short hardwareType;
+    protected short protocolType;
+    protected byte hardwareAddressLength;
+    protected byte protocolAddressLength;
+    protected short opCode;
+    protected byte[] senderHardwareAddress;
+    protected byte[] senderProtocolAddress;
+    protected byte[] targetHardwareAddress;
+    protected byte[] targetProtocolAddress;
+
+    /**
+     * @return the hardwareType
+     */
+    public short getHardwareType() {
+        return hardwareType;
+    }
+
+    /**
+     * @param hardwareType the hardwareType to set
+     */
+    public ARP setHardwareType(short hardwareType) {
+        this.hardwareType = hardwareType;
+        return this;
+    }
+
+    /**
+     * @return the protocolType
+     */
+    public short getProtocolType() {
+        return protocolType;
+    }
+
+    /**
+     * @param protocolType the protocolType to set
+     */
+    public ARP setProtocolType(short protocolType) {
+        this.protocolType = protocolType;
+        return this;
+    }
+
+    /**
+     * @return the hardwareAddressLength
+     */
+    public byte getHardwareAddressLength() {
+        return hardwareAddressLength;
+    }
+
+    /**
+     * @param hardwareAddressLength the hardwareAddressLength to set
+     */
+    public ARP setHardwareAddressLength(byte hardwareAddressLength) {
+        this.hardwareAddressLength = hardwareAddressLength;
+        return this;
+    }
+
+    /**
+     * @return the protocolAddressLength
+     */
+    public byte getProtocolAddressLength() {
+        return protocolAddressLength;
+    }
+
+    /**
+     * @param protocolAddressLength the protocolAddressLength to set
+     */
+    public ARP setProtocolAddressLength(byte protocolAddressLength) {
+        this.protocolAddressLength = protocolAddressLength;
+        return this;
+    }
+
+    /**
+     * @return the opCode
+     */
+    public short getOpCode() {
+        return opCode;
+    }
+
+    /**
+     * @param opCode the opCode to set
+     */
+    public ARP setOpCode(short opCode) {
+        this.opCode = opCode;
+        return this;
+    }
+
+    /**
+     * @return the senderHardwareAddress
+     */
+    public byte[] getSenderHardwareAddress() {
+        return senderHardwareAddress;
+    }
+
+    /**
+     * @param senderHardwareAddress the senderHardwareAddress to set
+     */
+    public ARP setSenderHardwareAddress(byte[] senderHardwareAddress) {
+        this.senderHardwareAddress = senderHardwareAddress;
+        return this;
+    }
+
+    /**
+     * @return the senderProtocolAddress
+     */
+    public byte[] getSenderProtocolAddress() {
+        return senderProtocolAddress;
+    }
+
+    /**
+     * @param senderProtocolAddress the senderProtocolAddress to set
+     */
+    public ARP setSenderProtocolAddress(byte[] senderProtocolAddress) {
+        this.senderProtocolAddress = senderProtocolAddress;
+        return this;
+    }
+    
+    public ARP setSenderProtocolAddress(int address) {
+        this.senderProtocolAddress = ByteBuffer.allocate(4).putInt(address).array();
+        return this;
+    }
+
+    /**
+     * @return the targetHardwareAddress
+     */
+    public byte[] getTargetHardwareAddress() {
+        return targetHardwareAddress;
+    }
+
+    /**
+     * @param targetHardwareAddress the targetHardwareAddress to set
+     */
+    public ARP setTargetHardwareAddress(byte[] targetHardwareAddress) {
+        this.targetHardwareAddress = targetHardwareAddress;
+        return this;
+    }
+
+    /**
+     * @return the targetProtocolAddress
+     */
+    public byte[] getTargetProtocolAddress() {
+        return targetProtocolAddress;
+    }
+
+    /**
+     * @return True if gratuitous ARP (SPA = TPA), false otherwise
+     */
+    public boolean isGratuitous() {        
+        assert(senderProtocolAddress.length == targetProtocolAddress.length);
+        
+        int indx = 0;
+        while (indx < senderProtocolAddress.length) {
+            if (senderProtocolAddress[indx] != targetProtocolAddress[indx]) {
+                return false;
+            }
+            indx++;
+        }
+        
+        return true;
+    }
+    
+    /**
+     * @param targetProtocolAddress the targetProtocolAddress to set
+     */
+    public ARP setTargetProtocolAddress(byte[] targetProtocolAddress) {
+        this.targetProtocolAddress = targetProtocolAddress;
+        return this;
+    }
+    
+    public ARP setTargetProtocolAddress(int address) {
+        this.targetProtocolAddress = ByteBuffer.allocate(4).putInt(address).array();
+        return this;
+    }
+
+    @Override
+    public byte[] serialize() {
+        int length = 8 + (2 * (0xff & this.hardwareAddressLength))
+                + (2 * (0xff & this.protocolAddressLength));
+        byte[] data = new byte[length];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.putShort(this.hardwareType);
+        bb.putShort(this.protocolType);
+        bb.put(this.hardwareAddressLength);
+        bb.put(this.protocolAddressLength);
+        bb.putShort(this.opCode);
+        bb.put(this.senderHardwareAddress, 0, 0xff & this.hardwareAddressLength);
+        bb.put(this.senderProtocolAddress, 0, 0xff & this.protocolAddressLength);
+        bb.put(this.targetHardwareAddress, 0, 0xff & this.hardwareAddressLength);
+        bb.put(this.targetProtocolAddress, 0, 0xff & this.protocolAddressLength);
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.hardwareType = bb.getShort();
+        this.protocolType = bb.getShort();
+        this.hardwareAddressLength = bb.get();
+        this.protocolAddressLength = bb.get();
+        this.opCode = bb.getShort();
+        this.senderHardwareAddress = new byte[0xff & this.hardwareAddressLength];
+        bb.get(this.senderHardwareAddress, 0, this.senderHardwareAddress.length);
+        this.senderProtocolAddress = new byte[0xff & this.protocolAddressLength];
+        bb.get(this.senderProtocolAddress, 0, this.senderProtocolAddress.length);
+        this.targetHardwareAddress = new byte[0xff & this.hardwareAddressLength];
+        bb.get(this.targetHardwareAddress, 0, this.targetHardwareAddress.length);
+        this.targetProtocolAddress = new byte[0xff & this.protocolAddressLength];
+        bb.get(this.targetProtocolAddress, 0, this.targetProtocolAddress.length);
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 13121;
+        int result = super.hashCode();
+        result = prime * result + hardwareAddressLength;
+        result = prime * result + hardwareType;
+        result = prime * result + opCode;
+        result = prime * result + protocolAddressLength;
+        result = prime * result + protocolType;
+        result = prime * result + Arrays.hashCode(senderHardwareAddress);
+        result = prime * result + Arrays.hashCode(senderProtocolAddress);
+        result = prime * result + Arrays.hashCode(targetHardwareAddress);
+        result = prime * result + Arrays.hashCode(targetProtocolAddress);
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof ARP))
+            return false;
+        ARP other = (ARP) obj;
+        if (hardwareAddressLength != other.hardwareAddressLength)
+            return false;
+        if (hardwareType != other.hardwareType)
+            return false;
+        if (opCode != other.opCode)
+            return false;
+        if (protocolAddressLength != other.protocolAddressLength)
+            return false;
+        if (protocolType != other.protocolType)
+            return false;
+        if (!Arrays.equals(senderHardwareAddress, other.senderHardwareAddress))
+            return false;
+        if (!Arrays.equals(senderProtocolAddress, other.senderProtocolAddress))
+            return false;
+        if (!Arrays.equals(targetHardwareAddress, other.targetHardwareAddress))
+            return false;
+        if (!Arrays.equals(targetProtocolAddress, other.targetProtocolAddress))
+            return false;
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "ARP [hardwareType=" + hardwareType + ", protocolType="
+                + protocolType + ", hardwareAddressLength="
+                + hardwareAddressLength + ", protocolAddressLength="
+                + protocolAddressLength + ", opCode=" + opCode
+                + ", senderHardwareAddress="
+                + Arrays.toString(senderHardwareAddress)
+                + ", senderProtocolAddress="
+                + Arrays.toString(senderProtocolAddress)
+                + ", targetHardwareAddress="
+                + Arrays.toString(targetHardwareAddress)
+                + ", targetProtocolAddress="
+                + Arrays.toString(targetProtocolAddress) + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BPDU.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BPDU.java
new file mode 100644
index 0000000..6c27216
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BPDU.java
@@ -0,0 +1,138 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This class is a Rapid Spanning Tree Protocol
+ * Bridge Protocol Data Unit
+ * @author alexreimers
+ */
+public class BPDU extends BasePacket {
+    public enum BPDUType {
+        CONFIG,
+        TOPOLOGY_CHANGE;
+    }
+    
+    private final long destMac = 0x0180c2000000L; // 01-80-c2-00-00-00
+    
+    // TODO - check this for RSTP
+    private LLC llcHeader;
+    private short protocolId = 0;
+    private byte version = 0;
+    private byte type;
+    private byte flags;
+    private byte[] rootBridgeId;
+    private int rootPathCost;
+    private byte[] senderBridgeId; // switch cluster MAC
+    private short portId; // port it was transmitted from
+    private short messageAge; // 256ths of a second
+    private short maxAge; // 256ths of a second
+    private short helloTime; // 256ths of a second
+    private short forwardDelay; // 256ths of a second
+    
+    public BPDU(BPDUType type) {
+        rootBridgeId = new byte[8];
+        senderBridgeId = new byte[8];
+        
+        llcHeader = new LLC();
+        llcHeader.setDsap((byte) 0x42);
+        llcHeader.setSsap((byte) 0x42);
+        llcHeader.setCtrl((byte) 0x03);
+        
+        switch(type) {
+            case CONFIG:
+                this.type = 0x0;
+                break;
+            case TOPOLOGY_CHANGE:
+                this.type = (byte) 0x80; // 1000 0000
+                break;
+            default:
+                this.type = 0;
+                break;
+        }
+    }
+    
+    @Override
+    public byte[] serialize() {
+        byte[] data;
+        // TODO check these
+        if (type == 0x0) { 
+            // config
+            data = new byte[38];
+        } else {
+            // topology change
+            data = new byte[7]; // LLC + TC notification
+        }
+        
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        // Serialize the LLC header
+        byte[] llc = llcHeader.serialize();
+        bb.put(llc, 0, llc.length);
+        bb.putShort(protocolId);
+        bb.put(version);
+        bb.put(type);
+        
+        if (type == 0x0) {
+            bb.put(flags);
+            bb.put(rootBridgeId, 0, rootBridgeId.length);
+            bb.putInt(rootPathCost);
+            bb.put(senderBridgeId, 0, senderBridgeId.length);
+            bb.putShort(portId);
+            bb.putShort(messageAge);
+            bb.putShort(maxAge);
+            bb.putShort(helloTime);
+            bb.putShort(forwardDelay);
+        }
+        
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        
+        // LLC header
+        llcHeader.deserialize(data, offset, 3);
+        
+        this.protocolId = bb.getShort();
+        this.version = bb.get();
+        this.type = bb.get();
+        
+        // These fields only exist if it's a configuration BPDU
+        if (this.type == 0x0) {
+            this.flags = bb.get();
+            bb.get(rootBridgeId, 0, 6);
+            this.rootPathCost = bb.getInt();
+            bb.get(this.senderBridgeId, 0, 6);
+            this.portId = bb.getShort();
+            this.messageAge = bb.getShort();
+            this.maxAge = bb.getShort();
+            this.helloTime = bb.getShort();
+            this.forwardDelay = bb.getShort();
+        }
+        // TODO should we set other fields to 0?
+        
+        return this;
+    }
+
+    public long getDestMac() {
+        return destMac;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSN.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSN.java
new file mode 100644
index 0000000..27c8f70
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSN.java
@@ -0,0 +1,172 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Shudong Zhou (shudong.zhou@bigswitch.com)
+ *
+ */
+public class BSN extends BasePacket {
+	public static final int BSN_MAGIC = 0x20000604;
+	public static final short BSN_VERSION_CURRENT = 0x0;
+	public static final short BSN_TYPE_PROBE = 0x1;
+	public static final short BSN_TYPE_BDDP  = 0x2;
+	public static Map<Short, Class<? extends IPacket>> typeClassMap;
+	
+    static {
+        typeClassMap = new HashMap<Short, Class<? extends IPacket>>();
+        typeClassMap.put(BSN_TYPE_PROBE, BSNPROBE.class);
+        typeClassMap.put(BSN_TYPE_BDDP, LLDP.class);
+    }
+
+	protected short type;
+	protected short version;
+
+	public BSN() {
+    	version = BSN_VERSION_CURRENT;
+	}
+	
+    public BSN(short type) {
+    	this.type = type;
+    	version = BSN_VERSION_CURRENT;
+    }
+
+    public short getType() {
+		return type;
+	}
+
+	public BSN setType(short type) {
+		this.type = type;
+		return this;
+	}
+	
+    public short getVersion() {
+		return version;
+	}
+
+	public BSN setVersion(short version) {
+		this.version = version;
+		return this;
+	}
+
+    @Override
+    public byte[] serialize() {
+    	short length = 4 /* magic */ + 2 /* type */ + 2 /* version */;
+    	
+    	byte[] payloadData = null;
+    	if (this.payload != null) {
+            payload.setParent(this);
+            payloadData = payload.serialize();
+            length += payloadData.length;
+        }
+    
+        byte[] data = new byte[length];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.putInt(BSN_MAGIC);
+        bb.putShort(this.type);
+        bb.putShort(this.version);
+        if (payloadData != null)
+        	bb.put(payloadData);
+
+        if (this.parent != null && this.parent instanceof Ethernet)
+            ((Ethernet)this.parent).setEtherType(Ethernet.TYPE_BSN);
+
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        
+        int magic = bb.getInt();
+        if (magic != BSN_MAGIC) {
+        	throw new RuntimeException("Invalid BSN magic " + magic);
+        }
+        
+        this.type = bb.getShort();
+        this.version = bb.getShort();
+        if (this.version != BSN_VERSION_CURRENT) {
+        	throw new RuntimeException(
+        			"Invalid BSN packet version " + this.version + ", should be "
+        	        + BSN_VERSION_CURRENT);
+        }
+        
+        IPacket payload;
+        if (typeClassMap.containsKey(this.type)) {
+            Class<? extends IPacket> clazz = typeClassMap.get(this.type);
+            try {
+                payload = clazz.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException("Error parsing payload for BSN packet" + e);
+            }
+        } else {
+            payload = new Data();
+        }
+        
+        this.payload = new Data();
+        this.payload = payload.deserialize(data, bb.position(), bb.limit() - bb.position());
+        this.payload.setParent(this);
+        
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 883;
+        int result = super.hashCode();
+        result = prime * result + version;
+        result = prime * result + type;
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof BSN))
+            return false;
+        BSN other = (BSN) obj;
+        return (type == other.type &&
+        		version == other.version);
+    }
+    
+    public String toString() {
+    	StringBuffer sb = new StringBuffer("\n");
+    	sb.append("BSN packet");
+        if (typeClassMap.containsKey(this.type))
+        	sb.append(" type: " + typeClassMap.get(this.type).getCanonicalName());
+        else
+        	sb.append(" type: " + this.type);
+        
+    	return sb.toString();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java
new file mode 100644
index 0000000..720b45f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java
@@ -0,0 +1,197 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.openflow.util.HexString;
+
+/**
+ * @author Shudong Zhou (shudong.zhou@bigswitch.com)
+ *
+ */
+public class BSNPROBE extends BasePacket {	
+	protected long controllerId;
+	protected int sequenceId;
+	protected byte[] srcMac;
+	protected byte[] dstMac;
+	protected long srcSwDpid;
+	protected int srcPortNo;
+
+    public BSNPROBE() {
+        srcMac = new byte[6];
+        dstMac = new byte[6];
+    }
+
+
+	public long getControllerId() {
+		return this.controllerId;
+	}
+
+	public BSNPROBE setControllerId(long controllerId) {
+		this.controllerId = controllerId;
+		return this;
+	}
+
+	public int getSequenceId() {
+		return sequenceId;
+	}
+
+	public BSNPROBE setSequenceId(int sequenceId) {
+		this.sequenceId = sequenceId;
+		return this;
+	}
+	
+    public byte[] getSrcMac() {
+        return this.srcMac;
+    }
+
+    public BSNPROBE setSrcMac(byte[] srcMac) {
+        this.srcMac = srcMac;
+        return this;
+    }
+    
+	public byte[] getDstMac() {
+		return dstMac;
+	}
+
+	public BSNPROBE setDstMac(byte[] dstMac) {
+		this.dstMac = dstMac;
+		return this;
+	}
+
+	public long getSrcSwDpid() {
+		return srcSwDpid;
+	}
+
+	public BSNPROBE setSrcSwDpid(long srcSwDpid) {
+		this.srcSwDpid = srcSwDpid;
+		return this;
+	}
+
+	public int getSrcPortNo() {
+		return srcPortNo;
+	}
+
+	public BSNPROBE setSrcPortNo(int srcPortNo) {
+		this.srcPortNo = srcPortNo;
+		return this;
+	}
+
+    @Override
+    public byte[] serialize() {
+    	short length = 8 /* controllerId */ + 4 /* seqId */
+    			+ 12 /* srcMac dstMac */ + 8 /* srcSwDpid */ + 4 /* srcPortNo */;
+    	
+    	byte[] payloadData = null;
+    	if (this.payload != null) {
+            payload.setParent(this);
+            payloadData = payload.serialize();
+            length += payloadData.length;
+        }
+    
+        byte[] data = new byte[length];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.putLong(this.controllerId);
+        bb.putInt(this.sequenceId);
+        bb.put(this.srcMac);
+        bb.put(this.dstMac);
+        bb.putLong(this.srcSwDpid);
+        bb.putInt(this.srcPortNo);
+        if (payloadData != null)
+        	bb.put(payloadData);
+
+        if (this.parent != null && this.parent instanceof BSN)
+            ((BSN)this.parent).setType(BSN.BSN_TYPE_PROBE);
+
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        
+        controllerId = bb.getLong();
+        sequenceId = bb.getInt();
+        bb.get(this.srcMac, 0, 6);
+        bb.get(this.dstMac, 0, 6);
+        this.srcSwDpid = bb.getLong();
+        this.srcPortNo = bb.getInt();
+        
+        if (bb.hasRemaining()) {
+        	this.payload = new Data();
+	        this.payload = payload.deserialize(data, bb.position(), bb.limit() - bb.position());
+	        this.payload.setParent(this);
+        }
+        
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 883;
+        int result = super.hashCode();
+        result = prime * result + srcMac.hashCode();
+        result = prime * result + dstMac.hashCode();
+        result = prime * result + (int) (srcSwDpid >> 32) + (int) srcSwDpid;
+        result = prime * result + srcPortNo;
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof BSNPROBE))
+            return false;
+        BSNPROBE other = (BSNPROBE) obj;
+        if (!Arrays.equals(srcMac, other.srcMac))
+            return false;
+        if (!Arrays.equals(dstMac, other.dstMac))
+        	return false;
+        return (sequenceId == other.sequenceId &&
+        	    srcSwDpid == other.srcSwDpid &&
+        	    srcPortNo == other.srcPortNo
+        	    );
+    }
+    
+    public String toString() {
+    	StringBuffer sb = new StringBuffer("\n");
+    	sb.append("BSN Probe packet");
+    	sb.append("\nSource Mac: ");
+    	sb.append(HexString.toHexString(srcMac));
+    	sb.append("\nDestination Mac: ");
+    	sb.append(HexString.toHexString(dstMac));
+    	sb.append("\nSource Switch: ");
+    	sb.append(HexString.toHexString(srcSwDpid));
+    	sb.append(" port: " + srcPortNo);
+    	sb.append("\nSequence No.:" + sequenceId);
+    	
+    	return sb.toString();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BasePacket.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BasePacket.java
new file mode 100644
index 0000000..4ecfded
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BasePacket.java
@@ -0,0 +1,116 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+
+/**
+*
+* @author David Erickson (daviderickson@cs.stanford.edu)
+*/
+public abstract class BasePacket implements IPacket {
+    protected IPacket parent;
+    protected IPacket payload;
+
+    /**
+     * @return the parent
+     */
+    @Override
+    public IPacket getParent() {
+        return parent;
+    }
+
+    /**
+     * @param parent the parent to set
+     */
+    @Override
+    public IPacket setParent(IPacket parent) {
+        this.parent = parent;
+        return this;
+    }
+
+    /**
+     * @return the payload
+     */
+    @Override
+    public IPacket getPayload() {
+        return payload;
+    }
+
+    /**
+     * @param payload the payload to set
+     */
+    @Override
+    public IPacket setPayload(IPacket payload) {
+        this.payload = payload;
+        return this;
+    }
+    
+    @Override
+    public void resetChecksum() {
+        if (this.parent != null)
+            this.parent.resetChecksum();
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 6733;
+        int result = 1;
+        result = prime * result + ((payload == null) ? 0 : payload.hashCode());
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (!(obj instanceof BasePacket))
+            return false;
+        BasePacket other = (BasePacket) obj;
+        if (payload == null) {
+            if (other.payload != null)
+                return false;
+        } else if (!payload.equals(other.payload))
+            return false;
+        return true;
+    }
+    
+    @Override
+    public Object clone() {
+        IPacket pkt;
+        try {
+            pkt = this.getClass().newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException("Could not clone packet");
+        }
+        // TODO: we are using serialize()/deserialize() to perform the 
+        // cloning. Not the most efficient way but simple. We can revisit
+        // if we hit performance problems.
+        byte[] data = this.serialize();
+        pkt.deserialize(this.serialize(), 0, data.length);
+        pkt.setParent(this.parent);
+        return pkt;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCP.java
new file mode 100644
index 0000000..f73d9c9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCP.java
@@ -0,0 +1,517 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class DHCP extends BasePacket {
+    /**
+     * ------------------------------------------
+     * |op (1)  | htype(1) | hlen(1) | hops(1)  |
+     * ------------------------------------------
+     * |        xid (4)                         |
+     * ------------------------------------------
+     * |  secs (2)          |   flags (2)       |
+     * ------------------------------------------
+     * |            ciaddr (4)                  |
+     * ------------------------------------------
+     * |            yiaddr (4)                  |
+     * ------------------------------------------
+     * |            siaddr (4)                  |
+     * ------------------------------------------
+     * |            giaddr (4)                  |
+     * ------------------------------------------
+     * |            chaddr (16)                  |
+     * ------------------------------------------
+     * |            sname (64)                  |
+     * ------------------------------------------
+     * |            file (128)                  |
+     * ------------------------------------------
+     * |            options (312)               |
+     * ------------------------------------------
+     * 
+     */
+    // Header + magic without options
+    public static int MIN_HEADER_LENGTH = 240;
+    public static byte OPCODE_REQUEST = 0x1;
+    public static byte OPCODE_REPLY = 0x2;
+
+    public static byte HWTYPE_ETHERNET = 0x1;
+    
+    public enum DHCPOptionCode {
+        OptionCode_SubnetMask           ((byte)1),
+        OptionCode_RequestedIP          ((byte)50),
+        OptionCode_LeaseTime            ((byte)51),
+        OptionCode_MessageType          ((byte)53),
+        OptionCode_DHCPServerIp         ((byte)54),
+        OptionCode_RequestedParameters  ((byte)55),
+        OptionCode_RenewalTime          ((byte)58),
+        OPtionCode_RebindingTime        ((byte)59),
+        OptionCode_ClientID             ((byte)61),
+        OptionCode_END                  ((byte)255);
+    
+        protected byte value;
+        
+        private DHCPOptionCode(byte value) {
+            this.value = value;
+        }
+        
+        public byte getValue() {
+            return value;
+        }
+    }
+    
+    protected byte opCode;
+    protected byte hardwareType;
+    protected byte hardwareAddressLength;
+    protected byte hops;
+    protected int transactionId;
+    protected short seconds;
+    protected short flags;
+    protected int clientIPAddress;
+    protected int yourIPAddress;
+    protected int serverIPAddress;
+    protected int gatewayIPAddress;
+    protected byte[] clientHardwareAddress;
+    protected String serverName;
+    protected String bootFileName;
+    protected List<DHCPOption> options = new ArrayList<DHCPOption>();
+
+    /**
+     * @return the opCode
+     */
+    public byte getOpCode() {
+        return opCode;
+    }
+
+    /**
+     * @param opCode the opCode to set
+     */
+    public DHCP setOpCode(byte opCode) {
+        this.opCode = opCode;
+        return this;
+    }
+
+    /**
+     * @return the hardwareType
+     */
+    public byte getHardwareType() {
+        return hardwareType;
+    }
+
+    /**
+     * @param hardwareType the hardwareType to set
+     */
+    public DHCP setHardwareType(byte hardwareType) {
+        this.hardwareType = hardwareType;
+        return this;
+    }
+
+    /**
+     * @return the hardwareAddressLength
+     */
+    public byte getHardwareAddressLength() {
+        return hardwareAddressLength;
+    }
+
+    /**
+     * @param hardwareAddressLength the hardwareAddressLength to set
+     */
+    public DHCP setHardwareAddressLength(byte hardwareAddressLength) {
+        this.hardwareAddressLength = hardwareAddressLength;
+        return this;
+    }
+
+    /**
+     * @return the hops
+     */
+    public byte getHops() {
+        return hops;
+    }
+
+    /**
+     * @param hops the hops to set
+     */
+    public DHCP setHops(byte hops) {
+        this.hops = hops;
+        return this;
+    }
+
+    /**
+     * @return the transactionId
+     */
+    public int getTransactionId() {
+        return transactionId;
+    }
+
+    /**
+     * @param transactionId the transactionId to set
+     */
+    public DHCP setTransactionId(int transactionId) {
+        this.transactionId = transactionId;
+        return this;
+    }
+
+    /**
+     * @return the seconds
+     */
+    public short getSeconds() {
+        return seconds;
+    }
+
+    /**
+     * @param seconds the seconds to set
+     */
+    public DHCP setSeconds(short seconds) {
+        this.seconds = seconds;
+        return this;
+    }
+
+    /**
+     * @return the flags
+     */
+    public short getFlags() {
+        return flags;
+    }
+
+    /**
+     * @param flags the flags to set
+     */
+    public DHCP setFlags(short flags) {
+        this.flags = flags;
+        return this;
+    }
+
+    /**
+     * @return the clientIPAddress
+     */
+    public int getClientIPAddress() {
+        return clientIPAddress;
+    }
+
+    /**
+     * @param clientIPAddress the clientIPAddress to set
+     */
+    public DHCP setClientIPAddress(int clientIPAddress) {
+        this.clientIPAddress = clientIPAddress;
+        return this;
+    }
+
+    /**
+     * @return the yourIPAddress
+     */
+    public int getYourIPAddress() {
+        return yourIPAddress;
+    }
+
+    /**
+     * @param yourIPAddress the yourIPAddress to set
+     */
+    public DHCP setYourIPAddress(int yourIPAddress) {
+        this.yourIPAddress = yourIPAddress;
+        return this;
+    }
+
+    /**
+     * @return the serverIPAddress
+     */
+    public int getServerIPAddress() {
+        return serverIPAddress;
+    }
+
+    /**
+     * @param serverIPAddress the serverIPAddress to set
+     */
+    public DHCP setServerIPAddress(int serverIPAddress) {
+        this.serverIPAddress = serverIPAddress;
+        return this;
+    }
+
+    /**
+     * @return the gatewayIPAddress
+     */
+    public int getGatewayIPAddress() {
+        return gatewayIPAddress;
+    }
+
+    /**
+     * @param gatewayIPAddress the gatewayIPAddress to set
+     */
+    public DHCP setGatewayIPAddress(int gatewayIPAddress) {
+        this.gatewayIPAddress = gatewayIPAddress;
+        return this;
+    }
+
+    /**
+     * @return the clientHardwareAddress
+     */
+    public byte[] getClientHardwareAddress() {
+        return clientHardwareAddress;
+    }
+
+    /**
+     * @param clientHardwareAddress the clientHardwareAddress to set
+     */
+    public DHCP setClientHardwareAddress(byte[] clientHardwareAddress) {
+        this.clientHardwareAddress = clientHardwareAddress;
+        return this;
+    }
+    
+    /**
+     * Gets a specific DHCP option parameter
+     * @param opetionCode The option code to get
+     * @return The value of the option if it exists, null otherwise
+     */
+    public DHCPOption getOption(DHCPOptionCode optionCode) {
+        for (DHCPOption opt : options) {
+            if (opt.code == optionCode.value)
+                return opt;
+        }
+        return null;
+    }
+
+    /**
+     * @return the options
+     */
+    public List<DHCPOption> getOptions() {
+        return options;
+    }
+
+    /**
+     * @param options the options to set
+     */
+    public DHCP setOptions(List<DHCPOption> options) {
+        this.options = options;
+        return this;
+    }
+
+    /**
+     * @return the packetType base on option 53
+     */
+    public DHCPPacketType getPacketType() {
+        ListIterator<DHCPOption> lit = options.listIterator();
+        while (lit.hasNext()) {
+            DHCPOption option = lit.next();
+            // only care option 53
+            if (option.getCode() == 53) {
+                return DHCPPacketType.getType(option.getData()[0]);
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * @return the serverName
+     */
+    public String getServerName() {
+        return serverName;
+    }
+
+    /**
+     * @param serverName the serverName to set
+     */
+    public DHCP setServerName(String serverName) {
+        this.serverName = serverName;
+        return this;
+    }
+
+    /**
+     * @return the bootFileName
+     */
+    public String getBootFileName() {
+        return bootFileName;
+    }
+
+    /**
+     * @param bootFileName the bootFileName to set
+     */
+    public DHCP setBootFileName(String bootFileName) {
+        this.bootFileName = bootFileName;
+        return this;
+    }
+
+    @Override
+    public byte[] serialize() {
+        // not guaranteed to retain length/exact format
+        resetChecksum();
+
+        // minimum size 240 including magic cookie, options generally padded to 300
+        int optionsLength = 0;
+        for (DHCPOption option : this.options) {
+            if (option.getCode() == 0 || option.getCode() == 255) {
+                optionsLength += 1;
+            } else {
+                optionsLength += 2 + (int)(0xff & option.getLength());
+            }
+        }
+        int optionsPadLength = 0;
+        if (optionsLength < 60)
+            optionsPadLength = 60 - optionsLength;
+
+        byte[] data = new byte[240+optionsLength+optionsPadLength];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put(this.opCode);
+        bb.put(this.hardwareType);
+        bb.put(this.hardwareAddressLength);
+        bb.put(this.hops);
+        bb.putInt(this.transactionId);
+        bb.putShort(this.seconds);
+        bb.putShort(this.flags);
+        bb.putInt(this.clientIPAddress);
+        bb.putInt(this.yourIPAddress);
+        bb.putInt(this.serverIPAddress);
+        bb.putInt(this.gatewayIPAddress);
+        bb.put(this.clientHardwareAddress);
+        if (this.clientHardwareAddress.length < 16) {
+            for (int i = 0; i < (16 - this.clientHardwareAddress.length); ++i) {
+                bb.put((byte) 0x0);
+            }
+        }
+        writeString(this.serverName, bb, 64);
+        writeString(this.bootFileName, bb, 128);
+        // magic cookie
+        bb.put((byte) 0x63);
+        bb.put((byte) 0x82);
+        bb.put((byte) 0x53);
+        bb.put((byte) 0x63);
+        for (DHCPOption option : this.options) {
+            int code = option.getCode() & 0xff;
+            bb.put((byte) code);
+            if ((code != 0) && (code != 255)) {
+                bb.put(option.getLength());
+                bb.put(option.getData());
+            }
+        }
+        // assume the rest is padded out with zeroes
+        return data;
+    }
+
+    protected void writeString(String string, ByteBuffer bb, int maxLength) {
+        if (string == null) {
+            for (int i = 0; i < maxLength; ++i) {
+                bb.put((byte) 0x0);
+            }
+        } else {
+            byte[] bytes = null;
+            try {
+                 bytes = string.getBytes("ascii");
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException("Failure encoding server name", e);
+            }
+            int writeLength = bytes.length;
+            if (writeLength > maxLength) {
+                writeLength = maxLength;
+            }
+            bb.put(bytes, 0, writeLength);
+            for (int i = writeLength; i < maxLength; ++i) {
+                bb.put((byte) 0x0);
+            }
+        }
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        if (bb.remaining() < MIN_HEADER_LENGTH) {
+            return this;
+        }
+        
+        this.opCode = bb.get();
+        this.hardwareType = bb.get();
+        this.hardwareAddressLength = bb.get();
+        this.hops = bb.get();
+        this.transactionId = bb.getInt();
+        this.seconds = bb.getShort();
+        this.flags = bb.getShort();
+        this.clientIPAddress = bb.getInt();
+        this.yourIPAddress = bb.getInt();
+        this.serverIPAddress = bb.getInt();
+        this.gatewayIPAddress = bb.getInt();
+        int hardwareAddressLength = 0xff & this.hardwareAddressLength;
+        this.clientHardwareAddress = new byte[hardwareAddressLength];
+
+        bb.get(this.clientHardwareAddress);
+        for (int i = hardwareAddressLength; i < 16; ++i)
+            bb.get();
+        this.serverName = readString(bb, 64);
+        this.bootFileName = readString(bb, 128);
+        // read the magic cookie
+        // magic cookie
+        bb.get();
+        bb.get();
+        bb.get();
+        bb.get();
+        // read options
+        while (bb.hasRemaining()) {
+            DHCPOption option = new DHCPOption();
+            int code = 0xff & bb.get(); // convert signed byte to int in range [0,255]
+            option.setCode((byte) code);
+            if (code == 0) {
+                // skip these
+                continue;
+            } else if (code != 255) {
+                if (bb.hasRemaining()) {
+                    int l = 0xff & bb.get(); // convert signed byte to int in range [0,255]
+                    option.setLength((byte) l);
+                    if (bb.remaining() >= l) {
+                        byte[] optionData = new byte[l];
+                        bb.get(optionData);
+                        option.setData(optionData);
+                    } else {
+                        // Skip the invalid option and set the END option
+                        code = 0xff;
+                        option.setCode((byte)code);
+                        option.setLength((byte) 0);
+                    }
+                } else {
+                    // Skip the invalid option and set the END option
+                    code = 0xff;
+                    option.setCode((byte)code);
+                    option.setLength((byte) 0);
+                }
+            }
+            this.options.add(option);
+            if (code == 255) {
+                // remaining bytes are supposed to be 0, but ignore them just in case
+                break;
+            }
+        }
+
+        return this;
+    }
+
+    protected String readString(ByteBuffer bb, int maxLength) {
+        byte[] bytes = new byte[maxLength];
+        bb.get(bytes);
+        String result = null;
+        try {
+            result = new String(bytes, "ascii").trim();
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("Failure decoding string", e);
+        }
+        return result;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPOption.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPOption.java
new file mode 100644
index 0000000..1fcc324
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPOption.java
@@ -0,0 +1,118 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.util.Arrays;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class DHCPOption {
+    protected byte code;
+    protected byte length;
+    protected byte[] data;
+
+    /**
+     * @return the code
+     */
+    public byte getCode() {
+        return code;
+    }
+
+    /**
+     * @param code the code to set
+     */
+    public DHCPOption setCode(byte code) {
+        this.code = code;
+        return this;
+    }
+
+    /**
+     * @return the length
+     */
+    public byte getLength() {
+        return length;
+    }
+
+    /**
+     * @param length the length to set
+     */
+    public DHCPOption setLength(byte length) {
+        this.length = length;
+        return this;
+    }
+
+    /**
+     * @return the data
+     */
+    public byte[] getData() {
+        return data;
+    }
+
+    /**
+     * @param data the data to set
+     */
+    public DHCPOption setData(byte[] data) {
+        this.data = data;
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + code;
+        result = prime * result + Arrays.hashCode(data);
+        result = prime * result + length;
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (!(obj instanceof DHCPOption))
+            return false;
+        DHCPOption other = (DHCPOption) obj;
+        if (code != other.code)
+            return false;
+        if (!Arrays.equals(data, other.data))
+            return false;
+        if (length != other.length)
+            return false;
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "DHCPOption [code=" + code + ", length=" + length + ", data="
+                + Arrays.toString(data) + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPPacketType.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPPacketType.java
new file mode 100644
index 0000000..3417a18
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPPacketType.java
@@ -0,0 +1,116 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+public enum DHCPPacketType {
+    // From RFC 1533
+    DHCPDISCOVER        (1),
+    DHCPOFFER           (2),
+    DHCPREQUEST         (3),
+    DHCPDECLINE         (4),
+    DHCPACK             (5),
+    DHCPNAK             (6),
+    DHCPRELEASE         (7),
+    
+    // From RFC2132
+    DHCPINFORM          (8),
+    
+    // From RFC3203
+    DHCPFORCERENEW      (9),
+    
+    // From RFC4388
+    DHCPLEASEQUERY      (10),
+    DHCPLEASEUNASSIGNED (11),
+    DHCPLEASEUNKNOWN    (12),
+    DHCPLEASEACTIVE     (13);
+    
+    protected int value;
+    
+    private DHCPPacketType(int value) {
+        this.value = value;
+    }
+    
+    public int getValue() {
+        return value;
+    }
+    
+    public String toString(){
+        switch (value) {
+            case 1:
+                return "DHCPDISCOVER";
+            case 2:
+                return "DHCPOFFER";
+            case 3:
+                return "DHCPREQUEST";
+            case 4:
+                return "DHCPDECLINE";
+            case 5:
+                return "DHCPACK";
+            case 6:
+                return "DHCPNAK";
+            case 7:
+                return "DHCPRELEASE";
+            case 8:
+                return "DHCPINFORM";
+            case 9:
+                return "DHCPFORCERENEW";
+            case 10:
+                return "DHCPLEASEQUERY";
+            case 11:
+                return "DHCPLEASEUNASSIGNED";
+            case 12:
+                return "DHCPLEASEUNKNOWN";
+            case 13:
+                return "DHCPLEASEACTIVE";
+        }
+        
+        return null;
+    }
+    public static DHCPPacketType getType(int value) {
+        switch (value) {
+            case 1:
+                return DHCPDISCOVER;
+            case 2:
+                return DHCPOFFER;
+            case 3:
+                return DHCPREQUEST;
+            case 4:
+                return DHCPDECLINE;
+            case 5:
+                return DHCPACK;
+            case 6:
+                return DHCPNAK;
+            case 7:
+                return DHCPRELEASE;
+            case 8:
+                return DHCPINFORM;
+            case 9:
+                return DHCPFORCERENEW;
+            case 10:
+                return DHCPLEASEQUERY;
+            case 11:
+                return DHCPLEASEUNASSIGNED;
+            case 12:
+                return DHCPLEASEUNKNOWN;
+            case 13:
+                return DHCPLEASEACTIVE;
+        }
+        
+        return null;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Data.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Data.java
new file mode 100644
index 0000000..47762da
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Data.java
@@ -0,0 +1,94 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.util.Arrays;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class Data extends BasePacket {
+    protected byte[] data;
+
+    /**
+     * 
+     */
+    public Data() {
+    }
+
+    /**
+     * @param data
+     */
+    public Data(byte[] data) {
+        this.data = data;
+    }
+
+    /**
+     * @return the data
+     */
+    public byte[] getData() {
+        return data;
+    }
+
+    /**
+     * @param data the data to set
+     */
+    public Data setData(byte[] data) {
+        this.data = data;
+        return this;
+    }
+
+    public byte[] serialize() {
+        return this.data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        this.data = Arrays.copyOfRange(data, offset, data.length);
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 1571;
+        int result = super.hashCode();
+        result = prime * result + Arrays.hashCode(data);
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof Data))
+            return false;
+        Data other = (Data) obj;
+        if (!Arrays.equals(data, other.data))
+            return false;
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Ethernet.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Ethernet.java
new file mode 100644
index 0000000..6bd627b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Ethernet.java
@@ -0,0 +1,468 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.util.MACAddress;
+import org.openflow.util.HexString;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class Ethernet extends BasePacket {
+    private static String HEXES = "0123456789ABCDEF";
+    public static final short TYPE_ARP = 0x0806;
+    public static final short TYPE_RARP = (short) 0x8035;
+    public static final short TYPE_IPv4 = 0x0800;
+    public static final short TYPE_LLDP = (short) 0x88cc;
+    public static final short TYPE_BSN = (short) 0x8942;
+    public static final short VLAN_UNTAGGED = (short)0xffff;
+    public static final short DATALAYER_ADDRESS_LENGTH = 6; // bytes
+    public static Map<Short, Class<? extends IPacket>> etherTypeClassMap;
+
+    static {
+        etherTypeClassMap = new HashMap<Short, Class<? extends IPacket>>();
+        etherTypeClassMap.put(TYPE_ARP, ARP.class);
+        etherTypeClassMap.put(TYPE_RARP, ARP.class);
+        etherTypeClassMap.put(TYPE_IPv4, IPv4.class);
+        etherTypeClassMap.put(TYPE_LLDP, LLDP.class);
+        etherTypeClassMap.put(TYPE_BSN, BSN.class);
+    }
+
+    protected MACAddress destinationMACAddress;
+    protected MACAddress sourceMACAddress;
+    protected byte priorityCode;
+    protected short vlanID;
+    protected short etherType;
+    protected boolean pad = false;
+
+    /**
+     * By default, set Ethernet to untagged
+     */
+    public Ethernet() {
+        super();
+        this.vlanID = VLAN_UNTAGGED;
+    }
+    
+    /**
+     * @return the destination MAC as a byte array
+     */
+    public byte[] getDestinationMACAddress() {
+        return destinationMACAddress.toBytes();
+    }
+    
+    /**
+     * @return the destination MAC
+     */
+    public MACAddress getDestinationMAC() {
+        return destinationMACAddress;
+    }
+
+    /**
+     * @param destinationMACAddress the destination MAC to set
+     */
+    public Ethernet setDestinationMACAddress(byte[] destinationMACAddress) {
+        this.destinationMACAddress = MACAddress.valueOf(destinationMACAddress);
+        return this;
+    }
+
+    /**
+     * @param destinationMACAddress the destination MAC to set
+     */
+    public Ethernet setDestinationMACAddress(String destinationMACAddress) {
+        this.destinationMACAddress = MACAddress.valueOf(destinationMACAddress);
+        return this;
+    }
+
+    /**
+     * @return the source MACAddress as a byte array
+     */
+    public byte[] getSourceMACAddress() {
+        return sourceMACAddress.toBytes();
+    }
+    
+    /**
+     * @return the source MACAddress
+     */
+    public MACAddress getSourceMAC() {
+        return sourceMACAddress;
+    }
+
+    /**
+     * @param sourceMACAddress the source MAC to set
+     */
+    public Ethernet setSourceMACAddress(byte[] sourceMACAddress) {
+        this.sourceMACAddress = MACAddress.valueOf(sourceMACAddress);
+        return this;
+    }
+
+    /**
+     * @param sourceMACAddress the source MAC to set
+     */
+    public Ethernet setSourceMACAddress(String sourceMACAddress) {
+        this.sourceMACAddress = MACAddress.valueOf(sourceMACAddress);
+        return this;
+    }
+
+    /**
+     * @return the priorityCode
+     */
+    public byte getPriorityCode() {
+        return priorityCode;
+    }
+
+    /**
+     * @param priorityCode the priorityCode to set
+     */
+    public Ethernet setPriorityCode(byte priorityCode) {
+        this.priorityCode = priorityCode;
+        return this;
+    }
+
+    /**
+     * @return the vlanID
+     */
+    public short getVlanID() {
+        return vlanID;
+    }
+
+    /**
+     * @param vlanID the vlanID to set
+     */
+    public Ethernet setVlanID(short vlanID) {
+        this.vlanID = vlanID;
+        return this;
+    }
+
+    /**
+     * @return the etherType
+     */
+    public short getEtherType() {
+        return etherType;
+    }
+
+    /**
+     * @param etherType the etherType to set
+     */
+    public Ethernet setEtherType(short etherType) {
+        this.etherType = etherType;
+        return this;
+    }
+    
+    /**
+     * @return True if the Ethernet frame is broadcast, false otherwise
+     */
+    public boolean isBroadcast() {
+        assert(destinationMACAddress.length() == 6);
+        return destinationMACAddress.isBroadcast();
+    }
+    
+    /**
+     * @return True is the Ethernet frame is multicast, False otherwise
+     */
+    public boolean isMulticast() {
+        return destinationMACAddress.isMulticast();
+    }
+    /**
+     * Pad this packet to 60 bytes minimum, filling with zeros?
+     * @return the pad
+     */
+    public boolean isPad() {
+        return pad;
+    }
+
+    /**
+     * Pad this packet to 60 bytes minimum, filling with zeros?
+     * @param pad the pad to set
+     */
+    public Ethernet setPad(boolean pad) {
+        this.pad = pad;
+        return this;
+    }
+
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (payload != null) {
+            payload.setParent(this);
+            payloadData = payload.serialize();
+        }
+        int length = 14 + ((vlanID == VLAN_UNTAGGED) ? 0 : 4) +
+                          ((payloadData == null) ? 0 : payloadData.length);
+        if (pad && length < 60) {
+            length = 60;
+        }
+        byte[] data = new byte[length];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put(destinationMACAddress.toBytes());
+        bb.put(sourceMACAddress.toBytes());
+        if (vlanID != VLAN_UNTAGGED) {
+            bb.putShort((short) 0x8100);
+            bb.putShort((short) ((priorityCode << 13) | (vlanID & 0x0fff)));
+        }
+        bb.putShort(etherType);
+        if (payloadData != null)
+            bb.put(payloadData);
+        if (pad) {
+            Arrays.fill(data, bb.position(), data.length, (byte)0x0);
+        }
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        if (length <= 0)
+            return null;
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        if (this.destinationMACAddress == null)
+            this.destinationMACAddress = MACAddress.valueOf(new byte[6]);
+        byte[] dstAddr = new byte[MACAddress.MAC_ADDRESS_LENGTH];
+        bb.get(dstAddr);
+        this.destinationMACAddress = MACAddress.valueOf(dstAddr);
+
+        if (this.sourceMACAddress == null)
+            this.sourceMACAddress = MACAddress.valueOf(new byte[6]);
+        byte[] srcAddr = new byte[MACAddress.MAC_ADDRESS_LENGTH];
+        bb.get(srcAddr);
+        this.sourceMACAddress = MACAddress.valueOf(srcAddr);
+
+        short etherType = bb.getShort();
+        if (etherType == (short) 0x8100) {
+            short tci = bb.getShort();
+            this.priorityCode = (byte) ((tci >> 13) & 0x07);
+            this.vlanID = (short) (tci & 0x0fff);
+            etherType = bb.getShort();
+        } else {
+            this.vlanID = VLAN_UNTAGGED;
+        }
+        this.etherType = etherType;
+        
+        IPacket payload;
+        if (Ethernet.etherTypeClassMap.containsKey(this.etherType)) {
+            Class<? extends IPacket> clazz = Ethernet.etherTypeClassMap.get(this.etherType);
+            try {
+                payload = clazz.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException("Error parsing payload for Ethernet packet", e);
+            }
+        } else {
+            payload = new Data();
+        }
+        this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+        this.payload.setParent(this);
+        return this;
+    }
+
+    /**
+     * Checks to see if a string is a valid MAC address.
+     * @param macAddress
+     * @return True if macAddress is a valid MAC, False otherwise
+     */
+    public static boolean isMACAddress(String macAddress) {
+        String[] macBytes = macAddress.split(":");
+        if (macBytes.length != 6)
+            return false;
+        for (int i = 0; i < 6; ++i) {
+            if (HEXES.indexOf(macBytes[i].toUpperCase().charAt(0)) == -1 || 
+                HEXES.indexOf(macBytes[i].toUpperCase().charAt(1)) == -1) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Accepts a MAC address of the form 00:aa:11:bb:22:cc, case does not
+     * matter, and returns a corresponding byte[].
+     * @param macAddress The MAC address to convert into a bye array
+     * @return The macAddress as a byte array 
+     */
+    public static byte[] toMACAddress(String macAddress) {
+        return MACAddress.valueOf(macAddress).toBytes();
+    }
+
+
+    /**
+     * Accepts a MAC address and returns the corresponding long, where the
+     * MAC bytes are set on the lower order bytes of the long.
+     * @param macAddress
+     * @return a long containing the mac address bytes
+     */
+    public static long toLong(byte[] macAddress) {
+        return MACAddress.valueOf(macAddress).toLong();
+    }
+
+    /**
+     * Convert a long MAC address to a byte array
+     * @param macAddress
+     * @return the bytes of the mac address
+     */
+    public static byte[] toByteArray(long macAddress) {
+        return MACAddress.valueOf(macAddress).toBytes();
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 7867;
+        int result = super.hashCode();
+        result = prime * result + destinationMACAddress.hashCode();
+        result = prime * result + etherType;
+        result = prime * result + vlanID;
+        result = prime * result + priorityCode;
+        result = prime * result + (pad ? 1231 : 1237);
+        result = prime * result + sourceMACAddress.hashCode();
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof Ethernet))
+            return false;
+        Ethernet other = (Ethernet) obj;
+        if (!destinationMACAddress.equals(other.destinationMACAddress))
+            return false;
+        if (priorityCode != other.priorityCode)
+            return false;
+        if (vlanID != other.vlanID)
+            return false;
+        if (etherType != other.etherType)
+            return false;
+        if (pad != other.pad)
+            return false;
+        if (!sourceMACAddress.equals(other.sourceMACAddress))
+            return false;
+        return true;
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString(java.lang.Object)
+     */
+    @Override
+    public String toString() {
+
+        StringBuffer sb = new StringBuffer("\n");
+
+        IPacket pkt = (IPacket) this.getPayload();
+
+        if (pkt instanceof ARP)
+            sb.append("arp");
+        else if (pkt instanceof LLDP)
+            sb.append("lldp");
+        else if (pkt instanceof ICMP)
+            sb.append("icmp");
+        else if (pkt instanceof IPv4)
+            sb.append("ip");
+        else if (pkt instanceof DHCP)
+            sb.append("dhcp");
+        else  sb.append(this.getEtherType());
+
+        sb.append("\ndl_vlan: ");
+        if (this.getVlanID() == Ethernet.VLAN_UNTAGGED)
+            sb.append("untagged");
+        else
+            sb.append(this.getVlanID());
+        sb.append("\ndl_vlan_pcp: ");
+        sb.append(this.getPriorityCode());
+        sb.append("\ndl_src: ");
+        sb.append(HexString.toHexString(this.getSourceMACAddress()));
+        sb.append("\ndl_dst: ");
+        sb.append(HexString.toHexString(this.getDestinationMACAddress()));
+
+
+        if (pkt instanceof ARP) {
+            ARP p = (ARP) pkt;
+            sb.append("\nnw_src: ");
+            sb.append(IPv4.fromIPv4Address(IPv4.toIPv4Address(p.getSenderProtocolAddress())));
+            sb.append("\nnw_dst: ");
+            sb.append(IPv4.fromIPv4Address(IPv4.toIPv4Address(p.getTargetProtocolAddress())));
+        }
+        else if (pkt instanceof LLDP) {
+            sb.append("lldp packet");
+        }
+        else if (pkt instanceof ICMP) {
+            ICMP icmp = (ICMP) pkt;
+            sb.append("\nicmp_type: ");
+            sb.append(icmp.getIcmpType());
+            sb.append("\nicmp_code: ");
+            sb.append(icmp.getIcmpCode());
+        }
+        else if (pkt instanceof IPv4) {
+            IPv4 p = (IPv4) pkt;
+            sb.append("\nnw_src: ");
+            sb.append(IPv4.fromIPv4Address(p.getSourceAddress()));
+            sb.append("\nnw_dst: ");
+            sb.append(IPv4.fromIPv4Address(p.getDestinationAddress()));
+            sb.append("\nnw_tos: ");
+            sb.append(p.getDiffServ());
+            sb.append("\nnw_proto: ");
+            sb.append(p.getProtocol());
+
+            if (pkt instanceof TCP) {
+                sb.append("\ntp_src: ");
+                sb.append(((TCP) pkt).getSourcePort());
+                sb.append("\ntp_dst: ");
+                sb.append(((TCP) pkt).getDestinationPort());
+
+            } else if (pkt instanceof UDP) {
+                sb.append("\ntp_src: ");
+                sb.append(((UDP) pkt).getSourcePort());
+                sb.append("\ntp_dst: ");
+                sb.append(((UDP) pkt).getDestinationPort());
+            }
+
+            if (pkt instanceof ICMP) {
+                ICMP icmp = (ICMP) pkt;
+                sb.append("\nicmp_type: ");
+                sb.append(icmp.getIcmpType());
+                sb.append("\nicmp_code: ");
+                sb.append(icmp.getIcmpCode());
+            }
+
+        }
+        else if (pkt instanceof DHCP) {
+            sb.append("\ndhcp packet");
+        }
+        else if (pkt instanceof Data) {
+            sb.append("\ndata packet");
+        }
+        else if (pkt instanceof LLC) {
+            sb.append("\nllc packet");
+        }
+        else if (pkt instanceof BPDU) {
+            sb.append("\nbpdu packet");
+        }
+        else sb.append("\nunknwon packet");
+
+        return sb.toString();
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ICMP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ICMP.java
new file mode 100644
index 0000000..5431277
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ICMP.java
@@ -0,0 +1,170 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implements ICMP packet format
+ * @author shudong.zhou@bigswitch.com
+ */
+public class ICMP extends BasePacket {
+    protected byte icmpType;
+    protected byte icmpCode;
+    protected short checksum;
+
+    /**
+     * @return the icmpType
+     */
+    public byte getIcmpType() {
+        return icmpType;
+    }
+
+    /**
+     * @param icmpType to set
+     */
+    public ICMP setIcmpType(byte icmpType) {
+        this.icmpType = icmpType;
+        return this;
+    }
+
+    /**
+     * @return the icmp code
+     */
+    public byte getIcmpCode() {
+        return icmpCode;
+    }
+
+    /**
+     * @param icmpCode code to set
+     */
+    public ICMP setIcmpCode(byte icmpCode) {
+        this.icmpCode = icmpCode;
+        return this;
+    }
+
+    /**
+     * @return the checksum
+     */
+    public short getChecksum() {
+        return checksum;
+    }
+
+    /**
+     * @param checksum the checksum to set
+     */
+    public ICMP setChecksum(short checksum) {
+        this.checksum = checksum;
+        return this;
+    }
+
+    /**
+     * Serializes the packet. Will compute and set the following fields if they
+     * are set to specific values at the time serialize is called:
+     *      -checksum : 0
+     *      -length : 0
+     */
+    public byte[] serialize() {
+        int length = 4;
+        byte[] payloadData = null;
+        if (payload != null) {
+            payload.setParent(this);
+            payloadData = payload.serialize();
+            length += payloadData.length;
+        }
+
+        byte[] data = new byte[length];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put(this.icmpType);
+        bb.put(this.icmpCode);
+        bb.putShort(this.checksum);
+        if (payloadData != null)
+            bb.put(payloadData);
+
+        if (this.parent != null && this.parent instanceof IPv4)
+            ((IPv4)this.parent).setProtocol(IPv4.PROTOCOL_ICMP);
+
+        // compute checksum if needed
+        if (this.checksum == 0) {
+            bb.rewind();
+            int accumulation = 0;
+
+            for (int i = 0; i < length / 2; ++i) {
+                accumulation += 0xffff & bb.getShort();
+            }
+            // pad to an even number of shorts
+            if (length % 2 > 0) {
+                accumulation += (bb.get() & 0xff) << 8;
+            }
+
+            accumulation = ((accumulation >> 16) & 0xffff)
+                    + (accumulation & 0xffff);
+            this.checksum = (short) (~accumulation & 0xffff);
+            bb.putShort(2, this.checksum);
+        }
+        return data;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 5807;
+        int result = super.hashCode();
+        result = prime * result + icmpType;
+        result = prime * result + icmpCode;
+        result = prime * result + checksum;
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof ICMP))
+            return false;
+        ICMP other = (ICMP) obj;
+        if (icmpType != other.icmpType)
+            return false;
+        if (icmpCode != other.icmpCode)
+            return false;
+        if (checksum != other.checksum)
+            return false;
+        return true;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.icmpType = bb.get();
+        this.icmpCode = bb.get();
+        this.checksum = bb.getShort();
+        
+        this.payload = new Data();
+        this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+        this.payload.setParent(this);
+        return this;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPacket.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPacket.java
new file mode 100644
index 0000000..02376cd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPacket.java
@@ -0,0 +1,77 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+/**
+*
+* @author David Erickson (daviderickson@cs.stanford.edu)
+*/
+public interface IPacket {
+    /**
+     * 
+     * @return
+     */
+    public IPacket getPayload();
+
+    /**
+     * 
+     * @param packet
+     * @return
+     */
+    public IPacket setPayload(IPacket packet);
+
+    /**
+     * 
+     * @return
+     */
+    public IPacket getParent();
+
+    /**
+     * 
+     * @param packet
+     * @return
+     */
+    public IPacket setParent(IPacket packet);
+
+    /**
+     * Reset any checksums as needed, and call resetChecksum on all parents
+     */
+    public void resetChecksum();
+    
+    /**
+     * Sets all payloads parent packet if applicable, then serializes this 
+     * packet and all payloads
+     * @return a byte[] containing this packet and payloads
+     */
+    public byte[] serialize();
+
+    /**
+     * Deserializes this packet layer and all possible payloads
+     * @param data
+     * @param offset offset to start deserializing from
+     * @param length length of the data to deserialize
+     * @return the deserialized data
+     */
+    public IPacket deserialize(byte[] data, int offset, int length);
+    
+    /** Clone this packet and its payload packet but not its parent. 
+     * 
+     * @return
+     */
+    public Object clone();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPv4.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPv4.java
new file mode 100644
index 0000000..01f886d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPv4.java
@@ -0,0 +1,558 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class IPv4 extends BasePacket {
+    public static final byte PROTOCOL_ICMP = 0x1;
+    public static final byte PROTOCOL_TCP = 0x6;
+    public static final byte PROTOCOL_UDP = 0x11;
+    public static Map<Byte, Class<? extends IPacket>> protocolClassMap;
+
+    static {
+        protocolClassMap = new HashMap<Byte, Class<? extends IPacket>>();
+        protocolClassMap.put(PROTOCOL_ICMP, ICMP.class);
+        protocolClassMap.put(PROTOCOL_TCP, TCP.class);
+        protocolClassMap.put(PROTOCOL_UDP, UDP.class);
+    }
+
+    protected byte version;
+    protected byte headerLength;
+    protected byte diffServ;
+    protected short totalLength;
+    protected short identification;
+    protected byte flags;
+    protected short fragmentOffset;
+    protected byte ttl;
+    protected byte protocol;
+    protected short checksum;
+    protected int sourceAddress;
+    protected int destinationAddress;
+    protected byte[] options;
+
+    protected boolean isTruncated;
+
+    /**
+     * Default constructor that sets the version to 4.
+     */
+    public IPv4() {
+        super();
+        this.version = 4;
+        isTruncated = false;
+    }
+
+    /**
+     * @return the version
+     */
+    public byte getVersion() {
+        return version;
+    }
+
+    /**
+     * @param version the version to set
+     */
+    public IPv4 setVersion(byte version) {
+        this.version = version;
+        return this;
+    }
+
+    /**
+     * @return the headerLength
+     */
+    public byte getHeaderLength() {
+        return headerLength;
+    }
+
+    /**
+     * @return the diffServ
+     */
+    public byte getDiffServ() {
+        return diffServ;
+    }
+
+    /**
+     * @param diffServ the diffServ to set
+     */
+    public IPv4 setDiffServ(byte diffServ) {
+        this.diffServ = diffServ;
+        return this;
+    }
+
+    /**
+     * @return the totalLength
+     */
+    public short getTotalLength() {
+        return totalLength;
+    }
+
+    /**
+     * @return the identification
+     */
+    public short getIdentification() {
+        return identification;
+    }
+
+    public boolean isTruncated() {
+        return isTruncated;
+    }
+
+    public void setTruncated(boolean isTruncated) {
+        this.isTruncated = isTruncated;
+    }
+
+    /**
+     * @param identification the identification to set
+     */
+    public IPv4 setIdentification(short identification) {
+        this.identification = identification;
+        return this;
+    }
+
+    /**
+     * @return the flags
+     */
+    public byte getFlags() {
+        return flags;
+    }
+
+    /**
+     * @param flags the flags to set
+     */
+    public IPv4 setFlags(byte flags) {
+        this.flags = flags;
+        return this;
+    }
+
+    /**
+     * @return the fragmentOffset
+     */
+    public short getFragmentOffset() {
+        return fragmentOffset;
+    }
+
+    /**
+     * @param fragmentOffset the fragmentOffset to set
+     */
+    public IPv4 setFragmentOffset(short fragmentOffset) {
+        this.fragmentOffset = fragmentOffset;
+        return this;
+    }
+
+    /**
+     * @return the ttl
+     */
+    public byte getTtl() {
+        return ttl;
+    }
+
+    /**
+     * @param ttl the ttl to set
+     */
+    public IPv4 setTtl(byte ttl) {
+        this.ttl = ttl;
+        return this;
+    }
+
+    /**
+     * @return the protocol
+     */
+    public byte getProtocol() {
+        return protocol;
+    }
+
+    /**
+     * @param protocol the protocol to set
+     */
+    public IPv4 setProtocol(byte protocol) {
+        this.protocol = protocol;
+        return this;
+    }
+
+    /**
+     * @return the checksum
+     */
+    public short getChecksum() {
+        return checksum;
+    }
+
+    /**
+     * @param checksum the checksum to set
+     */
+    public IPv4 setChecksum(short checksum) {
+        this.checksum = checksum;
+        return this;
+    }
+    @Override
+    public void resetChecksum() {
+        this.checksum = 0;
+        super.resetChecksum();
+    }
+
+    /**
+     * @return the sourceAddress
+     */
+    public int getSourceAddress() {
+        return sourceAddress;
+    }
+
+    /**
+     * @param sourceAddress the sourceAddress to set
+     */
+    public IPv4 setSourceAddress(int sourceAddress) {
+        this.sourceAddress = sourceAddress;
+        return this;
+    }
+
+    /**
+     * @param sourceAddress the sourceAddress to set
+     */
+    public IPv4 setSourceAddress(String sourceAddress) {
+        this.sourceAddress = IPv4.toIPv4Address(sourceAddress);
+        return this;
+    }
+
+    /**
+     * @return the destinationAddress
+     */
+    public int getDestinationAddress() {
+        return destinationAddress;
+    }
+
+    /**
+     * @param destinationAddress the destinationAddress to set
+     */
+    public IPv4 setDestinationAddress(int destinationAddress) {
+        this.destinationAddress = destinationAddress;
+        return this;
+    }
+
+    /**
+     * @param destinationAddress the destinationAddress to set
+     */
+    public IPv4 setDestinationAddress(String destinationAddress) {
+        this.destinationAddress = IPv4.toIPv4Address(destinationAddress);
+        return this;
+    }
+
+    /**
+     * @return the options
+     */
+    public byte[] getOptions() {
+        return options;
+    }
+
+    /**
+     * @param options the options to set
+     */
+    public IPv4 setOptions(byte[] options) {
+        if (options != null && (options.length % 4) > 0)
+            throw new IllegalArgumentException(
+                    "Options length must be a multiple of 4");
+        this.options = options;
+        return this;
+    }
+
+    /**
+     * Serializes the packet. Will compute and set the following fields if they
+     * are set to specific values at the time serialize is called:
+     *      -checksum : 0
+     *      -headerLength : 0
+     *      -totalLength : 0
+     */
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (payload != null) {
+            payload.setParent(this);
+            payloadData = payload.serialize();
+        }
+
+        int optionsLength = 0;
+        if (this.options != null)
+            optionsLength = this.options.length / 4;
+        this.headerLength = (byte) (5 + optionsLength);
+
+        this.totalLength = (short) (this.headerLength * 4 + ((payloadData == null) ? 0
+                : payloadData.length));
+
+        byte[] data = new byte[this.totalLength];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.put((byte) (((this.version & 0xf) << 4) | (this.headerLength & 0xf)));
+        bb.put(this.diffServ);
+        bb.putShort(this.totalLength);
+        bb.putShort(this.identification);
+        bb.putShort((short) (((this.flags & 0x7) << 13) | (this.fragmentOffset & 0x1fff)));
+        bb.put(this.ttl);
+        bb.put(this.protocol);
+        bb.putShort(this.checksum);
+        bb.putInt(this.sourceAddress);
+        bb.putInt(this.destinationAddress);
+        if (this.options != null)
+            bb.put(this.options);
+        if (payloadData != null)
+            bb.put(payloadData);
+
+        // compute checksum if needed
+        if (this.checksum == 0) {
+            bb.rewind();
+            int accumulation = 0;
+            for (int i = 0; i < this.headerLength * 2; ++i) {
+                accumulation += 0xffff & bb.getShort();
+            }
+            accumulation = ((accumulation >> 16) & 0xffff)
+                    + (accumulation & 0xffff);
+            this.checksum = (short) (~accumulation & 0xffff);
+            bb.putShort(10, this.checksum);
+        }
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        short sscratch;
+
+        this.version = bb.get();
+        this.headerLength = (byte) (this.version & 0xf);
+        this.version = (byte) ((this.version >> 4) & 0xf);
+        this.diffServ = bb.get();
+        this.totalLength = bb.getShort();
+        this.identification = bb.getShort();
+        sscratch = bb.getShort();
+        this.flags = (byte) ((sscratch >> 13) & 0x7);
+        this.fragmentOffset = (short) (sscratch & 0x1fff);
+        this.ttl = bb.get();
+        this.protocol = bb.get();
+        this.checksum = bb.getShort();
+        this.sourceAddress = bb.getInt();
+        this.destinationAddress = bb.getInt();
+
+        if (this.headerLength > 5) {
+            int optionsLength = (this.headerLength - 5) * 4;
+            this.options = new byte[optionsLength];
+            bb.get(this.options);
+        }
+
+        IPacket payload;
+        if (IPv4.protocolClassMap.containsKey(this.protocol)) {
+            Class<? extends IPacket> clazz = IPv4.protocolClassMap.get(this.protocol);
+            try {
+                payload = clazz.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException("Error parsing payload for IPv4 packet", e);
+            }
+        } else {
+            payload = new Data();
+        }
+        this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+        this.payload.setParent(this);
+
+        if (this.totalLength != length)
+            this.isTruncated = true;
+        else
+            this.isTruncated = false;
+
+        return this;
+    }
+
+    /**
+     * Accepts an IPv4 address of the form xxx.xxx.xxx.xxx, ie 192.168.0.1 and
+     * returns the corresponding 32 bit integer.
+     * @param ipAddress
+     * @return
+     */
+    public static int toIPv4Address(String ipAddress) {
+        if (ipAddress == null)
+            throw new IllegalArgumentException("Specified IPv4 address must" +
+                "contain 4 sets of numerical digits separated by periods");
+        String[] octets = ipAddress.split("\\.");
+        if (octets.length != 4) 
+            throw new IllegalArgumentException("Specified IPv4 address must" +
+                "contain 4 sets of numerical digits separated by periods");
+
+        int result = 0;
+        for (int i = 0; i < 4; ++i) {
+            result |= Integer.valueOf(octets[i]) << ((3-i)*8);
+        }
+        return result;
+    }
+
+    /**
+     * Accepts an IPv4 address in a byte array and returns the corresponding
+     * 32-bit integer value.
+     * @param ipAddress
+     * @return
+     */
+    public static int toIPv4Address(byte[] ipAddress) {
+        int ip = 0;
+        for (int i = 0; i < 4; i++) {
+          int t = (ipAddress[i] & 0xff) << ((3-i)*8);
+          ip |= t;
+        }
+        return ip;
+    }
+
+    /**
+     * Accepts an IPv4 address and returns of string of the form xxx.xxx.xxx.xxx
+     * ie 192.168.0.1
+     * 
+     * @param ipAddress
+     * @return
+     */
+    public static String fromIPv4Address(int ipAddress) {
+        StringBuffer sb = new StringBuffer();
+        int result = 0;
+        for (int i = 0; i < 4; ++i) {
+            result = (ipAddress >> ((3-i)*8)) & 0xff;
+            sb.append(Integer.valueOf(result).toString());
+            if (i != 3)
+                sb.append(".");
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Accepts a collection of IPv4 addresses as integers and returns a single
+     * String useful in toString method's containing collections of IP
+     * addresses.
+     * 
+     * @param ipAddresses collection
+     * @return
+     */
+    public static String fromIPv4AddressCollection(Collection<Integer> ipAddresses) {
+        if (ipAddresses == null)
+            return "null";
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        for (Integer ip : ipAddresses) {
+            sb.append(fromIPv4Address(ip));
+            sb.append(",");
+        }
+        sb.replace(sb.length()-1, sb.length(), "]");
+        return sb.toString();
+    }
+
+    /**
+     * Accepts an IPv4 address of the form xxx.xxx.xxx.xxx, ie 192.168.0.1 and
+     * returns the corresponding byte array.
+     * @param ipAddress The IP address in the form xx.xxx.xxx.xxx.
+     * @return The IP address separated into bytes
+     */
+    public static byte[] toIPv4AddressBytes(String ipAddress) {
+        String[] octets = ipAddress.split("\\.");
+        if (octets.length != 4) 
+            throw new IllegalArgumentException("Specified IPv4 address must" +
+                "contain 4 sets of numerical digits separated by periods");
+
+        byte[] result = new byte[4];
+        for (int i = 0; i < 4; ++i) {
+            result[i] = Integer.valueOf(octets[i]).byteValue();
+        }
+        return result;
+    }
+    
+    /**
+     * Accepts an IPv4 address in the form of an integer and
+     * returns the corresponding byte array.
+     * @param ipAddress The IP address as an integer.
+     * @return The IP address separated into bytes.
+     */
+    public static byte[] toIPv4AddressBytes(int ipAddress) {
+    	return new byte[] {
+                (byte)(ipAddress >>> 24),
+                (byte)(ipAddress >>> 16),
+                (byte)(ipAddress >>> 8),
+                (byte)ipAddress};
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 2521;
+        int result = super.hashCode();
+        result = prime * result + checksum;
+        result = prime * result + destinationAddress;
+        result = prime * result + diffServ;
+        result = prime * result + flags;
+        result = prime * result + fragmentOffset;
+        result = prime * result + headerLength;
+        result = prime * result + identification;
+        result = prime * result + Arrays.hashCode(options);
+        result = prime * result + protocol;
+        result = prime * result + sourceAddress;
+        result = prime * result + totalLength;
+        result = prime * result + ttl;
+        result = prime * result + version;
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof IPv4))
+            return false;
+        IPv4 other = (IPv4) obj;
+        if (checksum != other.checksum)
+            return false;
+        if (destinationAddress != other.destinationAddress)
+            return false;
+        if (diffServ != other.diffServ)
+            return false;
+        if (flags != other.flags)
+            return false;
+        if (fragmentOffset != other.fragmentOffset)
+            return false;
+        if (headerLength != other.headerLength)
+            return false;
+        if (identification != other.identification)
+            return false;
+        if (!Arrays.equals(options, other.options))
+            return false;
+        if (protocol != other.protocol)
+            return false;
+        if (sourceAddress != other.sourceAddress)
+            return false;
+        if (totalLength != other.totalLength)
+            return false;
+        if (ttl != other.ttl)
+            return false;
+        if (version != other.version)
+            return false;
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLC.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLC.java
new file mode 100644
index 0000000..dc7d6d8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLC.java
@@ -0,0 +1,75 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This class represents an Link Local Control
+ * header that is used in Ethernet 802.3.
+ * @author alexreimers
+ *
+ */
+public class LLC extends BasePacket {
+    private byte dsap = 0;
+    private byte ssap = 0;
+    private byte ctrl = 0;
+    
+    public byte getDsap() {
+        return dsap;
+    }
+
+    public void setDsap(byte dsap) {
+        this.dsap = dsap;
+    }
+
+    public byte getSsap() {
+        return ssap;
+    }
+
+    public void setSsap(byte ssap) {
+        this.ssap = ssap;
+    }
+
+    public byte getCtrl() {
+        return ctrl;
+    }
+
+    public void setCtrl(byte ctrl) {
+        this.ctrl = ctrl;
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] data = new byte[3];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put(dsap);
+        bb.put(ssap);
+        bb.put(ctrl);
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        dsap = bb.get();
+        ssap = bb.get();
+        ctrl = bb.get();
+        return this;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDP.java
new file mode 100644
index 0000000..8c2c457
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDP.java
@@ -0,0 +1,204 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class LLDP extends BasePacket {
+    protected LLDPTLV chassisId;
+    protected LLDPTLV portId;
+    protected LLDPTLV ttl;
+    protected List<LLDPTLV> optionalTLVList;
+    protected short ethType;
+
+    public LLDP() {
+        this.optionalTLVList = new ArrayList<LLDPTLV>();
+        this.ethType = Ethernet.TYPE_LLDP;
+    }
+
+    /**
+     * @return the chassisId
+     */
+    public LLDPTLV getChassisId() {
+        return chassisId;
+    }
+
+    /**
+     * @param chassisId the chassisId to set
+     */
+    public LLDP setChassisId(LLDPTLV chassisId) {
+        this.chassisId = chassisId;
+        return this;
+    }
+
+    /**
+     * @return the portId
+     */
+    public LLDPTLV getPortId() {
+        return portId;
+    }
+
+    /**
+     * @param portId the portId to set
+     */
+    public LLDP setPortId(LLDPTLV portId) {
+        this.portId = portId;
+        return this;
+    }
+
+    /**
+     * @return the ttl
+     */
+    public LLDPTLV getTtl() {
+        return ttl;
+    }
+
+    /**
+     * @param ttl the ttl to set
+     */
+    public LLDP setTtl(LLDPTLV ttl) {
+        this.ttl = ttl;
+        return this;
+    }
+
+    /**
+     * @return the optionalTLVList
+     */
+    public List<LLDPTLV> getOptionalTLVList() {
+        return optionalTLVList;
+    }
+
+    /**
+     * @param optionalTLVList the optionalTLVList to set
+     */
+    public LLDP setOptionalTLVList(List<LLDPTLV> optionalTLVList) {
+        this.optionalTLVList = optionalTLVList;
+        return this;
+    }
+
+    @Override
+    public byte[] serialize() {
+        int length = 2+this.chassisId.getLength() + 2+this.portId.getLength() +
+            2+this.ttl.getLength() + 2;
+        for (LLDPTLV tlv : this.optionalTLVList) {
+            length += 2 + tlv.getLength();
+        }
+
+        byte[] data = new byte[length];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put(this.chassisId.serialize());
+        bb.put(this.portId.serialize());
+        bb.put(this.ttl.serialize());
+        for (LLDPTLV tlv : this.optionalTLVList) {
+            bb.put(tlv.serialize());
+        }
+        bb.putShort((short) 0); // End of LLDPDU
+
+        if (this.parent != null && this.parent instanceof Ethernet)
+            ((Ethernet)this.parent).setEtherType(ethType);
+
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        LLDPTLV tlv;
+        do {
+            tlv = new LLDPTLV().deserialize(bb);
+
+            // if there was a failure to deserialize stop processing TLVs
+            if (tlv == null)
+                break;
+            switch (tlv.getType()) {
+                case 0x0:
+                    // can throw this one away, its just an end delimiter
+                    break;
+                case 0x1:
+                    this.chassisId = tlv;
+                    break;
+                case 0x2:
+                    this.portId = tlv;
+                    break;
+                case 0x3:
+                    this.ttl = tlv;
+                    break;
+                default:
+                    this.optionalTLVList.add(tlv);
+                    break;
+            }
+        } while (tlv.getType() != 0 && bb.hasRemaining());
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 883;
+        int result = super.hashCode();
+        result = prime * result
+                + ((chassisId == null) ? 0 : chassisId.hashCode());
+        result = prime * result + (optionalTLVList.hashCode());
+        result = prime * result + ((portId == null) ? 0 : portId.hashCode());
+        result = prime * result + ((ttl == null) ? 0 : ttl.hashCode());
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof LLDP))
+            return false;
+        LLDP other = (LLDP) obj;
+        if (chassisId == null) {
+            if (other.chassisId != null)
+                return false;
+        } else if (!chassisId.equals(other.chassisId))
+            return false;
+        if (!optionalTLVList.equals(other.optionalTLVList))
+            return false;
+        if (portId == null) {
+            if (other.portId != null)
+                return false;
+        } else if (!portId.equals(other.portId))
+            return false;
+        if (ttl == null) {
+            if (other.ttl != null)
+                return false;
+        } else if (!ttl.equals(other.ttl))
+            return false;
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPOrganizationalTLV.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPOrganizationalTLV.java
new file mode 100644
index 0000000..a0930bd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPOrganizationalTLV.java
@@ -0,0 +1,181 @@
+/**
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+/**
+ * The class representing LLDP Organizationally Specific TLV.
+ *
+ * @author Sho Shimizu (sho.shimizu@gmail.com)
+ */
+public class LLDPOrganizationalTLV extends LLDPTLV {
+    public static final int OUI_LENGTH = 3;
+    public static final int SUBTYPE_LENGTH = 1;
+    public static final byte ORGANIZATIONAL_TLV_TYPE = 127;
+    public static final int MAX_INFOSTRING_LENGTH = 507;
+
+    protected byte[] oui;
+    protected byte subType;
+    private byte[] infoString;
+
+    public LLDPOrganizationalTLV() {
+        type = ORGANIZATIONAL_TLV_TYPE;
+    }
+
+    /**
+     * Set the value of OUI.
+     * @param oui The value of OUI to be set.
+     * @return This LLDP Organizationally Specific TLV.
+     */
+    public LLDPOrganizationalTLV setOUI(byte[] oui) {
+        if (oui.length != OUI_LENGTH) {
+            throw new IllegalArgumentException("The length of OUI must be " + OUI_LENGTH +
+                ", but it is " + oui.length);
+        }
+        this.oui = Arrays.copyOf(oui, oui.length);
+        return this;
+    }
+
+    /**
+     * Returns the value of the OUI.
+     * @return The value of the OUI .
+     */
+    public byte[] getOUI() {
+        return Arrays.copyOf(oui, oui.length);
+    }
+
+    /**
+     * Set the value of sub type.
+     * @param subType The value of sub type to be set.
+     * @return This LLDP Organizationally Specific TLV.
+     */
+    public LLDPOrganizationalTLV setSubType(byte subType) {
+        this.subType = subType;
+        return this;
+    }
+
+    /**
+     * Returns the value of the sub type.
+     * @return The value of the sub type.
+     */
+    public byte getSubType() {
+        return subType;
+    }
+
+    /**
+     * Set the value of information string.
+     * @param infoString the byte array of the value of information string.
+     * @return This LLDP Organizationally Specific TLV.
+     */
+    public LLDPOrganizationalTLV setInfoString(byte[] infoString) {
+        if (infoString.length > MAX_INFOSTRING_LENGTH) {
+            throw new IllegalArgumentException("The length of infoString cannot exceed " + MAX_INFOSTRING_LENGTH);
+        }
+        this.infoString = Arrays.copyOf(infoString, infoString.length);
+        return this;
+    }
+
+    /**
+     * Set the value of information string.
+     * The String value is automatically converted into byte array with UTF-8 encoding.
+     * @param infoString the String value of information string.
+     * @return This LLDP Organizationally Specific TLV.
+     */
+    public LLDPOrganizationalTLV setInfoString(String infoString) {
+        byte[] infoStringBytes = infoString.getBytes(Charset.forName("UTF-8"));
+        return setInfoString(infoStringBytes);
+    }
+
+    /**
+     * Returns the value of information string.
+     * @return the value of information string.
+     */
+    public byte[] getInfoString() {
+        return Arrays.copyOf(infoString, infoString.length);
+    }
+
+    @Override
+    public byte[] serialize() {
+        int valueLength = OUI_LENGTH + SUBTYPE_LENGTH + infoString.length;
+        value = new byte[valueLength];
+        ByteBuffer bb = ByteBuffer.wrap(value);
+        bb.put(oui);
+        bb.put(subType);
+        bb.put(infoString);
+        return super.serialize();
+    }
+
+    @Override
+    public LLDPTLV deserialize(ByteBuffer bb) {
+        super.deserialize(bb);
+        ByteBuffer optionalField = ByteBuffer.wrap(value);
+
+        byte[] oui = new byte[OUI_LENGTH];
+        optionalField.get(oui);
+        setOUI(oui);
+
+        setSubType(optionalField.get());
+
+        byte[] infoString = new byte[getLength() - OUI_LENGTH - SUBTYPE_LENGTH];
+        optionalField.get(infoString);
+        setInfoString(infoString);
+        return this;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 1423;
+        int result = 1;
+        result = prime * result + type;
+        result = prime * result + length;
+        result = prime * result + Arrays.hashCode(oui);
+        result = prime * result + subType;
+        result = prime * result + Arrays.hashCode(infoString);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (!(o instanceof LLDPOrganizationalTLV)) {
+            return false;
+        }
+
+        LLDPOrganizationalTLV other = (LLDPOrganizationalTLV)o;
+        if (this.type != other.type) {
+            return false;
+        }
+        if (this.length != other.length) {
+            return false;
+        }
+        if (!Arrays.equals(this.oui, other.oui)) {
+            return false;
+        }
+        if (this.subType != other.subType) {
+            return false;
+        }
+        if (!Arrays.equals(this.infoString, other.infoString)) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPTLV.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPTLV.java
new file mode 100644
index 0000000..0552321
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPTLV.java
@@ -0,0 +1,140 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ *
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class LLDPTLV {
+    protected byte type;
+    protected short length;
+    protected byte[] value;
+
+    /**
+     * @return the type
+     */
+    public byte getType() {
+        return type;
+    }
+
+    /**
+     * @param type the type to set
+     */
+    public LLDPTLV setType(byte type) {
+        this.type = type;
+        return this;
+    }
+
+    /**
+     * @return the length
+     */
+    public short getLength() {
+        return length;
+    }
+
+    /**
+     * @param length the length to set
+     */
+    public LLDPTLV setLength(short length) {
+        this.length = length;
+        return this;
+    }
+
+    /**
+     * @return the value
+     */
+    public byte[] getValue() {
+        return value;
+    }
+
+    /**
+     * @param value the value to set
+     */
+    public LLDPTLV setValue(byte[] value) {
+        this.value = value;
+        return this;
+    }
+
+    public byte[] serialize() {
+        // type = 7 bits
+        // info string length 9 bits, each value == byte
+        // info string
+        short scratch = (short) (((0x7f & this.type) << 9) | (0x1ff & this.length));
+        byte[] data = new byte[2+this.length];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.putShort(scratch);
+        if (this.value != null)
+            bb.put(this.value);
+        return data;
+    }
+
+    public LLDPTLV deserialize(ByteBuffer bb) {
+        short sscratch;
+        sscratch = bb.getShort();
+        this.type = (byte) ((sscratch >> 9) & 0x7f);
+        this.length = (short) (sscratch & 0x1ff);
+        if (this.length > 0) {
+            this.value = new byte[this.length];
+
+            // if there is an underrun just toss the TLV
+            if (bb.remaining() < this.length)
+                return null;
+            bb.get(this.value);
+        }
+        return this;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 1423;
+        int result = 1;
+        result = prime * result + length;
+        result = prime * result + type;
+        result = prime * result + Arrays.hashCode(value);
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (!(obj instanceof LLDPTLV))
+            return false;
+        LLDPTLV other = (LLDPTLV) obj;
+        if (length != other.length)
+            return false;
+        if (type != other.type)
+            return false;
+        if (!Arrays.equals(value, other.value))
+            return false;
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/TCP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/TCP.java
new file mode 100644
index 0000000..889e4c6
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/TCP.java
@@ -0,0 +1,290 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+
+/**
+ *
+ * @author shudong.zhou@bigswitch.com
+ */
+public class TCP extends BasePacket {
+    protected short sourcePort;
+    protected short destinationPort;
+    protected int sequence;
+    protected int acknowledge;
+    protected byte dataOffset;
+    protected short flags;
+    protected short windowSize;
+    protected short checksum;
+    protected short urgentPointer;
+    protected byte[] options;
+
+    /**
+     * @return the sourcePort
+     */
+    public short getSourcePort() {
+        return sourcePort;
+    }
+
+    /**
+     * @param sourcePort the sourcePort to set
+     */
+    public TCP setSourcePort(short sourcePort) {
+        this.sourcePort = sourcePort;
+        return this;
+    }
+
+    /**
+     * @return the destinationPort
+     */
+    public short getDestinationPort() {
+        return destinationPort;
+    }
+
+    /**
+     * @param destinationPort the destinationPort to set
+     */
+    public TCP setDestinationPort(short destinationPort) {
+        this.destinationPort = destinationPort;
+        return this;
+    }
+
+    /**
+     * @return the checksum
+     */
+    public short getChecksum() {
+        return checksum;
+    }
+    
+    public int getSequence() {
+        return this.sequence;
+    }
+    public TCP setSequence(int seq) {
+        this.sequence = seq;
+        return this;
+    }
+    public int getAcknowledge() {
+        return this.acknowledge;
+    }
+    public TCP setAcknowledge(int ack) {
+        this.acknowledge = ack;
+        return this;
+    }
+    public byte getDataOffset() {
+        return this.dataOffset;
+    }
+    public TCP setDataOffset(byte offset) {
+        this.dataOffset = offset;
+        return this;
+    }
+    public short getFlags() {
+        return this.flags;
+    }
+    public TCP setFlags(short flags) {
+        this.flags = flags;
+        return this;
+    }
+    public short getWindowSize() {
+        return this.windowSize;
+    }
+    public TCP setWindowSize(short windowSize) {
+        this.windowSize = windowSize;
+        return this;
+    }
+    public short getTcpChecksum() {
+        return this.checksum;
+    }
+    public TCP setTcpChecksum(short checksum) {
+        this.checksum = checksum;
+        return this;
+    }
+    
+    @Override
+    public void resetChecksum() {
+        this.checksum = 0;
+        super.resetChecksum();
+    }
+    
+    public short getUrgentPointer(short urgentPointer) {
+        return this.urgentPointer;
+    }
+    public TCP setUrgentPointer(short urgentPointer) {
+        this.urgentPointer= urgentPointer;
+        return this;
+    }
+    public byte[] getOptions() {
+        return this.options;
+    }
+    public TCP setOptions(byte[] options) {
+        this.options = options;
+        this.dataOffset = (byte) ((20 + options.length + 3) >> 2);
+        return this;
+    }
+    /**
+     * @param checksum the checksum to set
+     */
+    public TCP setChecksum(short checksum) {
+        this.checksum = checksum;
+        return this;
+    }
+
+    /**
+     * Serializes the packet. Will compute and set the following fields if they
+     * are set to specific values at the time serialize is called:
+     *      -checksum : 0
+     *      -length : 0
+     */
+    public byte[] serialize() {
+        int length;
+        if (dataOffset == 0)
+            dataOffset = 5;  // default header length
+        length = dataOffset << 2;
+        byte[] payloadData = null;
+        if (payload != null) {
+            payload.setParent(this);
+            payloadData = payload.serialize();
+            length += payloadData.length;
+        }
+
+        byte[] data = new byte[length];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.putShort(this.sourcePort);
+        bb.putShort(this.destinationPort);
+        bb.putInt(this.sequence);
+        bb.putInt(this.acknowledge);
+        bb.putShort((short) (this.flags | (dataOffset << 12)));
+        bb.putShort(this.windowSize);
+        bb.putShort(this.checksum);
+        bb.putShort(this.urgentPointer);
+        if (dataOffset > 5) {
+            int padding;
+            bb.put(options);
+            padding = (dataOffset << 2) - 20 - options.length;
+            for (int i = 0; i < padding; i++)
+                bb.put((byte) 0);
+        }
+        if (payloadData != null)
+            bb.put(payloadData);
+
+        if (this.parent != null && this.parent instanceof IPv4)
+            ((IPv4)this.parent).setProtocol(IPv4.PROTOCOL_TCP);
+
+        // compute checksum if needed
+        if (this.checksum == 0) {
+            bb.rewind();
+            int accumulation = 0;
+
+            // compute pseudo header mac
+            if (this.parent != null && this.parent instanceof IPv4) {
+                IPv4 ipv4 = (IPv4) this.parent;
+                accumulation += ((ipv4.getSourceAddress() >> 16) & 0xffff)
+                        + (ipv4.getSourceAddress() & 0xffff);
+                accumulation += ((ipv4.getDestinationAddress() >> 16) & 0xffff)
+                        + (ipv4.getDestinationAddress() & 0xffff);
+                accumulation += ipv4.getProtocol() & 0xff;
+                accumulation += length & 0xffff;
+            }
+
+            for (int i = 0; i < length / 2; ++i) {
+                accumulation += 0xffff & bb.getShort();
+            }
+            // pad to an even number of shorts
+            if (length % 2 > 0) {
+                accumulation += (bb.get() & 0xff) << 8;
+            }
+
+            accumulation = ((accumulation >> 16) & 0xffff)
+                    + (accumulation & 0xffff);
+            this.checksum = (short) (~accumulation & 0xffff);
+            bb.putShort(16, this.checksum);
+        }
+        return data;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 5807;
+        int result = super.hashCode();
+        result = prime * result + checksum;
+        result = prime * result + destinationPort;
+        result = prime * result + sourcePort;
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof TCP))
+            return false;
+        TCP other = (TCP) obj;
+        // May want to compare fields based on the flags set
+        return (checksum == other.checksum) &&
+               (destinationPort == other.destinationPort) &&
+               (sourcePort == other.sourcePort) &&
+               (sequence == other.sequence) &&
+               (acknowledge == other.acknowledge) &&
+               (dataOffset == other.dataOffset) &&
+               (flags == other.flags) &&
+               (windowSize == other.windowSize) &&
+               (urgentPointer == other.urgentPointer) &&
+               (dataOffset == 5 || options.equals(other.options));
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.sourcePort = bb.getShort();
+        this.destinationPort = bb.getShort();
+        this.sequence = bb.getInt();
+        this.acknowledge = bb.getInt();
+        this.flags = bb.getShort();
+        this.dataOffset = (byte) ((this.flags >> 12) & 0xf);
+        this.flags = (short) (this.flags & 0x1ff);
+        this.windowSize = bb.getShort();
+        this.checksum = bb.getShort();
+        this.urgentPointer = bb.getShort();
+        if (this.dataOffset > 5) {
+            int optLength = (dataOffset << 2) - 20;
+            if (bb.limit() < bb.position()+optLength) {
+                optLength = bb.limit() - bb.position();
+            }
+            try {
+                this.options = new byte[optLength];
+                bb.get(this.options, 0, optLength);
+            } catch (IndexOutOfBoundsException e) {
+                this.options = null;
+            }
+        }
+        
+        this.payload = new Data();
+        this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+        this.payload.setParent(this);
+        return this;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/UDP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/UDP.java
new file mode 100644
index 0000000..cbeeedf
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/UDP.java
@@ -0,0 +1,231 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.packet;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class UDP extends BasePacket {
+    public static Map<Short, Class<? extends IPacket>> decodeMap;
+    public static short DHCP_SERVER_PORT = (short)67;
+    public static short DHCP_CLIENT_PORT = (short)68;
+
+    static {
+        decodeMap = new HashMap<Short, Class<? extends IPacket>>();
+        /*
+         * Disable DHCP until the deserialize code is hardened to deal with garbage input
+         */
+        UDP.decodeMap.put(DHCP_SERVER_PORT, DHCP.class);
+        UDP.decodeMap.put(DHCP_CLIENT_PORT, DHCP.class);
+        
+    }
+
+    protected short sourcePort;
+    protected short destinationPort;
+    protected short length;
+    protected short checksum;
+
+    /**
+     * @return the sourcePort
+     */
+    public short getSourcePort() {
+        return sourcePort;
+    }
+
+    /**
+     * @param sourcePort the sourcePort to set
+     */
+    public UDP setSourcePort(short sourcePort) {
+        this.sourcePort = sourcePort;
+        return this;
+    }
+
+    /**
+     * @return the destinationPort
+     */
+    public short getDestinationPort() {
+        return destinationPort;
+    }
+
+    /**
+     * @param destinationPort the destinationPort to set
+     */
+    public UDP setDestinationPort(short destinationPort) {
+        this.destinationPort = destinationPort;
+        return this;
+    }
+
+    /**
+     * @return the length
+     */
+    public short getLength() {
+        return length;
+    }
+
+    /**
+     * @return the checksum
+     */
+    public short getChecksum() {
+        return checksum;
+    }
+
+    /**
+     * @param checksum the checksum to set
+     */
+    public UDP setChecksum(short checksum) {
+        this.checksum = checksum;
+        return this;
+    }
+
+    @Override
+    public void resetChecksum() {
+        this.checksum = 0;
+        super.resetChecksum();
+    }
+
+    /**
+     * Serializes the packet. Will compute and set the following fields if they
+     * are set to specific values at the time serialize is called:
+     *      -checksum : 0
+     *      -length : 0
+     */
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (payload != null) {
+            payload.setParent(this);
+            payloadData = payload.serialize();
+        }
+
+        this.length = (short) (8 + ((payloadData == null) ? 0
+                : payloadData.length));
+
+        byte[] data = new byte[this.length];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.putShort(this.sourcePort);
+        bb.putShort(this.destinationPort);
+        bb.putShort(this.length);
+        bb.putShort(this.checksum);
+        if (payloadData != null)
+            bb.put(payloadData);
+
+        if (this.parent != null && this.parent instanceof IPv4)
+            ((IPv4)this.parent).setProtocol(IPv4.PROTOCOL_UDP);
+
+        // compute checksum if needed
+        if (this.checksum == 0) {
+            bb.rewind();
+            int accumulation = 0;
+
+            // compute pseudo header mac
+            if (this.parent != null && this.parent instanceof IPv4) {
+                IPv4 ipv4 = (IPv4) this.parent;
+                accumulation += ((ipv4.getSourceAddress() >> 16) & 0xffff)
+                        + (ipv4.getSourceAddress() & 0xffff);
+                accumulation += ((ipv4.getDestinationAddress() >> 16) & 0xffff)
+                        + (ipv4.getDestinationAddress() & 0xffff);
+                accumulation += ipv4.getProtocol() & 0xff;
+                accumulation += this.length & 0xffff;
+            }
+
+            for (int i = 0; i < this.length / 2; ++i) {
+                accumulation += 0xffff & bb.getShort();
+            }
+            // pad to an even number of shorts
+            if (this.length % 2 > 0) {
+                accumulation += (bb.get() & 0xff) << 8;
+            }
+
+            accumulation = ((accumulation >> 16) & 0xffff)
+                    + (accumulation & 0xffff);
+            this.checksum = (short) (~accumulation & 0xffff);
+            bb.putShort(6, this.checksum);
+        }
+        return data;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 5807;
+        int result = super.hashCode();
+        result = prime * result + checksum;
+        result = prime * result + destinationPort;
+        result = prime * result + length;
+        result = prime * result + sourcePort;
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (!(obj instanceof UDP))
+            return false;
+        UDP other = (UDP) obj;
+        if (checksum != other.checksum)
+            return false;
+        if (destinationPort != other.destinationPort)
+            return false;
+        if (length != other.length)
+            return false;
+        if (sourcePort != other.sourcePort)
+            return false;
+        return true;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        this.sourcePort = bb.getShort();
+        this.destinationPort = bb.getShort();
+        this.length = bb.getShort();
+        this.checksum = bb.getShort();
+
+        if (UDP.decodeMap.containsKey(this.destinationPort)) {
+            try {
+                this.payload = UDP.decodeMap.get(this.destinationPort).getConstructor().newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException("Failure instantiating class", e);
+            }
+        } else if (UDP.decodeMap.containsKey(this.sourcePort)) {
+            try {
+                this.payload = UDP.decodeMap.get(this.sourcePort).getConstructor().newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException("Failure instantiating class", e);
+            }
+        } else {
+            this.payload = new Data();
+        }
+        this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+        this.payload.setParent(this);
+        return this;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerClient.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerClient.java
new file mode 100644
index 0000000..abed853
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerClient.java
@@ -0,0 +1,93 @@
+package net.floodlightcontroller.packetstreamer;
+
+import net.floodlightcontroller.packetstreamer.thrift.*;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.thrift.TException;
+import org.apache.thrift.transport.TFramedTransport;
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TSocket;
+import org.apache.thrift.transport.TTransportException;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TProtocol;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The PacketStreamer Sample Client.
+ */
+public class PacketStreamerClient {
+    protected static Logger log = LoggerFactory.getLogger(PacketStreamerClient.class);
+
+    /** 
+     * Main function entry point;
+     * @param args
+     */
+    public static void main(String [] args) {
+        try {
+            int serverPort = Integer.parseInt(System.getProperty("net.floodlightcontroller.packetstreamer.port", "9090"));
+            TTransport transport;
+            transport = new TFramedTransport(new TSocket("localhost", serverPort));
+            transport.open();
+  
+
+            TProtocol protocol = new  TBinaryProtocol(transport);
+            PacketStreamer.Client client = new PacketStreamer.Client(protocol);
+
+            sendPackets(client, (short)2, OFMessageType.PACKET_IN, true);
+            log.debug("Terminate session1");
+            client.terminateSession("session1");
+
+            transport.close();
+        } catch (TException x) {
+            x.printStackTrace();
+        } 
+    }
+
+    /** 
+     * Send test packets of the given OFMessageType to the packetstreamer server;
+     * @param client Packetstreamer client object
+     * @param numPackets number of test packets to be sent
+     * @param ofType OFMessageType of the test packets
+     * @param sync true if send with synchronous interface, false for asynchronous interface
+     * @throws TException
+     */
+    private static void sendPackets(PacketStreamer.Client client, short numPackets, OFMessageType ofType, boolean sync) 
+    throws TException {
+        while (numPackets-- > 0) {
+            Message msg = new Message();
+            Packet packet = new Packet();
+    
+            List<String> sids = new ArrayList<String>();
+            sids.add("session1");
+            sids.add("session2");
+            msg.setSessionIDs(sids);
+            packet.setMessageType(ofType);
+            long sw_dpid = numPackets/40 + 1;
+            packet.setSwPortTuple(new SwitchPortTuple(sw_dpid, (short)(numPackets - (sw_dpid-1)*40)));
+    
+            String strData = "New data, sequence " + numPackets;
+            packet.setData(strData.getBytes());
+            msg.setPacket(packet);
+
+            try {
+                if (sync) {
+                      client.pushMessageSync(msg);
+                      log.debug("Send packet sync: " + msg.toString());
+                } else {
+                      client.pushMessageAsync(msg);
+                      log.debug("Send packet sync: " + msg.toString());
+                }
+            } catch (TTransportException e) {
+                log.error(e.toString());
+            }
+            
+            try {
+                Thread.sleep(100);
+            } catch (Exception e) {}
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerHandler.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerHandler.java
new file mode 100644
index 0000000..903295e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerHandler.java
@@ -0,0 +1,213 @@
+package net.floodlightcontroller.packetstreamer;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.packetstreamer.thrift.*;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The PacketStreamer handler class that implements the service APIs.
+ */
+@LogMessageCategory("OpenFlow Message Tracing")
+public class PacketStreamerHandler implements PacketStreamer.Iface {
+
+	/**
+	 * The queue wrapper class that contains the queue for the streamed packets.
+	 */
+    protected class SessionQueue {
+        protected BlockingQueue<ByteBuffer> pQueue;
+
+        /**
+         * The queue wrapper constructor
+         */
+        public SessionQueue() {
+            this.pQueue = new LinkedBlockingQueue<ByteBuffer>();
+        }
+
+        /**
+         * The access method to get to the internal queue.
+         */
+        public BlockingQueue<ByteBuffer> getQueue() {
+            return this.pQueue;
+        }
+    }
+    
+    /**
+     * The class logger object
+     */
+    protected static Logger log = 
+            LoggerFactory.getLogger(PacketStreamerServer.class);
+    
+    /**
+     * A sessionId-to-queue mapping
+     */
+    protected Map<String, SessionQueue> msgQueues;
+
+    /**
+     * The handler's constructor
+     */
+    public PacketStreamerHandler() {
+        this.msgQueues = new ConcurrentHashMap<String, SessionQueue>();
+    }
+
+    /**
+     * The implementation for getPackets() function.
+     * This is a blocking API.
+     * 
+     * @param sessionid
+     * @return A list of packets associated with the session
+     */
+    @Override
+    @LogMessageDocs({
+        @LogMessageDoc(level="ERROR",
+                message="Interrupted while waiting for session start",
+                explanation="The thread was interrupted waiting " +
+                     "for the packet streamer session to start",
+                recommendation=LogMessageDoc.CHECK_CONTROLLER),
+        @LogMessageDoc(level="ERROR",
+                message="Interrupted while waiting for packets",
+                explanation="The thread was interrupted waiting " +
+                        "for packets",
+                recommendation=LogMessageDoc.CHECK_CONTROLLER)
+    })
+    public List<ByteBuffer> getPackets(String sessionid)
+            throws org.apache.thrift.TException {
+        List<ByteBuffer> packets = new ArrayList<ByteBuffer>();
+        int count = 0;
+        
+        while (!msgQueues.containsKey(sessionid) && count++ < 100) {
+            log.debug("Queue for session {} doesn't exist yet.", sessionid);
+            try {
+                Thread.sleep(100);    // Wait 100 ms to check again.
+            } catch (InterruptedException e) {
+                log.error("Interrupted while waiting for session start");
+            }
+        }
+
+        if (count < 100) {
+	        SessionQueue pQueue = msgQueues.get(sessionid);
+	        BlockingQueue<ByteBuffer> queue = pQueue.getQueue();
+	        // Block if queue is empty
+	        try {
+	            packets.add(queue.take());
+	            queue.drainTo(packets);
+	        } catch (InterruptedException e) {
+	            log.error("Interrupted while waiting for packets");
+	        }
+        }
+
+        return packets;
+    }
+
+    /**
+     * The implementation for pushMessageSync() function.
+     * 
+     * @param msg
+     * @return 1 for success, 0 for failure
+     * @throws TException
+     */
+    @Override
+    @LogMessageDocs({
+        @LogMessageDoc(level="ERROR",
+                message="Could not push empty message",
+                explanation="An empty message was sent to the packet streamer",
+                recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG),
+        @LogMessageDoc(level="ERROR",
+                message="queue for session {sessionId} is null",
+                explanation="The queue for the packet streamer session " +
+                		"is missing",
+                recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    })
+
+    public int pushMessageSync(Message msg)
+            throws org.apache.thrift.TException {
+
+        if (msg == null) {
+            log.error("Could not push empty message");
+            return 0;
+        }
+
+        List<String> sessionids = msg.getSessionIDs();
+        for (String sid : sessionids) {
+            SessionQueue pQueue = null;
+
+            if (!msgQueues.containsKey(sid)) {
+                pQueue = new SessionQueue();
+                msgQueues.put(sid, pQueue);
+            } else {
+                pQueue = msgQueues.get(sid);
+            }
+
+            log.debug("pushMessageSync: SessionId: " + sid + 
+                      " Receive a message, " + msg.toString() + "\n");
+            ByteBuffer bb = ByteBuffer.wrap(msg.getPacket().getData());
+            //ByteBuffer dst = ByteBuffer.wrap(msg.getPacket().toString().getBytes());
+            BlockingQueue<ByteBuffer> queue = pQueue.getQueue();
+            if (queue != null) {
+                if (!queue.offer(bb)) {
+                    log.error("Failed to queue message for session: " + sid);
+                } else {
+                    log.debug("insert a message to session: " + sid);
+                }
+            } else {
+                log.error("queue for session {} is null", sid);
+            }
+        }
+
+        return 1;
+    }
+
+    /**
+     * The implementation for pushMessageAsync() function.
+     * 
+     * @param msg
+     * @throws TException
+     */
+    @Override
+    public void pushMessageAsync(Message msg)
+            throws org.apache.thrift.TException {
+        pushMessageSync(msg);
+        return;
+    }
+
+    /**
+     * The implementation for terminateSession() function.
+     * It removes the session to queue association.
+     * @param sessionid
+     * @throws TException
+     */
+    @Override
+    public void terminateSession(String sessionid)
+            throws org.apache.thrift.TException {
+        if (!msgQueues.containsKey(sessionid)) {
+            return;
+        }
+
+        SessionQueue pQueue = msgQueues.get(sessionid);
+
+        log.debug("terminateSession: SessionId: " + sessionid + "\n");
+        String data = "FilterTimeout";
+        ByteBuffer bb = ByteBuffer.wrap(data.getBytes());
+        BlockingQueue<ByteBuffer> queue = pQueue.getQueue();
+        if (queue != null) {
+            if (!queue.offer(bb)) {
+                log.error("Failed to queue message for session: " + sessionid);
+            }
+            msgQueues.remove(sessionid);
+        } else {
+            log.error("queue for session {} is null", sessionid);
+        }
+    }
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerServer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerServer.java
new file mode 100644
index 0000000..4a425e0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerServer.java
@@ -0,0 +1,72 @@
+package net.floodlightcontroller.packetstreamer;
+
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.server.TServer;
+import org.apache.thrift.server.THsHaServer;
+import org.apache.thrift.transport.TFramedTransport;
+import org.apache.thrift.transport.TNonblockingServerSocket;
+import org.apache.thrift.transport.TNonblockingServerTransport;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+// Generated code
+import net.floodlightcontroller.packetstreamer.thrift.*;
+
+/**
+ * The PacketStreamer Server that brokers the packet streaming service.
+ */
+@LogMessageCategory("OpenFlow Message Tracing")
+public class PacketStreamerServer {
+    protected static Logger log = LoggerFactory.getLogger(PacketStreamerServer.class);
+    protected static int port = 9090;
+    protected static PacketStreamerHandler handler;
+    protected static PacketStreamer.Processor<PacketStreamerHandler> processor;
+
+    
+    /** 
+     * Main function entry point;
+     * @param args
+     */
+    public static void main(String [] args) {
+        try {
+            port = Integer.parseInt(System.getProperty("net.floodlightcontroller.packetstreamer.port", "9090"));
+            
+            handler = new PacketStreamerHandler();
+            processor = new PacketStreamer.Processor<PacketStreamerHandler>(handler);
+
+            Runnable simple = new Runnable() {
+                public void run() {
+                    hshaServer(processor);
+                }
+            };
+
+            new Thread(simple).start();
+        } catch (Exception x) {
+            x.printStackTrace();
+        }
+    }
+
+    
+    /** 
+     * The function to create a thrift Half-Sync and Half-Async Server.
+     * @param processor
+     */
+    public static void hshaServer(PacketStreamer.Processor<PacketStreamerHandler> processor) {
+        try {
+            TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(port);
+            THsHaServer.Args args = new THsHaServer.Args(serverTransport);
+            args.processor(processor);
+            args.transportFactory(new TFramedTransport.Factory());
+            args.protocolFactory(new TBinaryProtocol.Factory(true, true));
+            TServer server = new THsHaServer(args);
+    
+            log.info("Starting the packetstreamer hsha server on port {} ...", port);
+            server.serve();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java
new file mode 100644
index 0000000..e76253d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java
@@ -0,0 +1,122 @@
+package net.floodlightcontroller.perfmon;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import net.floodlightcontroller.core.IOFMessageListener;
+
+@JsonSerialize(using=CumulativeTimeBucketJSONSerializer.class)
+public class CumulativeTimeBucket {
+    private long startTime_ns; // First pkt time-stamp in this bucket
+    private Map<Integer, OneComponentTime> compStats;
+    private long totalPktCnt;
+    private long totalProcTimeNs; // total processing time for one pkt in
+    private long sumSquaredProcTimeNs2;
+    private long maxTotalProcTimeNs;
+    private long minTotalProcTimeNs;
+    private long avgTotalProcTimeNs;
+    private long sigmaTotalProcTimeNs; // std. deviation
+
+    public long getStartTimeNs() {
+        return startTime_ns;
+    }
+
+    public long getTotalPktCnt() {
+        return totalPktCnt;
+    }
+    
+    public long getAverageProcTimeNs() {
+        return avgTotalProcTimeNs;
+    }
+
+    public long getMinTotalProcTimeNs() {
+        return minTotalProcTimeNs;
+    }
+    
+    public long getMaxTotalProcTimeNs() {
+        return maxTotalProcTimeNs;
+    }
+    
+    public long getTotalSigmaProcTimeNs() {
+        return sigmaTotalProcTimeNs;
+    }
+    
+    public int getNumComps() {
+        return compStats.values().size();
+    }
+    
+    public Collection<OneComponentTime> getModules() {
+        return compStats.values();
+    }
+
+    public CumulativeTimeBucket(List<IOFMessageListener> listeners) {
+        compStats = new ConcurrentHashMap<Integer, OneComponentTime>(listeners.size());
+        for (IOFMessageListener l : listeners) {
+            OneComponentTime oct = new OneComponentTime(l);
+            compStats.put(oct.hashCode(), oct);
+        }
+        startTime_ns = System.nanoTime();
+    }
+
+    private void updateSquaredProcessingTime(long curTimeNs) {
+        sumSquaredProcTimeNs2 += (Math.pow(curTimeNs, 2));
+    }
+    
+    /**
+     * Resets all counters and counters for each component time
+     */
+    public void reset() {
+        startTime_ns = System.nanoTime();
+        totalPktCnt = 0;
+        totalProcTimeNs = 0;
+        avgTotalProcTimeNs = 0;
+        sumSquaredProcTimeNs2 = 0;
+        maxTotalProcTimeNs = Long.MIN_VALUE;
+        minTotalProcTimeNs = Long.MAX_VALUE;
+        sigmaTotalProcTimeNs = 0;
+        for (OneComponentTime oct : compStats.values()) {
+            oct.resetAllCounters();
+        }
+    }
+    
+    private void computeSigma() {
+        // Computes std. deviation from the sum of count numbers and from
+        // the sum of the squares of count numbers
+        double temp = totalProcTimeNs;
+        temp = Math.pow(temp, 2) / totalPktCnt;
+        temp = (sumSquaredProcTimeNs2 - temp) / totalPktCnt;
+        sigmaTotalProcTimeNs = (long) Math.sqrt(temp);
+    }
+    
+    public void computeAverages() {
+        // Must be called last to, needs latest info
+        computeSigma();
+        
+        for (OneComponentTime oct : compStats.values()) {
+            oct.computeSigma();
+        }
+    }
+    
+    public void updatePerPacketCounters(long procTimeNs) {
+        totalPktCnt++;
+        totalProcTimeNs += procTimeNs;
+        avgTotalProcTimeNs = totalProcTimeNs / totalPktCnt;
+        updateSquaredProcessingTime(procTimeNs);
+        
+        if (procTimeNs > maxTotalProcTimeNs) {
+            maxTotalProcTimeNs = procTimeNs;
+        }
+        
+        if (procTimeNs < minTotalProcTimeNs) {
+            minTotalProcTimeNs = procTimeNs;
+        }
+    }
+    
+    public void updateOneComponent(IOFMessageListener l, long procTimeNs) {
+        compStats.get(l.hashCode()).updatePerPacketCounters(procTimeNs);
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java
new file mode 100644
index 0000000..e492777
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java
@@ -0,0 +1,47 @@
+package net.floodlightcontroller.perfmon;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+public class CumulativeTimeBucketJSONSerializer
+                                extends JsonSerializer<CumulativeTimeBucket> {
+    /**
+     * Performs the serialization of a OneComponentTime object
+     */
+   @Override
+   public void serialize(CumulativeTimeBucket ctb,
+                   JsonGenerator jGen,
+                   SerializerProvider serializer) 
+                   throws IOException, JsonProcessingException {
+       jGen.writeStartObject();
+       Timestamp ts = new Timestamp(ctb.getStartTimeNs()/1000000);
+       jGen.writeStringField("start-time", ts.toString());
+       jGen.writeStringField("current-time", 
+         new Timestamp(System.currentTimeMillis()).toString());
+       jGen.writeNumberField("total-packets", ctb.getTotalPktCnt());
+       jGen.writeNumberField("average", ctb.getAverageProcTimeNs());
+       jGen.writeNumberField("min", ctb.getMinTotalProcTimeNs());
+       jGen.writeNumberField("max", ctb.getMaxTotalProcTimeNs());
+       jGen.writeNumberField("std-dev", ctb.getTotalSigmaProcTimeNs());
+       jGen.writeArrayFieldStart("modules");
+       for (OneComponentTime oct : ctb.getModules()) {
+           serializer.defaultSerializeValue(oct, jGen);
+       }
+       jGen.writeEndArray();
+       jGen.writeEndObject();
+   }
+
+   /**
+    * Tells SimpleModule that we are the serializer for OFMatch
+    */
+   @Override
+   public Class<CumulativeTimeBucket> handledType() {
+       return CumulativeTimeBucket.class;
+   }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java
new file mode 100644
index 0000000..80dfda0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java
@@ -0,0 +1,37 @@
+package net.floodlightcontroller.perfmon;
+
+import java.util.List;
+
+import org.openflow.protocol.OFMessage;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IPktInProcessingTimeService extends IFloodlightService {
+
+    /**
+     * Creates time buckets for a set of modules to measure their performance
+     * @param listeners The message listeners to create time buckets for
+     */
+    public void bootstrap(List<IOFMessageListener> listeners);
+    
+    /**
+     * Stores a timestamp in ns. Used right before a service handles an
+     * OF message. Only stores if the service is enabled.
+     */
+    public void recordStartTimeComp(IOFMessageListener listener);
+    
+    public void recordEndTimeComp(IOFMessageListener listener);
+    
+    public void recordStartTimePktIn();
+    
+    public void recordEndTimePktIn(IOFSwitch sw, OFMessage m, FloodlightContext cntx);
+    
+    public boolean isEnabled();
+    
+    public void setEnabled(boolean enabled);
+    
+    public CumulativeTimeBucket getCtb();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java
new file mode 100644
index 0000000..3d9504b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java
@@ -0,0 +1,109 @@
+package net.floodlightcontroller.perfmon;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.openflow.protocol.OFMessage;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+/**
+ * An IPktInProcessingTimeService implementation that does nothing.
+ * This is used mainly for performance testing or if you don't
+ * want to use the IPktInProcessingTimeService features.
+ * @author alexreimers
+ *
+ */
+public class NullPktInProcessingTime 
+    implements IFloodlightModule, IPktInProcessingTimeService {
+    
+    private CumulativeTimeBucket ctb;
+    private boolean inited = false;
+    
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IPktInProcessingTimeService.class);
+        return l;
+    }
+    
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m = 
+            new HashMap<Class<? extends IFloodlightService>,
+                        IFloodlightService>();
+        // We are the class that implements the service
+        m.put(IPktInProcessingTimeService.class, this);
+        return m;
+    }
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        // We don't have any dependencies
+        return null;
+    }
+    
+    @Override
+    public void init(FloodlightModuleContext context)
+                             throws FloodlightModuleException {
+
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // no-op
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return false;
+    }
+
+    @Override
+    public void bootstrap(List<IOFMessageListener> listeners) {
+        if (!inited)
+            ctb = new CumulativeTimeBucket(listeners);
+    }
+
+    @Override
+    public void recordStartTimeComp(IOFMessageListener listener) {
+
+    }
+
+    @Override
+    public void recordEndTimeComp(IOFMessageListener listener) {
+
+    }
+
+    @Override
+    public void recordStartTimePktIn() {
+
+    }
+
+    @Override
+    public void recordEndTimePktIn(IOFSwitch sw, OFMessage m,
+                                   FloodlightContext cntx) {
+        
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+    
+    }
+
+    @Override
+    public CumulativeTimeBucket getCtb() {
+        return ctb;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java
new file mode 100644
index 0000000..3e9734b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java
@@ -0,0 +1,129 @@
+package net.floodlightcontroller.perfmon;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import net.floodlightcontroller.core.IOFMessageListener;
+
+/**
+ * Holds OF message processing time information for one IFloodlightModule.
+ * @author Subrata
+ */
+public class OneComponentTime {
+    private int compId; // hascode of IOFMessageListener
+    private String compName;
+    private int pktCnt;
+    // all times in nanoseconds
+    private long totalProcTimeNs;
+    private long sumSquaredProcTimeNs2; // squared
+    private long maxProcTimeNs;
+    private long minProcTimeNs;
+    private long avgProcTimeNs;
+    private long sigmaProcTimeNs;  // std. deviation
+
+    public OneComponentTime(IOFMessageListener module) {
+        compId = module.hashCode();
+        compName = module.getClass().getCanonicalName();
+        resetAllCounters();
+    }
+    
+    public void resetAllCounters() {
+        maxProcTimeNs = Long.MIN_VALUE;
+        minProcTimeNs = Long.MAX_VALUE;
+        pktCnt = 0;
+        totalProcTimeNs = 0;
+        sumSquaredProcTimeNs2 = 0;
+        avgProcTimeNs = 0;
+        sigmaProcTimeNs = 0;
+    }
+    
+    @JsonProperty("module-name")
+    public String getCompName() {
+        return compName;
+    }
+
+    @JsonProperty("num-packets")
+    public int getPktCnt() {
+        return pktCnt;
+    }
+
+    @JsonProperty("total")
+    public long getSumProcTimeNs() {
+        return totalProcTimeNs;
+    }
+
+    @JsonProperty("max")
+    public long getMaxProcTimeNs() {
+        return maxProcTimeNs;
+    }
+
+    @JsonProperty("min")
+    public long getMinProcTimeNs() {
+        return minProcTimeNs;
+    }
+
+    @JsonProperty("average")
+    public long getAvgProcTimeNs() {
+        return avgProcTimeNs;
+    }
+
+    @JsonProperty("std-dev")
+    public long getSigmaProcTimeNs() {
+        return sigmaProcTimeNs;
+    }
+    
+    @JsonProperty("average-squared")
+    public long getSumSquaredProcTimeNs() {
+        return sumSquaredProcTimeNs2;
+    }
+
+    // Methods used to update the counters
+    
+    private void increasePktCount() {
+        pktCnt++;
+    }
+    
+    private void updateTotalProcessingTime(long procTimeNs) {
+        totalProcTimeNs += procTimeNs;
+    }
+    
+    private void updateAvgProcessTime() {
+        avgProcTimeNs = totalProcTimeNs / pktCnt;
+    }
+    
+    private void updateSquaredProcessingTime(long procTimeNs) {
+        sumSquaredProcTimeNs2 += (Math.pow(procTimeNs, 2));
+    }
+    
+    private void calculateMinProcTime(long curTimeNs) {
+        if (curTimeNs < minProcTimeNs)
+            minProcTimeNs = curTimeNs;
+    }
+    
+    private void calculateMaxProcTime(long curTimeNs) {
+        if (curTimeNs > maxProcTimeNs)
+            maxProcTimeNs = curTimeNs;
+    }
+    
+    public void computeSigma() {
+        // Computes std. deviation from the sum of count numbers and from
+        // the sum of the squares of count numbers
+        double temp = totalProcTimeNs;
+        temp = Math.pow(temp, 2) / pktCnt;
+        temp = (sumSquaredProcTimeNs2 - temp) / pktCnt;
+        sigmaProcTimeNs = (long) Math.sqrt(temp);
+    }
+    
+    public void updatePerPacketCounters(long procTimeNs) {
+        increasePktCount();
+        updateTotalProcessingTime(procTimeNs);
+        calculateMinProcTime(procTimeNs);
+        calculateMaxProcTime(procTimeNs);
+        updateAvgProcessTime();
+        updateSquaredProcessingTime(procTimeNs);
+    }
+    
+    @Override
+    public int hashCode() {
+        return compId;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonDataResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonDataResource.java
new file mode 100644
index 0000000..c43708d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonDataResource.java
@@ -0,0 +1,33 @@
+package net.floodlightcontroller.perfmon;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Return the performance monitoring data for the get rest api call
+ * @author subrata
+ */
+public class PerfMonDataResource extends ServerResource {
+    protected static Logger logger = LoggerFactory.getLogger(PerfMonDataResource.class);  
+    
+    @Get("json")
+    public CumulativeTimeBucket handleApiQuery() {        
+        IPktInProcessingTimeService pktinProcTime = 
+            (IPktInProcessingTimeService)getContext().getAttributes().
+                get(IPktInProcessingTimeService.class.getCanonicalName());
+        
+        setStatus(Status.SUCCESS_OK, "OK");
+        // Allocate output object
+        if (pktinProcTime.isEnabled()) {
+            CumulativeTimeBucket ctb = pktinProcTime.getCtb();
+            ctb.computeAverages();
+            return ctb;
+        }
+        
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonToggleResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonToggleResource.java
new file mode 100644
index 0000000..9ea1876
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonToggleResource.java
@@ -0,0 +1,28 @@
+package net.floodlightcontroller.perfmon;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class PerfMonToggleResource extends ServerResource {
+    
+    @Get("json")
+    public String retrieve() {
+        IPktInProcessingTimeService pktinProcTime = 
+                (IPktInProcessingTimeService)getContext().getAttributes().
+                    get(IPktInProcessingTimeService.class.getCanonicalName());
+        
+        String param = ((String)getRequestAttributes().get("perfmonstate")).toLowerCase();
+        if (param.equals("reset")) {
+            pktinProcTime.getCtb().reset();
+        } else {
+            if (param.equals("enable") || param.equals("true")) {
+                pktinProcTime.setEnabled(true);
+            } else if (param.equals("disable") || param.equals("false")) {
+                pktinProcTime.setEnabled(false);
+            }
+        }
+        setStatus(Status.SUCCESS_OK, "OK");
+        return "{ \"enabled\" : " + pktinProcTime.isEnabled() + " }";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfWebRoutable.java
new file mode 100644
index 0000000..ace0bc8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfWebRoutable.java
@@ -0,0 +1,23 @@
+package net.floodlightcontroller.perfmon;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class PerfWebRoutable implements RestletRoutable {
+
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/data/json", PerfMonDataResource.class);
+        router.attach("/{perfmonstate}/json", PerfMonToggleResource.class); // enable, disable, or reset
+        return router;
+    }
+
+    @Override
+    public String basePath() {
+        return "/wm/performance";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java
new file mode 100644
index 0000000..639623b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java
@@ -0,0 +1,205 @@
+/**
+ * Performance monitoring package
+ */
+package net.floodlightcontroller.perfmon;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.restserver.IRestApiService;
+
+import org.openflow.protocol.OFMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class contains a set of buckets (called time buckets as the
+ * primarily contain 'times' that are used in a circular way to 
+ * store information on packet in processing time.
+ * Each bucket is meant to store the various processing time 
+ * related data for a fixed duration.
+ * Buckets are reused to reduce garbage generation! Once the
+ * last bucket is used up the LRU bucket is reused.
+ * 
+ * Naming convention for variable or constants
+ * variable_s : value in seconds
+ * variable_ms: value in milliseconds
+ * variable_us: value in microseconds
+ * variable_ns: value in nanoseconds
+ * 
+ * Key Constants:
+ * ONE_BUCKET_DURATION_SECONDS_INT:  time duration of each bucket
+ * BUCKET_SET_SIZE: Number of buckets
+ * TOT_PROC_TIME_WARN_THRESHOLD_US: if processing time for a packet
+ *    exceeds this threshold then a warning LOG message is generated
+ * TOT_PROC_TIME_ALERT_THRESHOLD_US: same as above but an alert level
+ *    syslog is generated instead
+ * 
+ */
+@LogMessageCategory("Performance Monitoring")
+public class PktInProcessingTime
+    implements IFloodlightModule, IPktInProcessingTimeService {
+
+    
+    // Our dependencies
+    private IRestApiService restApi;
+    
+    protected long ptWarningThresholdInNano;
+
+    // DB storage tables
+    protected static final String ControllerTableName = "controller_controller";
+    public static final String COLUMN_ID = "id";
+    public static final String COLUMN_PERF_MON = "performance_monitor_feature";
+    
+    protected static  Logger  logger = 
+        LoggerFactory.getLogger(PktInProcessingTime.class);
+    
+    protected boolean isEnabled = false;
+    protected boolean isInited = false;
+    // Maintains the time when the last packet was processed
+    protected long lastPktTime_ns;
+    private CumulativeTimeBucket ctb = null;
+
+    
+    /***
+     * BUCKET_SET_SIZE buckets each holding 10s of processing time data, a total
+     * of 30*10s = 5mins of processing time data is maintained
+     */
+    protected static final int ONE_BUCKET_DURATION_SECONDS = 10;// seconds
+    protected static final long ONE_BUCKET_DURATION_NANOSECONDS  =
+                                ONE_BUCKET_DURATION_SECONDS * 1000000000;
+    
+    @Override
+    public void bootstrap(List<IOFMessageListener> listeners) {
+        if (!isInited) {
+            ctb = new CumulativeTimeBucket(listeners);
+            isInited = true;
+        }
+    }
+    
+    @Override
+    public boolean isEnabled() {
+        return isEnabled && isInited;
+    }
+    
+    @Override
+    public void setEnabled(boolean enabled) {
+        this.isEnabled = enabled;
+        logger.debug("Setting module to " + isEnabled);
+    }
+    
+    @Override
+    public CumulativeTimeBucket getCtb() {
+        return ctb;
+    }
+    
+    private long startTimePktNs;
+    private long startTimeCompNs;
+    @Override
+    public void recordStartTimeComp(IOFMessageListener listener) {
+        if (isEnabled()) {
+            startTimeCompNs = System.nanoTime();
+        }
+    }
+    
+    @Override
+    public void recordEndTimeComp(IOFMessageListener listener) {
+        if (isEnabled()) {
+            long procTime = System.nanoTime() - startTimeCompNs;
+            ctb.updateOneComponent(listener, procTime);
+        }
+    }
+    
+    @Override
+    public void recordStartTimePktIn() {
+        if (isEnabled()) {
+            startTimePktNs = System.nanoTime();
+        }
+    }
+    
+    @Override
+    @LogMessageDoc(level="WARN",
+            message="Time to process packet-in exceeded threshold: {}",
+            explanation="Time to process packet-in exceeded the configured " +
+            		"performance threshold",
+            recommendation=LogMessageDoc.CHECK_CONTROLLER)
+    public void recordEndTimePktIn(IOFSwitch sw, OFMessage m, FloodlightContext cntx) {
+        if (isEnabled()) {
+            long procTimeNs = System.nanoTime() - startTimePktNs;
+            ctb.updatePerPacketCounters(procTimeNs);
+            
+            if (ptWarningThresholdInNano > 0 && 
+                    procTimeNs > ptWarningThresholdInNano) {
+                logger.warn("Time to process packet-in exceeded threshold: {}", 
+                            procTimeNs/1000);
+            }
+        }
+    }
+    
+    // IFloodlightModule methods
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IPktInProcessingTimeService.class);
+        return l;
+    }
+    
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m = 
+            new HashMap<Class<? extends IFloodlightService>,
+                        IFloodlightService>();
+        // We are the class that implements the service
+        m.put(IPktInProcessingTimeService.class, this);
+        return m;
+    }
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IRestApiService.class);
+        return l;
+    }
+    
+    @Override
+    public void init(FloodlightModuleContext context)
+                                             throws FloodlightModuleException {
+        restApi = context.getServiceImpl(IRestApiService.class);
+    }
+    
+    @Override
+    @LogMessageDoc(level="INFO",
+        message="Packet processing time threshold for warning" +
+            " set to {time} ms.",
+        explanation="Performance monitoring will log a warning if " +
+    		"packet processing time exceeds the configured threshold")
+    public void startUp(FloodlightModuleContext context) {
+        // Add our REST API
+        restApi.addRestletRoutable(new PerfWebRoutable());
+        
+        // TODO - Alex - change this to a config option
+        ptWarningThresholdInNano = Long.parseLong(System.getProperty(
+             "net.floodlightcontroller.core.PTWarningThresholdInMilli", "0")) * 1000000;
+        if (ptWarningThresholdInNano > 0) {
+            logger.info("Packet processing time threshold for warning" +
+            		" set to {} ms.", ptWarningThresholdInNano/1000000);
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/IRestApiService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/IRestApiService.java
new file mode 100644
index 0000000..d906795
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/IRestApiService.java
@@ -0,0 +1,16 @@
+package net.floodlightcontroller.restserver;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IRestApiService extends IFloodlightService {
+    /**
+     * Adds a REST API
+     * @param routeable
+     */
+    public void addRestletRoutable(RestletRoutable routable);
+
+    /**
+     * Runs the REST API server
+     */
+    public void run();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java
new file mode 100644
index 0000000..2ca8483
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java
@@ -0,0 +1,188 @@
+package net.floodlightcontroller.restserver;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.restlet.Application;
+import org.restlet.Component;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.Restlet;
+import org.restlet.data.Protocol;
+import org.restlet.data.Reference;
+import org.restlet.data.Status;
+import org.restlet.ext.jackson.JacksonRepresentation;
+import org.restlet.representation.Representation;
+import org.restlet.routing.Filter;
+import org.restlet.routing.Router;
+import org.restlet.routing.Template;
+import org.restlet.service.StatusService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public class RestApiServer
+    implements IFloodlightModule, IRestApiService {
+    protected static Logger logger = LoggerFactory.getLogger(RestApiServer.class);
+    protected List<RestletRoutable> restlets;
+    protected FloodlightModuleContext fmlContext;
+    protected int restPort = 8080;
+    
+    // ***********
+    // Application
+    // ***********
+    
+    protected class RestApplication extends Application {
+        protected Context context;
+        
+        public RestApplication() {
+            super(new Context());
+            this.context = getContext();
+        }
+        
+        @Override
+        public Restlet createInboundRoot() {
+            Router baseRouter = new Router(context);
+            baseRouter.setDefaultMatchingMode(Template.MODE_STARTS_WITH);
+            for (RestletRoutable rr : restlets) {
+                baseRouter.attach(rr.basePath(), rr.getRestlet(context));
+            }
+
+            Filter slashFilter = new Filter() {            
+                @Override
+                protected int beforeHandle(Request request, Response response) {
+                    Reference ref = request.getResourceRef();
+                    String originalPath = ref.getPath();
+                    if (originalPath.contains("//"))
+                    {
+                        String newPath = originalPath.replaceAll("/+", "/");
+                        ref.setPath(newPath);
+                    }
+                    return Filter.CONTINUE;
+                }
+
+            };
+            slashFilter.setNext(baseRouter);
+            
+            return slashFilter;
+        }
+        
+        public void run(FloodlightModuleContext fmlContext, int restPort) {
+            setStatusService(new StatusService() {
+                @Override
+                public Representation getRepresentation(Status status,
+                                                        Request request,
+                                                        Response response) {
+                    return new JacksonRepresentation<Status>(status);
+                }                
+            });
+            
+            // Add everything in the module context to the rest
+            for (Class<? extends IFloodlightService> s : fmlContext.getAllServices()) {
+                if (logger.isTraceEnabled()) {
+                    logger.trace("Adding {} for service {} into context",
+                                 s.getCanonicalName(), fmlContext.getServiceImpl(s));
+                }
+                context.getAttributes().put(s.getCanonicalName(), 
+                                            fmlContext.getServiceImpl(s));
+            }
+            
+            // Start listening for REST requests
+            try {
+                final Component component = new Component();
+                component.getServers().add(Protocol.HTTP, restPort);
+                component.getClients().add(Protocol.CLAP);
+                component.getDefaultHost().attach(this);
+                component.start();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+    
+    // ***************
+    // IRestApiService
+    // ***************
+    
+    @Override
+    public void addRestletRoutable(RestletRoutable routable) {
+        restlets.add(routable);
+    }
+
+    @Override
+    public void run() {
+        if (logger.isDebugEnabled()) {
+            StringBuffer sb = new StringBuffer();
+            sb.append("REST API routables: ");
+            for (RestletRoutable routable : restlets) {
+                sb.append(routable.getClass().getSimpleName());
+                sb.append(" (");
+                sb.append(routable.basePath());
+                sb.append("), ");
+            }
+            logger.debug(sb.toString());
+        }
+        
+        RestApplication restApp = new RestApplication();
+        restApp.run(fmlContext, restPort);
+    }
+    
+    // *****************
+    // IFloodlightModule
+    // *****************
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> services =
+                new ArrayList<Class<? extends IFloodlightService>>(1);
+        services.add(IRestApiService.class);
+        return services;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m = 
+            new HashMap<Class<? extends IFloodlightService>,
+                        IFloodlightService>();
+        m.put(IRestApiService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        // We don't have any
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        // This has to be done here since we don't know what order the
+        // startUp methods will be called
+        this.restlets = new ArrayList<RestletRoutable>();
+        this.fmlContext = context;
+        
+        // read our config options
+        Map<String, String> configOptions = context.getConfigParams(this);
+        String port = configOptions.get("port");
+        if (port != null) {
+            restPort = Integer.parseInt(port);
+        }
+        logger.debug("REST port set to {}", restPort);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext Context) {
+        // no-op
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestletRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestletRoutable.java
new file mode 100644
index 0000000..cb7dfce
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestletRoutable.java
@@ -0,0 +1,40 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.restserver;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+
+/**
+ * Register a set of REST resources with the central controller
+ * @author readams
+ */
+public interface RestletRoutable {
+    /**
+     * Get the restlet that will map to the resources
+     * @param context the context for constructing the restlet
+     * @return the restlet
+     */
+    Restlet getRestlet(Context context);
+    
+    /**
+     * Get the base path URL where the router should be registered
+     * @return the base path URL where the router should be registered
+     */
+    String basePath();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java
new file mode 100644
index 0000000..0c3703c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java
@@ -0,0 +1,67 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.routing;
+import java.util.HashMap;
+
+import net.floodlightcontroller.routing.Link;
+
+import org.openflow.util.HexString;
+
+public class BroadcastTree {
+    protected HashMap<Long, Link> links;
+    protected HashMap<Long, Integer> costs;
+
+    public BroadcastTree() {
+        links = new HashMap<Long, Link>();
+        costs = new HashMap<Long, Integer>();
+    }
+
+    public BroadcastTree(HashMap<Long, Link> links, HashMap<Long, Integer> costs) {
+        this.links = links;
+        this.costs = costs;
+    }
+
+    public Link getTreeLink(long node) {
+        return links.get(node);
+    }
+
+    public int getCost(long node) {
+        if (costs.get(node) == null) return -1;
+        return (costs.get(node));
+    }
+
+    public HashMap<Long, Link> getLinks() {
+        return links;
+    }
+
+    public void addTreeLink(long myNode, Link link) {
+        links.put(myNode, link);
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        for(long n: links.keySet()) {
+            sb.append("[" + HexString.toHexString(n) + ": cost=" + costs.get(n) + ", " + links.get(n) + "]");
+        }
+        return sb.toString();
+    }
+
+    public HashMap<Long, Integer> getCosts() {
+        return costs;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
new file mode 100644
index 0000000..22312c1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
@@ -0,0 +1,692 @@
+/**
+ *    Copyright 2011, Big Switch Networks, Inc. 
+ *    Originally created by David Erickson, Stanford University
+ * 
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.routing;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.util.AppCookie;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.IRoutingDecision;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.util.OFMessageDamper;
+import net.floodlightcontroller.util.TimedCache;
+
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract base class for implementing a forwarding module.  Forwarding is
+ * responsible for programming flows to a switch in response to a policy
+ * decision.
+ */
+@LogMessageCategory("Flow Programming")
+public abstract class ForwardingBase 
+    implements IOFMessageListener, IDeviceListener {
+    
+    protected static Logger log =
+            LoggerFactory.getLogger(ForwardingBase.class);
+
+    protected static int OFMESSAGE_DAMPER_CAPACITY = 50000; // TODO: find sweet spot
+    protected static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms 
+
+    public static short FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds
+    public static short FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite
+    
+    protected IFloodlightProviderService floodlightProvider;
+    protected IDeviceService deviceManager;
+    protected IRoutingService routingEngine;
+    protected ITopologyService topology;
+    protected ICounterStoreService counterStore;
+    
+    protected OFMessageDamper messageDamper;
+    
+    // for broadcast loop suppression
+    protected boolean broadcastCacheFeature = true;
+    public final int prime1 = 2633;  // for hash calculation
+    public final static int prime2 = 4357;  // for hash calculation
+    public TimedCache<Long> broadcastCache =
+        new TimedCache<Long>(100, 5*1000);  // 5 seconds interval;
+
+    // flow-mod - for use in the cookie
+    public static final int FORWARDING_APP_ID = 2; // TODO: This must be managed
+                                                   // by a global APP_ID class
+    public long appCookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0);
+    
+    // Comparator for sorting by SwitchCluster
+    public Comparator<SwitchPort> clusterIdComparator =
+            new Comparator<SwitchPort>() {
+                @Override
+                public int compare(SwitchPort d1, SwitchPort d2) {
+                    Long d1ClusterId = 
+                            topology.getL2DomainId(d1.getSwitchDPID());
+                    Long d2ClusterId = 
+                            topology.getL2DomainId(d2.getSwitchDPID());
+                    return d1ClusterId.compareTo(d2ClusterId);
+                }
+            };
+            
+    /**
+     * init data structures
+     * 
+     */
+    protected void init() {
+        messageDamper = new OFMessageDamper(OFMESSAGE_DAMPER_CAPACITY, 
+                                            EnumSet.of(OFType.FLOW_MOD),
+                                            OFMESSAGE_DAMPER_TIMEOUT);
+    }
+
+    /**
+     * Adds a listener for devicemanager and registers for PacketIns.
+     */
+    protected void startUp() {
+        deviceManager.addListener(this);
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+    }
+
+    /**
+     * Returns the application name "forwarding".
+     */
+    @Override
+    public String getName() {
+        return "forwarding";
+    }
+
+    /**
+     * All subclasses must define this function if they want any specific
+     * forwarding action
+     * 
+     * @param sw
+     *            Switch that the packet came in from
+     * @param pi
+     *            The packet that came in
+     * @param decision
+     *            Any decision made by a policy engine
+     */
+    public abstract Command
+            processPacketInMessage(IOFSwitch sw, OFPacketIn pi,
+                                   IRoutingDecision decision,
+                                   FloodlightContext cntx);
+
+    @Override
+    public Command receive(IOFSwitch sw, OFMessage msg,
+                           FloodlightContext cntx) {
+        switch (msg.getType()) {
+            case PACKET_IN:
+                IRoutingDecision decision = null;
+                if (cntx != null)
+                     decision =
+                             IRoutingDecision.rtStore.get(cntx,
+                                                          IRoutingDecision.CONTEXT_DECISION);
+
+                return this.processPacketInMessage(sw,
+                                                   (OFPacketIn) msg,
+                                                   decision,
+                                                   cntx);
+            default:
+                break;
+        }
+        return Command.CONTINUE;
+    }
+
+    /**
+     * Push routes from back to front
+     * @param route Route to push
+     * @param match OpenFlow fields to match on
+     * @param srcSwPort Source switch port for the first hop
+     * @param dstSwPort Destination switch port for final hop
+     * @param cookie The cookie to set in each flow_mod
+     * @param cntx The floodlight context
+     * @param reqeustFlowRemovedNotifn if set to true then the switch would
+     * send a flow mod removal notification when the flow mod expires
+     * @param doFlush if set to true then the flow mod would be immediately
+     *        written to the switch
+     * @param flowModCommand flow mod. command to use, e.g. OFFlowMod.OFPFC_ADD,
+     *        OFFlowMod.OFPFC_MODIFY etc.
+     * @return srcSwitchIincluded True if the source switch is included in this route
+     */
+    @LogMessageDocs({
+        @LogMessageDoc(level="WARN",
+            message="Unable to push route, switch at DPID {dpid} not available",
+            explanation="A switch along the calculated path for the " +
+                        "flow has disconnected.",
+            recommendation=LogMessageDoc.CHECK_SWITCH),
+        @LogMessageDoc(level="ERROR",
+            message="Failure writing flow mod",
+            explanation="An I/O error occurred while writing a " +
+                        "flow modification to a switch",
+            recommendation=LogMessageDoc.CHECK_SWITCH)            
+    })
+    public boolean pushRoute(Route route, OFMatch match, 
+                             Integer wildcard_hints,
+                             OFPacketIn pi,
+                             long pinSwitch,
+                             long cookie, 
+                             FloodlightContext cntx,
+                             boolean reqeustFlowRemovedNotifn,
+                             boolean doFlush,
+                             short   flowModCommand) {
+
+        boolean srcSwitchIncluded = false;
+        OFFlowMod fm =
+                (OFFlowMod) floodlightProvider.getOFMessageFactory()
+                                              .getMessage(OFType.FLOW_MOD);
+        OFActionOutput action = new OFActionOutput();
+        action.setMaxLength((short)0xffff);
+        List<OFAction> actions = new ArrayList<OFAction>();
+        actions.add(action);
+
+        fm.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
+            .setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT)
+            .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+            .setCookie(cookie)
+            .setCommand(flowModCommand)
+            .setMatch(match)
+            .setActions(actions)
+            .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
+
+        List<NodePortTuple> switchPortList = route.getPath();
+
+        for (int indx = switchPortList.size()-1; indx > 0; indx -= 2) {
+            // indx and indx-1 will always have the same switch DPID.
+            long switchDPID = switchPortList.get(indx).getNodeId();
+            IOFSwitch sw = floodlightProvider.getSwitches().get(switchDPID);
+            if (sw == null) {
+                if (log.isWarnEnabled()) {
+                    log.warn("Unable to push route, switch at DPID {} " +
+                            "not available", switchDPID);
+                }
+                return srcSwitchIncluded;
+            }
+
+            // set the match.
+            fm.setMatch(wildcard(match, sw, wildcard_hints));
+
+            // set buffer id if it is the source switch
+            if (1 == indx) {
+                // Set the flag to request flow-mod removal notifications only for the
+                // source switch. The removal message is used to maintain the flow
+                // cache. Don't set the flag for ARP messages - TODO generalize check
+                if ((reqeustFlowRemovedNotifn)
+                        && (match.getDataLayerType() != Ethernet.TYPE_ARP)) {
+                    fm.setFlags(OFFlowMod.OFPFF_SEND_FLOW_REM);
+                    match.setWildcards(fm.getMatch().getWildcards());
+                }
+            }
+
+            short outPort = switchPortList.get(indx).getPortId();
+            short inPort = switchPortList.get(indx-1).getPortId();
+            // set input and output ports on the switch
+            fm.getMatch().setInputPort(inPort);
+            ((OFActionOutput)fm.getActions().get(0)).setPort(outPort);
+
+            try {
+                counterStore.updatePktOutFMCounterStore(sw, fm);
+                if (log.isTraceEnabled()) {
+                    log.trace("Pushing Route flowmod routeIndx={} " + 
+                            "sw={} inPort={} outPort={}",
+                            new Object[] {indx,
+                                          sw,
+                                          fm.getMatch().getInputPort(),
+                                          outPort });
+                }
+                messageDamper.write(sw, fm, cntx);
+                if (doFlush) {
+                    sw.flush();
+                }
+
+                // Push the packet out the source switch
+                if (sw.getId() == pinSwitch) {
+                    // TODO: Instead of doing a packetOut here we could also 
+                    // send a flowMod with bufferId set.... 
+                    pushPacket(sw, match, pi, outPort, cntx);
+                    srcSwitchIncluded = true;
+                }
+            } catch (IOException e) {
+                log.error("Failure writing flow mod", e);
+            }
+
+            try {
+                fm = fm.clone();
+            } catch (CloneNotSupportedException e) {
+                log.error("Failure cloning flow mod", e);
+            }
+        }
+
+        return srcSwitchIncluded;
+    }
+
+    protected OFMatch wildcard(OFMatch match, IOFSwitch sw,
+                               Integer wildcard_hints) {
+        if (wildcard_hints != null) {
+            return match.clone().setWildcards(wildcard_hints.intValue());
+        }
+        return match.clone();
+    }
+    
+    /**
+     * Pushes a packet-out to a switch. If bufferId != BUFFER_ID_NONE we 
+     * assume that the packetOut switch is the same as the packetIn switch
+     * and we will use the bufferId 
+     * Caller needs to make sure that inPort and outPort differs
+     * @param packet    packet data to send
+     * @param sw        switch from which packet-out is sent
+     * @param bufferId  bufferId
+     * @param inPort    input port
+     * @param outPort   output port
+     * @param cntx      context of the packet
+     * @param flush     force to flush the packet.
+     */
+    @LogMessageDocs({
+        @LogMessageDoc(level="ERROR",
+            message="BufferId is not and packet data is null. " +
+                    "Cannot send packetOut. " +
+                    "srcSwitch={dpid} inPort={port} outPort={port}",
+            explanation="The switch send a malformed packet-in." +
+                        "The packet will be dropped",
+            recommendation=LogMessageDoc.REPORT_SWITCH_BUG),
+        @LogMessageDoc(level="ERROR",
+            message="Failure writing packet out",
+            explanation="An I/O error occurred while writing a " +
+                    "packet out to a switch",
+            recommendation=LogMessageDoc.CHECK_SWITCH)            
+    })
+    public void pushPacket(IPacket packet, 
+                           IOFSwitch sw,
+                           int bufferId,
+                           short inPort,
+                           short outPort, 
+                           FloodlightContext cntx,
+                           boolean flush) {
+        
+        
+        if (log.isTraceEnabled()) {
+            log.trace("PacketOut srcSwitch={} inPort={} outPort={}", 
+                      new Object[] {sw, inPort, outPort});
+        }
+
+        OFPacketOut po =
+                (OFPacketOut) floodlightProvider.getOFMessageFactory()
+                                                .getMessage(OFType.PACKET_OUT);
+
+        // set actions
+        List<OFAction> actions = new ArrayList<OFAction>();
+        actions.add(new OFActionOutput(outPort, (short) 0xffff));
+
+        po.setActions(actions)
+          .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
+        short poLength =
+                (short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH);
+
+        // set buffer_id, in_port
+        po.setBufferId(bufferId);
+        po.setInPort(inPort);
+
+        // set data - only if buffer_id == -1
+        if (po.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
+            if (packet == null) {
+                log.error("BufferId is not set and packet data is null. " +
+                          "Cannot send packetOut. " +
+                        "srcSwitch={} inPort={} outPort={}",
+                        new Object[] {sw, inPort, outPort});
+                return;
+            }
+            byte[] packetData = packet.serialize();
+            poLength += packetData.length;
+            po.setPacketData(packetData);
+        }
+
+        po.setLength(poLength);
+
+        try {
+            counterStore.updatePktOutFMCounterStore(sw, po);
+            messageDamper.write(sw, po, cntx, flush);
+        } catch (IOException e) {
+            log.error("Failure writing packet out", e);
+        }
+    }
+
+    /**
+     * Pushes a packet-out to a switch.  The assumption here is that
+     * the packet-in was also generated from the same switch.  Thus, if the input
+     * port of the packet-in and the outport are the same, the function will not 
+     * push the packet-out.
+     * @param sw        switch that generated the packet-in, and from which packet-out is sent
+     * @param match     OFmatch
+     * @param pi        packet-in
+     * @param outport   output port
+     * @param cntx      context of the packet
+     */
+    protected void pushPacket(IOFSwitch sw, OFMatch match, OFPacketIn pi, 
+                           short outport, FloodlightContext cntx) {
+
+        if (pi == null) {
+            return;
+        } else if (pi.getInPort() == outport){
+            log.warn("Packet out not sent as the outport matches inport. {}",
+                     pi);
+            return;
+        }
+
+        // The assumption here is (sw) is the switch that generated the 
+        // packet-in. If the input port is the same as output port, then
+        // the packet-out should be ignored.
+        if (pi.getInPort() == outport) {
+            if (log.isDebugEnabled()) {
+                log.debug("Attempting to do packet-out to the same " + 
+                          "interface as packet-in. Dropping packet. " + 
+                          " SrcSwitch={}, match = {}, pi={}", 
+                          new Object[]{sw, match, pi});
+                return;
+            }
+        }
+
+        if (log.isTraceEnabled()) {
+            log.trace("PacketOut srcSwitch={} match={} pi={}", 
+                      new Object[] {sw, match, pi});
+        }
+
+        OFPacketOut po =
+                (OFPacketOut) floodlightProvider.getOFMessageFactory()
+                                                .getMessage(OFType.PACKET_OUT);
+
+        // set actions
+        List<OFAction> actions = new ArrayList<OFAction>();
+        actions.add(new OFActionOutput(outport, (short) 0xffff));
+
+        po.setActions(actions)
+          .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
+        short poLength =
+                (short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH);
+
+        // If the switch doens't support buffering set the buffer id to be none
+        // otherwise it'll be the the buffer id of the PacketIn
+        if (sw.getBuffers() == 0) {
+            // We set the PI buffer id here so we don't have to check again below
+            pi.setBufferId(OFPacketOut.BUFFER_ID_NONE);
+            po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
+        } else {
+            po.setBufferId(pi.getBufferId());
+        }
+
+        po.setInPort(pi.getInPort());
+
+        // If the buffer id is none or the switch doesn's support buffering
+        // we send the data with the packet out
+        if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
+            byte[] packetData = pi.getPacketData();
+            poLength += packetData.length;
+            po.setPacketData(packetData);
+        }
+
+        po.setLength(poLength);
+
+        try {
+            counterStore.updatePktOutFMCounterStore(sw, po);
+            messageDamper.write(sw, po, cntx);
+        } catch (IOException e) {
+            log.error("Failure writing packet out", e);
+        }
+    }
+
+    
+    /**
+     * Write packetout message to sw with output actions to one or more
+     * output ports with inPort/outPorts passed in.
+     * @param packetData
+     * @param sw
+     * @param inPort
+     * @param ports
+     * @param cntx
+     */
+    public void packetOutMultiPort(byte[] packetData,
+                                   IOFSwitch sw,
+                                   short inPort,
+                                   Set<Integer> outPorts,
+                                   FloodlightContext cntx) {
+        //setting actions
+        List<OFAction> actions = new ArrayList<OFAction>();
+
+        Iterator<Integer> j = outPorts.iterator();
+
+        while (j.hasNext())
+        {
+            actions.add(new OFActionOutput(j.next().shortValue(), 
+                                           (short) 0));
+        }
+
+        OFPacketOut po = 
+                (OFPacketOut) floodlightProvider.getOFMessageFactory().
+                getMessage(OFType.PACKET_OUT);
+        po.setActions(actions);
+        po.setActionsLength((short) (OFActionOutput.MINIMUM_LENGTH * 
+                outPorts.size()));
+
+        // set buffer-id to BUFFER_ID_NONE, and set in-port to OFPP_NONE
+        po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
+        po.setInPort(inPort);
+
+        // data (note buffer_id is always BUFFER_ID_NONE) and length
+        short poLength = (short)(po.getActionsLength() + 
+                OFPacketOut.MINIMUM_LENGTH);
+        poLength += packetData.length;
+        po.setPacketData(packetData);
+        po.setLength(poLength);
+
+        try {
+            counterStore.updatePktOutFMCounterStore(sw, po);
+            if (log.isTraceEnabled()) {
+                log.trace("write broadcast packet on switch-id={} " + 
+                        "interfaces={} packet-out={}",
+                        new Object[] {sw.getId(), outPorts, po});
+            }
+            messageDamper.write(sw, po, cntx);
+
+        } catch (IOException e) {
+            log.error("Failure writing packet out", e);
+        }
+    }
+    
+    /** 
+     * @see packetOutMultiPort
+     * Accepts a PacketIn instead of raw packet data. Note that the inPort
+     * and switch can be different than the packet in switch/port
+     */
+    public void packetOutMultiPort(OFPacketIn pi,
+                                   IOFSwitch sw,
+                                   short inPort,
+                                   Set<Integer> outPorts,
+                                   FloodlightContext cntx) {
+        packetOutMultiPort(pi.getPacketData(), sw, inPort, outPorts, cntx);
+    }
+    
+    /** 
+     * @see packetOutMultiPort
+     * Accepts an IPacket instead of raw packet data. Note that the inPort
+     * and switch can be different than the packet in switch/port
+     */
+    public void packetOutMultiPort(IPacket packet,
+                                   IOFSwitch sw,
+                                   short inPort,
+                                   Set<Integer> outPorts,
+                                   FloodlightContext cntx) {
+        packetOutMultiPort(packet.serialize(), sw, inPort, outPorts, cntx);
+    }
+
+    protected boolean isInBroadcastCache(IOFSwitch sw, OFPacketIn pi,
+                        FloodlightContext cntx) {
+        // Get the cluster id of the switch.
+        // Get the hash of the Ethernet packet.
+        if (sw == null) return true;  
+        
+        // If the feature is disabled, always return false;
+        if (!broadcastCacheFeature) return false;
+
+        Ethernet eth = 
+            IFloodlightProviderService.bcStore.get(cntx,
+                IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+        
+        Long broadcastHash;
+        broadcastHash = topology.getL2DomainId(sw.getId()) * prime1 +
+                        pi.getInPort() * prime2 + eth.hashCode();
+        if (broadcastCache.update(broadcastHash)) {
+            sw.updateBroadcastCache(broadcastHash, pi.getInPort());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    protected boolean isInSwitchBroadcastCache(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
+        if (sw == null) return true;
+        
+        // If the feature is disabled, always return false;
+        if (!broadcastCacheFeature) return false;
+
+        // Get the hash of the Ethernet packet.
+        Ethernet eth =
+                IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+
+        long hash =  pi.getInPort() * prime2 + eth.hashCode();
+
+        // some FORWARD_OR_FLOOD packets are unicast with unknown destination mac
+        return sw.updateBroadcastCache(hash, pi.getInPort());
+    }
+
+    @LogMessageDocs({
+        @LogMessageDoc(level="ERROR",
+            message="Failure writing deny flow mod",
+            explanation="An I/O error occurred while writing a " +
+                    "deny flow mod to a switch",
+            recommendation=LogMessageDoc.CHECK_SWITCH)            
+    })
+    public static boolean
+            blockHost(IFloodlightProviderService floodlightProvider,
+                      SwitchPort sw_tup, long host_mac,
+                      short hardTimeout, long cookie) {
+
+        if (sw_tup == null) {
+            return false;
+        }
+
+        IOFSwitch sw = 
+                floodlightProvider.getSwitches().get(sw_tup.getSwitchDPID());
+        if (sw == null) return false;
+        int inputPort = sw_tup.getPort();
+        log.debug("blockHost sw={} port={} mac={}",
+                  new Object[] { sw, sw_tup.getPort(), new Long(host_mac) });
+
+        // Create flow-mod based on packet-in and src-switch
+        OFFlowMod fm =
+                (OFFlowMod) floodlightProvider.getOFMessageFactory()
+                                              .getMessage(OFType.FLOW_MOD);
+        OFMatch match = new OFMatch();
+        List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to
+                                                            // drop
+        match.setDataLayerSource(Ethernet.toByteArray(host_mac))
+             .setInputPort((short)inputPort)
+             .setWildcards(OFMatch.OFPFW_ALL & ~OFMatch.OFPFW_DL_SRC
+                     & ~OFMatch.OFPFW_IN_PORT);
+        fm.setCookie(cookie)
+          .setHardTimeout((short) hardTimeout)
+          .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
+          .setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT)
+          .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+          .setMatch(match)
+          .setActions(actions)
+          .setLengthU(OFFlowMod.MINIMUM_LENGTH); // +OFActionOutput.MINIMUM_LENGTH);
+
+        try {
+            log.debug("write drop flow-mod sw={} match={} flow-mod={}",
+                      new Object[] { sw, match, fm });
+            // TODO: can't use the message damper sine this method is static
+            sw.write(fm, null);
+        } catch (IOException e) {
+            log.error("Failure writing deny flow mod", e);
+            return false;
+        }
+        return true;
+
+    }
+
+    @Override
+    public void deviceAdded(IDevice device) {
+        // NOOP
+    }
+
+    @Override
+    public void deviceRemoved(IDevice device) {
+        // NOOP
+    }
+
+    @Override
+    public void deviceMoved(IDevice device) {
+    }
+
+    @Override
+    public void deviceIPV4AddrChanged(IDevice device) {
+
+    }
+
+    @Override
+    public void deviceVlanChanged(IDevice device) {
+
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        return (type.equals(OFType.PACKET_IN) && 
+                (name.equals("topology") || 
+                 name.equals("devicemanager")));
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return false;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java
new file mode 100644
index 0000000..ed72706
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java
@@ -0,0 +1,58 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.routing;
+
+import java.util.List;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.FloodlightContextStore;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+
+public interface IRoutingDecision {
+    public enum RoutingAction {
+        /*
+         * NONE:                    NO-OP, continue with the packet processing chain
+         * DROP:                    Drop this packet and this flow
+         * FORWARD:                 Forward this packet, and this flow, to the first (and only device) in getDestinationDevices(),
+         *                          if the destination is not known at this time, initiate a discovery action for it (e.g. ARP)
+         * FORWARD_OR_FLOOD:        Forward this packet, and this flow, to the first (and only device) in getDestinationDevices(),
+         *                          if the destination is not known at this time, flood this packet on the source switch
+         * BROADCAST:               Broadcast this packet on all links                         
+         * MULTICAST:               Multicast this packet to all the interfaces and devices attached
+         */
+        NONE, DROP, FORWARD, FORWARD_OR_FLOOD, BROADCAST, MULTICAST
+    }
+    
+    public static final FloodlightContextStore<IRoutingDecision> rtStore =
+        new FloodlightContextStore<IRoutingDecision>();
+    public static final String CONTEXT_DECISION = 
+            "net.floodlightcontroller.routing.decision";
+
+    public void addToContext(FloodlightContext cntx);
+    public RoutingAction getRoutingAction();
+    public void setRoutingAction(RoutingAction action);
+    public SwitchPort getSourcePort();
+    public IDevice getSourceDevice();
+    public List<IDevice> getDestinationDevices();
+    public void addDestinationDevice(IDevice d);
+    public List<SwitchPort> getMulticastInterfaces();
+    public void setMulticastInterfaces(List<SwitchPort> lspt);
+    public Integer getWildcards();
+    public void setWildcards(Integer wildcards);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingService.java
new file mode 100644
index 0000000..fcd70ad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingService.java
@@ -0,0 +1,49 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.routing;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.routing.Route;
+
+public interface IRoutingService extends IFloodlightService {
+
+    /** Provides a route between src and dst that allows tunnels. */
+    public Route getRoute(long src, long dst);
+
+    /** Provides a route between src and dst, with option to allow or 
+     *  not allow tunnels in the path.*/
+    public Route getRoute(long src, long dst, boolean tunnelEnabled);
+
+
+    public Route getRoute(long srcId, short srcPort, 
+                             long dstId, short dstPort);
+
+    public Route getRoute(long srcId, short srcPort, 
+                             long dstId, short dstPort, 
+                             boolean tunnelEnabled);
+
+    /** Check if a route exists between src and dst, including tunnel links
+     *  in the path.
+     */
+    public boolean routeExists(long src, long dst);
+
+    /** Check if a route exists between src and dst, with option to have
+     *  or not have tunnels as part of the path.
+     */
+    public boolean routeExists(long src, long dst, boolean tunnelEnabled);
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Link.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Link.java
new file mode 100755
index 0000000..7958596
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Link.java
@@ -0,0 +1,122 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.routing;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.core.web.serializers.UShortSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+
+public class Link {
+    private long src;
+    private short srcPort;
+    private long dst;
+    private short dstPort;
+
+
+    public Link(long srcId, short srcPort, long dstId, short dstPort) {
+        this.src = srcId;
+        this.srcPort = srcPort;
+        this.dst = dstId;
+        this.dstPort = dstPort;
+    }
+
+    // Convenience method
+    public Link(long srcId, int srcPort, long dstId, int dstPort) {
+        this.src = srcId;
+        this.srcPort = (short) srcPort;
+        this.dst = dstId;
+        this.dstPort = (short) dstPort;
+    }
+
+    @JsonProperty("src-switch")
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getSrc() {
+        return src;
+    }
+
+    @JsonProperty("src-port")
+    @JsonSerialize(using=UShortSerializer.class)
+    public short getSrcPort() {
+        return srcPort;
+    }
+
+    @JsonProperty("dst-switch")
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getDst() {
+        return dst;
+    }
+    @JsonProperty("dst-port")
+    @JsonSerialize(using=UShortSerializer.class)
+    public short getDstPort() {
+        return dstPort;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (int) (dst ^ (dst >>> 32));
+        result = prime * result + dstPort;
+        result = prime * result + (int) (src ^ (src >>> 32));
+        result = prime * result + srcPort;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        Link other = (Link) obj;
+        if (dst != other.dst)
+            return false;
+        if (dstPort != other.dstPort)
+            return false;
+        if (src != other.src)
+            return false;
+        if (srcPort != other.srcPort)
+            return false;
+        return true;
+    }
+
+
+    @Override
+    public String toString() {
+        return "Link [src=" + HexString.toHexString(this.src) 
+                + " outPort="
+                + (srcPort & 0xffff)
+                + ", dst=" + HexString.toHexString(this.dst)
+                + ", inPort="
+                + (dstPort & 0xffff)
+                + "]";
+    }
+    
+    public String toKeyString() {
+    	return (HexString.toHexString(this.src) + "|" +
+    			(this.srcPort & 0xffff) + "|" +
+    			HexString.toHexString(this.dst) + "|" +
+    		    (this.dstPort & 0xffff) );
+    }
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Route.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Route.java
new file mode 100755
index 0000000..211a924
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Route.java
@@ -0,0 +1,117 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.routing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.floodlightcontroller.topology.NodePortTuple;
+
+/**
+ * Represents a route between two switches
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class Route implements Comparable<Route> {
+    protected RouteId id;
+    protected List<NodePortTuple> switchPorts;
+
+    public Route(RouteId id, List<NodePortTuple> switchPorts) {
+        super();
+        this.id = id;
+        this.switchPorts = switchPorts;
+    }
+
+    public Route(Long src, Long dst) {
+        super();
+        this.id = new RouteId(src, dst);
+        this.switchPorts = new ArrayList<NodePortTuple>();
+    }
+
+    /**
+     * @return the id
+     */
+    public RouteId getId() {
+        return id;
+    }
+
+    /**
+     * @param id the id to set
+     */
+    public void setId(RouteId id) {
+        this.id = id;
+    }
+
+    /**
+     * @return the path
+     */
+    public List<NodePortTuple> getPath() {
+        return switchPorts;
+    }
+
+    /**
+     * @param path the path to set
+     */
+    public void setPath(List<NodePortTuple> switchPorts) {
+        this.switchPorts = switchPorts;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 5791;
+        int result = 1;
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((switchPorts == null) ? 0 : switchPorts.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        Route other = (Route) obj;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (switchPorts == null) {
+            if (other.switchPorts != null)
+                return false;
+        } else if (!switchPorts.equals(other.switchPorts))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "Route [id=" + id + ", switchPorts=" + switchPorts + "]";
+    }
+
+    /**
+     * Compares the path lengths between Routes.
+     */
+    @Override
+    public int compareTo(Route o) {
+        return ((Integer)switchPorts.size()).compareTo(o.switchPorts.size());
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RouteId.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RouteId.java
new file mode 100755
index 0000000..a550961
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RouteId.java
@@ -0,0 +1,102 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.routing;
+
+import org.openflow.util.HexString;
+
+/**
+ * Stores the endpoints of a route, in this case datapath ids
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class RouteId implements Cloneable, Comparable<RouteId> {
+    protected Long src;
+    protected Long dst;
+
+    public RouteId(Long src, Long dst) {
+        super();
+        this.src = src;
+        this.dst = dst;
+    }
+
+    public Long getSrc() {
+        return src;
+    }
+
+    public void setSrc(Long src) {
+        this.src = src;
+    }
+
+    public Long getDst() {
+        return dst;
+    }
+
+    public void setDst(Long dst) {
+        this.dst = dst;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 2417;
+        int result = 1;
+        result = prime * result + ((dst == null) ? 0 : dst.hashCode());
+        result = prime * result + ((src == null) ? 0 : src.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        RouteId other = (RouteId) obj;
+        if (dst == null) {
+            if (other.dst != null)
+                return false;
+        } else if (!dst.equals(other.dst))
+            return false;
+        if (src == null) {
+            if (other.src != null)
+                return false;
+        } else if (!src.equals(other.src))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "RouteId [src=" + HexString.toHexString(this.src) + " dst="
+                + HexString.toHexString(this.dst) + "]";
+    }
+
+    @Override
+    protected Object clone() throws CloneNotSupportedException {
+        return super.clone();
+    }
+
+    @Override
+    public int compareTo(RouteId o) {
+        int result = src.compareTo(o.getSrc());
+        if (result != 0)
+            return result;
+        return dst.compareTo(o.getDst());
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java
new file mode 100644
index 0000000..5b32b23
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java
@@ -0,0 +1,97 @@
+package net.floodlightcontroller.routing;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+
+
+public class RoutingDecision implements IRoutingDecision {
+
+    protected RoutingAction action;
+    protected Integer wildcards;
+    protected SwitchPort srcPort;
+    protected IDevice srcDevice;
+    protected List<IDevice> destDevices;
+    protected List<SwitchPort> broadcastIntertfaces;
+
+    public RoutingDecision(long swDipd,
+                                  short inPort,
+                                  IDevice srcDevice,
+                                  RoutingAction action) {
+        this.srcPort = new SwitchPort(swDipd, inPort);
+        this.srcDevice = srcDevice;
+        this.destDevices = 
+                Collections.synchronizedList(new ArrayList<IDevice>());
+        this.broadcastIntertfaces = 
+                Collections.synchronizedList(new ArrayList<SwitchPort>());
+        this.action = action;
+        this.wildcards = null;
+    }
+    
+    @Override
+    public RoutingAction getRoutingAction() {
+        return this.action;
+    }
+    
+    @Override
+    public void setRoutingAction(RoutingAction action) {
+        this.action = action;
+    }
+    
+    @Override
+    public SwitchPort getSourcePort() {
+        return this.srcPort;
+    }
+    
+    @Override
+    public IDevice getSourceDevice() {
+        return this.srcDevice;
+    }
+    
+    @Override
+    public List<IDevice> getDestinationDevices() {
+        return this.destDevices;
+    }
+    
+    @Override
+    public void addDestinationDevice(IDevice d) {
+        if (!destDevices.contains(d)) {
+            destDevices.add(d);
+        }
+    }
+    
+    @Override
+    public void setMulticastInterfaces(List<SwitchPort> lspt) {
+        this.broadcastIntertfaces = lspt;
+    }
+    
+    @Override
+    public List<SwitchPort> getMulticastInterfaces() {
+        return this.broadcastIntertfaces;
+    }
+    
+    @Override
+    public Integer getWildcards() {
+        return this.wildcards;
+    }
+    
+    @Override
+    public void setWildcards(Integer wildcards) {
+        this.wildcards = wildcards;
+    }
+   
+    @Override
+    public void addToContext(FloodlightContext cntx) {
+        rtStore.put(cntx, IRoutingDecision.CONTEXT_DECISION, this);
+    }
+    
+    public String toString() {
+        return "action " + action +
+               " wildcard " +
+               ((wildcards == null) ? null : "0x"+Integer.toHexString(wildcards.intValue()));
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java
new file mode 100644
index 0000000..66e02dd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java
@@ -0,0 +1,44 @@
+package net.floodlightcontroller.staticflowentry;
+
+import java.util.Map;
+
+import org.openflow.protocol.OFFlowMod;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IStaticFlowEntryPusherService extends IFloodlightService {
+    /**
+     * Adds a static flow.
+     * @param name Name of the flow mod. Must be unique.
+     * @param fm The flow to push.
+     * @param swDpid The switch DPID to push it to, in 00:00:00:00:00:00:00:01 notation.
+     */
+    public void addFlow(String name, OFFlowMod fm, String swDpid);
+    
+    /**
+     * Deletes a static flow
+     * @param name The name of the static flow to delete.
+     */
+    public void deleteFlow(String name);
+    
+    /**
+     * Deletes all static flows for a practicular switch
+     * @param dpid The DPID of the switch to delete flows for.
+     */
+    public void deleteFlowsForSwitch(long dpid);
+    
+    /**
+     * Deletes all flows.
+     */
+    public void deleteAllFlows();
+    
+    /**
+     * Gets all list of all flows
+     */
+    public Map<String, Map<String, OFFlowMod>> getFlows();
+    
+    /**
+     * Gets a list of flows by switch
+     */
+    public Map<String, OFFlowMod> getFlows(String dpid);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java
new file mode 100644
index 0000000..ba28619
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java
@@ -0,0 +1,831 @@
+package net.floodlightcontroller.staticflowentry;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.util.AppCookie;
+import net.floodlightcontroller.packet.IPv4;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionDataLayerDestination;
+import org.openflow.protocol.action.OFActionDataLayerSource;
+import org.openflow.protocol.action.OFActionEnqueue;
+import org.openflow.protocol.action.OFActionNetworkLayerDestination;
+import org.openflow.protocol.action.OFActionNetworkLayerSource;
+import org.openflow.protocol.action.OFActionNetworkTypeOfService;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.protocol.action.OFActionStripVirtualLan;
+import org.openflow.protocol.action.OFActionTransportLayerDestination;
+import org.openflow.protocol.action.OFActionTransportLayerSource;
+import org.openflow.protocol.action.OFActionVirtualLanIdentifier;
+import org.openflow.protocol.action.OFActionVirtualLanPriorityCodePoint;
+import org.openflow.util.HexString;
+
+/**
+ * Represents static flow entries to be maintained by the controller on the 
+ * switches. 
+ */
+@LogMessageCategory("Static Flow Pusher")
+public class StaticFlowEntries {
+    protected static Logger log = LoggerFactory.getLogger(StaticFlowEntries.class);
+    
+    private static class SubActionStruct {
+        OFAction action;
+        int      len;
+    }
+    
+    private static byte[] zeroMac = new byte[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+    
+    /**
+     * This function generates a random hash for the bottom half of the cookie
+     * 
+     * @param fm
+     * @param userCookie
+     * @param name
+     * @return A cookie that encodes the application ID and a hash
+     */
+    public static long computeEntryCookie(OFFlowMod fm, int userCookie, String name) {
+        // flow-specific hash is next 20 bits LOOK! who knows if this 
+        int prime = 211;
+        int flowHash = 2311;
+        for (int i=0; i < name.length(); i++)
+            flowHash = flowHash * prime + (int)name.charAt(i);
+        
+        return AppCookie.makeCookie(StaticFlowEntryPusher.STATIC_FLOW_APP_ID, flowHash);
+    }
+    
+    /**
+     * Sets defaults for an OFFlowMod
+     * @param fm The OFFlowMod to set defaults for
+     * @param entryName The name of the entry. Used to compute the cookie.
+     */
+    public static void initDefaultFlowMod(OFFlowMod fm, String entryName) {
+        fm.setIdleTimeout((short) 0);   // infinite
+        fm.setHardTimeout((short) 0);   // infinite
+        fm.setBufferId(OFPacketOut.BUFFER_ID_NONE);
+        fm.setCommand((short) 0);
+        fm.setFlags((short) 0);
+        fm.setOutPort(OFPort.OFPP_NONE.getValue());
+        fm.setCookie(computeEntryCookie(fm, 0, entryName));  
+        fm.setPriority(Short.MAX_VALUE);
+    }
+    
+    /**
+     * Gets the entry name of a flow mod
+     * @param fmJson The OFFlowMod in a JSON representation
+     * @return The name of the OFFlowMod, null if not found
+     * @throws IOException If there was an error parsing the JSON
+     */
+    public static String getEntryNameFromJson(String fmJson) throws IOException{
+        MappingJsonFactory f = new MappingJsonFactory();
+        JsonParser jp;
+        
+        try {
+            jp = f.createJsonParser(fmJson);
+        } catch (JsonParseException e) {
+            throw new IOException(e);
+        }
+        
+        jp.nextToken();
+        if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
+            throw new IOException("Expected START_OBJECT");
+        }
+        
+        while (jp.nextToken() != JsonToken.END_OBJECT) {
+            if (jp.getCurrentToken() != JsonToken.FIELD_NAME) {
+                throw new IOException("Expected FIELD_NAME");
+            }
+            
+            String n = jp.getCurrentName();
+            jp.nextToken();
+            if (jp.getText().equals("")) 
+                continue;
+            
+            if (n == "name")
+                return jp.getText();
+        }
+        
+        return null;
+    }
+    
+    /**
+     * Parses an OFFlowMod (and it's inner OFMatch) to the storage entry format.
+     * @param fm The FlowMod to parse
+     * @param sw The switch the FlowMod is going to be installed on
+     * @param name The name of this static flow entry
+     * @return A Map representation of the storage entry 
+     */
+    public static Map<String, Object> flowModToStorageEntry(OFFlowMod fm, String sw, String name) {
+        Map<String, Object> entry = new HashMap<String, Object>();
+        OFMatch match = fm.getMatch();
+        entry.put(StaticFlowEntryPusher.COLUMN_NAME, name);
+        entry.put(StaticFlowEntryPusher.COLUMN_SWITCH, sw);
+        entry.put(StaticFlowEntryPusher.COLUMN_ACTIVE, Boolean.toString(true));
+        entry.put(StaticFlowEntryPusher.COLUMN_PRIORITY, Short.toString(fm.getPriority()));
+        entry.put(StaticFlowEntryPusher.COLUMN_WILDCARD, Integer.toString(match.getWildcards()));
+        
+        if ((fm.getActions() != null) && (fm.getActions().size() > 0))
+        	entry.put(StaticFlowEntryPusher.COLUMN_ACTIONS, StaticFlowEntries.flowModActionsToString(fm.getActions()));
+        
+        if (match.getInputPort() != 0)
+        	entry.put(StaticFlowEntryPusher.COLUMN_IN_PORT, Short.toString(match.getInputPort()));
+        
+        if (!Arrays.equals(match.getDataLayerSource(), zeroMac))
+        	entry.put(StaticFlowEntryPusher.COLUMN_DL_SRC, HexString.toHexString(match.getDataLayerSource()));
+
+        if (!Arrays.equals(match.getDataLayerDestination(), zeroMac))
+        	entry.put(StaticFlowEntryPusher.COLUMN_DL_DST, HexString.toHexString(match.getDataLayerDestination()));
+        
+        if (match.getDataLayerVirtualLan() != -1)
+        	entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN, Short.toString(match.getDataLayerVirtualLan()));
+        
+        if (match.getDataLayerVirtualLanPriorityCodePoint() != 0)
+        	entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN_PCP, Short.toString(match.getDataLayerVirtualLanPriorityCodePoint()));
+        
+        if (match.getDataLayerType() != 0)
+        	entry.put(StaticFlowEntryPusher.COLUMN_DL_TYPE, Short.toString(match.getDataLayerType()));
+        
+        if (match.getNetworkTypeOfService() != 0)
+        	entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, Short.toString(match.getNetworkTypeOfService()));
+        
+        if (match.getNetworkProtocol() != 0)
+        	entry.put(StaticFlowEntryPusher.COLUMN_NW_PROTO, Short.toString(match.getNetworkProtocol()));
+        
+        if (match.getNetworkSource() != 0)
+        	entry.put(StaticFlowEntryPusher.COLUMN_NW_SRC, IPv4.fromIPv4Address(match.getNetworkSource()));
+        
+        if (match.getNetworkDestination() != 0)
+        	entry.put(StaticFlowEntryPusher.COLUMN_NW_DST, IPv4.fromIPv4Address(match.getNetworkDestination()));
+        
+        if (match.getTransportSource() != 0)
+        	entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, Short.toString(match.getTransportSource()));
+        
+        if (match.getTransportDestination() != 0)
+        	entry.put(StaticFlowEntryPusher.COLUMN_TP_DST, Short.toString(match.getTransportDestination()));
+        
+        return entry;
+    }
+    
+    /**
+     * Returns a String representation of all the openflow actions.
+     * @param fmActions A list of OFActions to encode into one string
+     * @return A string of the actions encoded for our database
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Could not decode action {action}",
+            explanation="A static flow entry contained an invalid action",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    private static String flowModActionsToString(List<OFAction> fmActions) {
+        StringBuilder sb = new StringBuilder();
+        for (OFAction a : fmActions) {
+            if (sb.length() > 0) {
+                sb.append(',');
+            }
+            switch(a.getType()) {
+                case OUTPUT:
+                    sb.append("output=" + Short.toString(((OFActionOutput)a).getPort()));
+                    break;
+                case OPAQUE_ENQUEUE:
+                    int queue = ((OFActionEnqueue)a).getQueueId();
+                    short port = ((OFActionEnqueue)a).getPort();
+                    sb.append("enqueue=" + Short.toString(port) + ":0x" + String.format("%02x", queue));
+                    break;
+                case STRIP_VLAN:
+                    sb.append("strip-vlan");
+                    break;
+                case SET_VLAN_ID:
+                    sb.append("set-vlan-id=" + 
+                        Short.toString(((OFActionVirtualLanIdentifier)a).getVirtualLanIdentifier()));
+                    break;
+                case SET_VLAN_PCP:
+                    sb.append("set-vlan-priority=" +
+                        Byte.toString(((OFActionVirtualLanPriorityCodePoint)a).getVirtualLanPriorityCodePoint()));
+                    break;
+                case SET_DL_SRC:
+                    sb.append("set-src-mac=" + 
+                        HexString.toHexString(((OFActionDataLayerSource)a).getDataLayerAddress()));
+                    break;
+                case SET_DL_DST:
+                    sb.append("set-dst-mac=" + 
+                        HexString.toHexString(((OFActionDataLayerDestination)a).getDataLayerAddress()));
+                    break;
+                case SET_NW_TOS:
+                    sb.append("set-tos-bits=" +
+                        Byte.toString(((OFActionNetworkTypeOfService)a).getNetworkTypeOfService()));
+                    break;
+                case SET_NW_SRC:
+                    sb.append("set-src-ip=" +
+                        IPv4.fromIPv4Address(((OFActionNetworkLayerSource)a).getNetworkAddress()));
+                    break;
+                case SET_NW_DST:
+                    sb.append("set-dst-ip=" +
+                        IPv4.fromIPv4Address(((OFActionNetworkLayerDestination)a).getNetworkAddress()));
+                    break;
+                case SET_TP_SRC:
+                    sb.append("set-src-port=" +
+                        Short.toString(((OFActionTransportLayerSource)a).getTransportPort()));
+                    break;
+                case SET_TP_DST:
+                    sb.append("set-dst-port=" +
+                        Short.toString(((OFActionTransportLayerDestination)a).getTransportPort()));
+                    break;
+                default:
+                    log.error("Could not decode action: {}", a);
+                    break;
+            }
+                
+        }
+        return sb.toString();
+    }
+    
+    /**
+     * Turns a JSON formatted Static Flow Pusher string into a storage entry
+     * Expects a string in JSON along the lines of:
+     *        {
+     *            "switch":       "AA:BB:CC:DD:EE:FF:00:11",
+     *            "name":         "flow-mod-1",
+     *            "cookie":       "0",
+     *            "priority":     "32768",
+     *            "ingress-port": "1",
+     *            "actions":      "output=2",
+     *        }
+     * @param fmJson The JSON formatted static flow pusher entry
+     * @return The map of the storage entry
+     * @throws IOException If there was an error parsing the JSON
+     */
+    public static Map<String, Object> jsonToStorageEntry(String fmJson) throws IOException {
+        Map<String, Object> entry = new HashMap<String, Object>();
+        MappingJsonFactory f = new MappingJsonFactory();
+        JsonParser jp;
+        
+        try {
+            jp = f.createJsonParser(fmJson);
+        } catch (JsonParseException e) {
+            throw new IOException(e);
+        }
+        
+        jp.nextToken();
+        if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
+            throw new IOException("Expected START_OBJECT");
+        }
+        
+        while (jp.nextToken() != JsonToken.END_OBJECT) {
+            if (jp.getCurrentToken() != JsonToken.FIELD_NAME) {
+                throw new IOException("Expected FIELD_NAME");
+            }
+            
+            String n = jp.getCurrentName();
+            jp.nextToken();
+            if (jp.getText().equals("")) 
+                continue;
+            
+            if (n == "name")
+                entry.put(StaticFlowEntryPusher.COLUMN_NAME, jp.getText());
+            else if (n == "switch")
+                entry.put(StaticFlowEntryPusher.COLUMN_SWITCH, jp.getText());
+            else if (n == "actions")
+                entry.put(StaticFlowEntryPusher.COLUMN_ACTIONS, jp.getText());
+            else if (n == "priority")
+                entry.put(StaticFlowEntryPusher.COLUMN_PRIORITY, jp.getText());
+            else if (n == "active")
+                entry.put(StaticFlowEntryPusher.COLUMN_ACTIVE, jp.getText());
+            else if (n == "wildcards")
+                entry.put(StaticFlowEntryPusher.COLUMN_WILDCARD, jp.getText());
+            else if (n == "ingress-port")
+                entry.put(StaticFlowEntryPusher.COLUMN_IN_PORT, jp.getText());
+            else if (n == "src-mac")
+                entry.put(StaticFlowEntryPusher.COLUMN_DL_SRC, jp.getText());
+            else if (n == "dst-mac")
+                entry.put(StaticFlowEntryPusher.COLUMN_DL_DST, jp.getText());
+            else if (n == "vlan-id")
+                entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN, jp.getText());
+            else if (n == "vlan-priority")
+                entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN_PCP, jp.getText());
+            else if (n == "ether-type")
+                entry.put(StaticFlowEntryPusher.COLUMN_DL_TYPE, jp.getText());
+            else if (n == "tos-bits")
+                entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, jp.getText());
+            else if (n == "protocol")
+                entry.put(StaticFlowEntryPusher.COLUMN_NW_PROTO, jp.getText());
+            else if (n == "src-ip")
+                entry.put(StaticFlowEntryPusher.COLUMN_NW_SRC, jp.getText());
+            else if (n == "dst-ip")
+                entry.put(StaticFlowEntryPusher.COLUMN_NW_DST, jp.getText());
+            else if (n == "src-port")
+                entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, jp.getText());
+            else if (n == "dst-port")
+                entry.put(StaticFlowEntryPusher.COLUMN_TP_DST, jp.getText());
+        }
+        
+        return entry;
+    }
+    
+    /**
+     * Parses OFFlowMod actions from strings.
+     * @param flowMod The OFFlowMod to set the actions for
+     * @param actionstr The string containing all the actions
+     * @param log A logger to log for errors.
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Unexpected action '{action}', '{subaction}'",
+            explanation="A static flow entry contained an invalid action",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    public static void parseActionString(OFFlowMod flowMod, String actionstr, Logger log) {
+        List<OFAction> actions = new LinkedList<OFAction>();
+        int actionsLength = 0;
+        if (actionstr != null) {
+            actionstr = actionstr.toLowerCase();
+            for (String subaction : actionstr.split(",")) {
+                String action = subaction.split("[=:]")[0];
+                SubActionStruct subaction_struct = null;
+                
+                if (action.equals("output")) {
+                    subaction_struct = StaticFlowEntries.decode_output(subaction, log);
+                }
+                else if (action.equals("enqueue")) {
+                    subaction_struct = decode_enqueue(subaction, log);
+                }
+                else if (action.equals("strip-vlan")) {
+                    subaction_struct = decode_strip_vlan(subaction, log);
+                }
+                else if (action.equals("set-vlan-id")) {
+                    subaction_struct = decode_set_vlan_id(subaction, log);
+                }
+                else if (action.equals("set-vlan-priority")) {
+                    subaction_struct = decode_set_vlan_priority(subaction, log);
+                }
+                else if (action.equals("set-src-mac")) {
+                    subaction_struct = decode_set_src_mac(subaction, log);
+                }
+                else if (action.equals("set-dst-mac")) {
+                    subaction_struct = decode_set_dst_mac(subaction, log);
+                }
+                else if (action.equals("set-tos-bits")) {
+                    subaction_struct = decode_set_tos_bits(subaction, log);
+                }
+                else if (action.equals("set-src-ip")) {
+                    subaction_struct = decode_set_src_ip(subaction, log);
+                }
+                else if (action.equals("set-dst-ip")) {
+                    subaction_struct = decode_set_dst_ip(subaction, log);
+                }
+                else if (action.equals("set-src-port")) {
+                    subaction_struct = decode_set_src_port(subaction, log);
+                }
+                else if (action.equals("set-dst-port")) {
+                    subaction_struct = decode_set_dst_port(subaction, log);
+                }
+                else {
+                    log.error("Unexpected action '{}', '{}'", action, subaction);
+                }
+                
+                if (subaction_struct != null) {
+                    actions.add(subaction_struct.action);
+                    actionsLength += subaction_struct.len;
+                }
+            }
+        }
+        log.debug("action {}", actions);
+        
+        flowMod.setActions(actions);
+        flowMod.setLengthU(OFFlowMod.MINIMUM_LENGTH + actionsLength);
+    } 
+    
+    @LogMessageDoc(level="ERROR",
+            message="Invalid subaction: '{subaction}'",
+            explanation="A static flow entry contained an invalid subaction",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    private static SubActionStruct decode_output(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n;
+        
+        n = Pattern.compile("output=(?:((?:0x)?\\d+)|(all)|(controller)|(local)|(ingress-port)|(normal)|(flood))").matcher(subaction);
+        if (n.matches()) {
+            OFActionOutput action = new OFActionOutput();
+            action.setMaxLength((short) Short.MAX_VALUE);
+            short port = OFPort.OFPP_NONE.getValue();
+            if (n.group(1) != null) {
+                try {
+                    port = get_short(n.group(1));
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid port in: '{}' (error ignored)", subaction);
+                    return null;
+                }
+            }
+            else if (n.group(2) != null)
+                port = OFPort.OFPP_ALL.getValue();
+            else if (n.group(3) != null)
+                port = OFPort.OFPP_CONTROLLER.getValue();
+            else if (n.group(4) != null)
+                port = OFPort.OFPP_LOCAL.getValue();
+            else if (n.group(5) != null)
+                port = OFPort.OFPP_IN_PORT.getValue();
+            else if (n.group(6) != null)
+                port = OFPort.OFPP_NORMAL.getValue();
+            else if (n.group(7) != null)
+                port = OFPort.OFPP_FLOOD.getValue();
+            action.setPort(port);
+            log.debug("action {}", action);
+            
+            sa = new SubActionStruct();
+            sa.action = action;
+            sa.len = OFActionOutput.MINIMUM_LENGTH;
+        }
+        else {
+            log.error("Invalid subaction: '{}'", subaction);
+            return null;
+        }
+        
+        return sa;
+    }
+    
+    private static SubActionStruct decode_enqueue(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n;
+        
+        n = Pattern.compile("enqueue=(?:((?:0x)?\\d+)\\:((?:0x)?\\d+))").matcher(subaction);
+        if (n.matches()) {
+            short portnum = 0;
+            if (n.group(1) != null) {
+                try {
+                    portnum = get_short(n.group(1));
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid port-num in: '{}' (error ignored)", subaction);
+                    return null;
+                }
+            }
+
+            int queueid = 0;
+            if (n.group(2) != null) {
+                try {
+                    queueid = get_int(n.group(2));
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid queue-id in: '{}' (error ignored)", subaction);
+                    return null;
+               }
+            }
+            
+            OFActionEnqueue action = new OFActionEnqueue();
+            action.setPort(portnum);
+            action.setQueueId(queueid);
+            log.debug("action {}", action);
+            
+            sa = new SubActionStruct();
+            sa.action = action;
+            sa.len = OFActionEnqueue.MINIMUM_LENGTH;
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+        
+        return sa;
+    }
+    
+    private static SubActionStruct decode_strip_vlan(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("strip-vlan").matcher(subaction);
+        
+        if (n.matches()) {
+            OFActionStripVirtualLan action = new OFActionStripVirtualLan();
+            log.debug("action {}", action);
+            
+            sa = new SubActionStruct();
+            sa.action = action;
+            sa.len = OFActionStripVirtualLan.MINIMUM_LENGTH;
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+    
+    private static SubActionStruct decode_set_vlan_id(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("set-vlan-id=((?:0x)?\\d+)").matcher(subaction);
+        
+        if (n.matches()) {            
+            if (n.group(1) != null) {
+                try {
+                    short vlanid = get_short(n.group(1));
+                    OFActionVirtualLanIdentifier action = new OFActionVirtualLanIdentifier();
+                    action.setVirtualLanIdentifier(vlanid);
+                    log.debug("  action {}", action);
+
+                    sa = new SubActionStruct();
+                    sa.action = action;
+                    sa.len = OFActionVirtualLanIdentifier.MINIMUM_LENGTH;
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid VLAN in: {} (error ignored)", subaction);
+                    return null;
+                }
+            }          
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+    
+    private static SubActionStruct decode_set_vlan_priority(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("set-vlan-priority=((?:0x)?\\d+)").matcher(subaction); 
+        
+        if (n.matches()) {            
+            if (n.group(1) != null) {
+                try {
+                    byte prior = get_byte(n.group(1));
+                    OFActionVirtualLanPriorityCodePoint action = new OFActionVirtualLanPriorityCodePoint();
+                    action.setVirtualLanPriorityCodePoint(prior);
+                    log.debug("  action {}", action);
+                    
+                    sa = new SubActionStruct();
+                    sa.action = action;
+                    sa.len = OFActionVirtualLanPriorityCodePoint.MINIMUM_LENGTH;
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid VLAN priority in: {} (error ignored)", subaction);
+                    return null;
+                }
+            }
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+    
+    private static SubActionStruct decode_set_src_mac(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("set-src-mac=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(subaction); 
+
+        if (n.matches()) {
+            byte[] macaddr = get_mac_addr(n, subaction, log);
+            if (macaddr != null) {
+                OFActionDataLayerSource action = new OFActionDataLayerSource();
+                action.setDataLayerAddress(macaddr);
+                log.debug("action {}", action);
+
+                sa = new SubActionStruct();
+                sa.action = action;
+                sa.len = OFActionDataLayerSource.MINIMUM_LENGTH;
+            }            
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+
+    private static SubActionStruct decode_set_dst_mac(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("set-dst-mac=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(subaction);
+        
+        if (n.matches()) {
+            byte[] macaddr = get_mac_addr(n, subaction, log);            
+            if (macaddr != null) {
+                OFActionDataLayerDestination action = new OFActionDataLayerDestination();
+                action.setDataLayerAddress(macaddr);
+                log.debug("  action {}", action);
+                
+                sa = new SubActionStruct();
+                sa.action = action;
+                sa.len = OFActionDataLayerDestination.MINIMUM_LENGTH;
+            }
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+    
+    private static SubActionStruct decode_set_tos_bits(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("set-tos-bits=((?:0x)?\\d+)").matcher(subaction); 
+
+        if (n.matches()) {
+            if (n.group(1) != null) {
+                try {
+                    byte tosbits = get_byte(n.group(1));
+                    OFActionNetworkTypeOfService action = new OFActionNetworkTypeOfService();
+                    action.setNetworkTypeOfService(tosbits);
+                    log.debug("  action {}", action);
+                    
+                    sa = new SubActionStruct();
+                    sa.action = action;
+                    sa.len = OFActionNetworkTypeOfService.MINIMUM_LENGTH;
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid dst-port in: {} (error ignored)", subaction);
+                    return null;
+                }
+            }
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+    
+    private static SubActionStruct decode_set_src_ip(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("set-src-ip=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(subaction);
+
+        if (n.matches()) {
+            int ipaddr = get_ip_addr(n, subaction, log);
+            OFActionNetworkLayerSource action = new OFActionNetworkLayerSource();
+            action.setNetworkAddress(ipaddr);
+            log.debug("  action {}", action);
+
+            sa = new SubActionStruct();
+            sa.action = action;
+            sa.len = OFActionNetworkLayerSource.MINIMUM_LENGTH;
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+
+    private static SubActionStruct decode_set_dst_ip(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("set-dst-ip=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(subaction);
+
+        if (n.matches()) {
+            int ipaddr = get_ip_addr(n, subaction, log);
+            OFActionNetworkLayerDestination action = new OFActionNetworkLayerDestination();
+            action.setNetworkAddress(ipaddr);
+            log.debug("action {}", action);
+ 
+            sa = new SubActionStruct();
+            sa.action = action;
+            sa.len = OFActionNetworkLayerDestination.MINIMUM_LENGTH;
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+
+    private static SubActionStruct decode_set_src_port(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("set-src-port=((?:0x)?\\d+)").matcher(subaction); 
+
+        if (n.matches()) {
+            if (n.group(1) != null) {
+                try {
+                    short portnum = get_short(n.group(1));
+                    OFActionTransportLayerSource action = new OFActionTransportLayerSource();
+                    action.setTransportPort(portnum);
+                    log.debug("action {}", action);
+                    
+                    sa = new SubActionStruct();
+                    sa.action = action;
+                    sa.len = OFActionTransportLayerSource.MINIMUM_LENGTH;;
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid src-port in: {} (error ignored)", subaction);
+                    return null;
+                }
+            }
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+
+    private static SubActionStruct decode_set_dst_port(String subaction, Logger log) {
+        SubActionStruct sa = null;
+        Matcher n = Pattern.compile("set-dst-port=((?:0x)?\\d+)").matcher(subaction);
+
+        if (n.matches()) {
+            if (n.group(1) != null) {
+                try {
+                    short portnum = get_short(n.group(1));
+                    OFActionTransportLayerDestination action = new OFActionTransportLayerDestination();
+                    action.setTransportPort(portnum);
+                    log.debug("action {}", action);
+                    
+                    sa = new SubActionStruct();
+                    sa.action = action;
+                    sa.len = OFActionTransportLayerDestination.MINIMUM_LENGTH;;
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid dst-port in: {} (error ignored)", subaction);
+                    return null;
+                }
+            }
+        }
+        else {
+            log.debug("Invalid action: '{}'", subaction);
+            return null;
+        }
+
+        return sa;
+    }
+
+    private static byte[] get_mac_addr(Matcher n, String subaction, Logger log) {
+        byte[] macaddr = new byte[6];
+        
+        for (int i=0; i<6; i++) {
+            if (n.group(i+1) != null) {
+                try {
+                    macaddr[i] = get_byte("0x" + n.group(i+1));
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid src-mac in: '{}' (error ignored)", subaction);
+                    return null;
+                }
+            }
+            else { 
+                log.debug("Invalid src-mac in: '{}' (null, error ignored)", subaction);
+                return null;
+            }
+        }
+        
+        return macaddr;
+    }
+    
+    private static int get_ip_addr(Matcher n, String subaction, Logger log) {
+        int ipaddr = 0;
+
+        for (int i=0; i<4; i++) {
+            if (n.group(i+1) != null) {
+                try {
+                    ipaddr = ipaddr<<8;
+                    ipaddr = ipaddr | get_int(n.group(i+1));
+                }
+                catch (NumberFormatException e) {
+                    log.debug("Invalid src-ip in: '{}' (error ignored)", subaction);
+                    return 0;
+                }
+            }
+            else {
+                log.debug("Invalid src-ip in: '{}' (null, error ignored)", subaction);
+                return 0;
+            }
+        }
+        
+        return ipaddr;
+    }
+    
+    // Parse int as decimal, hex (start with 0x or #) or octal (starts with 0)
+    private static int get_int(String str) {
+        return (int)Integer.decode(str);
+    }
+   
+    // Parse short as decimal, hex (start with 0x or #) or octal (starts with 0)
+    private static short get_short(String str) {
+        return (short)(int)Integer.decode(str);
+    }
+   
+    // Parse byte as decimal, hex (start with 0x or #) or octal (starts with 0)
+    private static byte get_byte(String str) {
+        return Integer.decode(str).byteValue();
+    }
+
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java
new file mode 100644
index 0000000..4ed59d7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java
@@ -0,0 +1,679 @@
+package net.floodlightcontroller.staticflowentry;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.AppCookie;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.staticflowentry.web.StaticFlowEntryWebRoutable;
+import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.IStorageSourceListener;
+
+import net.floodlightcontroller.storage.StorageException;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFFlowRemoved;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@LogMessageCategory("Static Flow Pusher")
+/**
+ * This module is responsible for maintaining a set of static flows on
+ * switches. This is just a big 'ol dumb list of flows and something external
+ * is responsible for ensuring they make sense for the network.
+ */
+public class StaticFlowEntryPusher 
+    implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService,
+        IStorageSourceListener, IOFMessageListener, IHAListener {
+    protected static Logger log = LoggerFactory.getLogger(StaticFlowEntryPusher.class);
+    public static final String StaticFlowName = "staticflowentry";
+    
+    public static final int STATIC_FLOW_APP_ID = 10;
+
+    public static final String TABLE_NAME = "controller_staticflowtableentry";
+    public static final String COLUMN_NAME = "name";
+    public static final String COLUMN_SWITCH = "switch_id";
+    public static final String COLUMN_ACTIVE = "active";
+    public static final String COLUMN_IDLE_TIMEOUT = "idle_timeout";
+    public static final String COLUMN_HARD_TIMEOUT = "hard_timeout";
+    public static final String COLUMN_PRIORITY = "priority";
+    public static final String COLUMN_COOKIE = "cookie";
+    public static final String COLUMN_WILDCARD = "wildcards";
+    public static final String COLUMN_IN_PORT = "in_port";
+    public static final String COLUMN_DL_SRC = "dl_src";
+    public static final String COLUMN_DL_DST = "dl_dst";
+    public static final String COLUMN_DL_VLAN = "dl_vlan";
+    public static final String COLUMN_DL_VLAN_PCP = "dl_vlan_pcp";
+    public static final String COLUMN_DL_TYPE = "dl_type";
+    public static final String COLUMN_NW_TOS = "nw_tos";
+    public static final String COLUMN_NW_PROTO = "nw_proto";
+    public static final String COLUMN_NW_SRC = "nw_src"; // includes CIDR-style
+                                                         // netmask, e.g.
+                                                         // "128.8.128.0/24"
+    public static final String COLUMN_NW_DST = "nw_dst";
+    public static final String COLUMN_TP_DST = "tp_dst";
+    public static final String COLUMN_TP_SRC = "tp_src";
+    public static final String COLUMN_ACTIONS = "actions";
+    public static String ColumnNames[] = { COLUMN_NAME, COLUMN_SWITCH,
+            COLUMN_ACTIVE, COLUMN_IDLE_TIMEOUT, COLUMN_HARD_TIMEOUT,
+            COLUMN_PRIORITY, COLUMN_COOKIE, COLUMN_WILDCARD, COLUMN_IN_PORT,
+            COLUMN_DL_SRC, COLUMN_DL_DST, COLUMN_DL_VLAN, COLUMN_DL_VLAN_PCP,
+            COLUMN_DL_TYPE, COLUMN_NW_TOS, COLUMN_NW_PROTO, COLUMN_NW_SRC,
+            COLUMN_NW_DST, COLUMN_TP_DST, COLUMN_TP_SRC, COLUMN_ACTIONS };
+ 
+
+    protected IFloodlightProviderService floodlightProvider;
+    protected IStorageSourceService storageSource;
+    protected IRestApiService restApi;
+
+    // Map<DPID, Map<Name, FlowMod>> ; FlowMod can be null to indicate non-active
+    protected Map<String, Map<String, OFFlowMod>> entriesFromStorage;
+    // Entry Name -> DPID of Switch it's on
+    protected Map<String, String> entry2dpid;
+
+    private BasicFactory ofMessageFactory;
+
+    // Class to sort FlowMod's by priority, from lowest to highest
+    class FlowModSorter implements Comparator<String> {
+        private String dpid;
+        public FlowModSorter(String dpid) {
+            this.dpid = dpid;
+        }
+        @Override
+        public int compare(String o1, String o2) {
+            OFFlowMod f1 = entriesFromStorage.get(dpid).get(o1);
+            OFFlowMod f2 = entriesFromStorage.get(dpid).get(o2);
+            if (f1 == null || f2 == null) // sort active=false flows by key
+                return o1.compareTo(o2);
+            return U16.f(f1.getPriority()) - U16.f(f2.getPriority());
+        }
+    };
+
+    /**
+     * used for debugging and unittests
+     * @return the number of static flow entries as cached from storage
+     */
+    public int countEntries() {
+        int size = 0;
+        if (entriesFromStorage == null)
+            return 0;
+        for (String ofswitch : entriesFromStorage.keySet())
+            size += entriesFromStorage.get(ofswitch).size();
+        return size;
+    }
+
+    public IFloodlightProviderService getFloodlightProvider() {
+        return floodlightProvider;
+    }
+
+    public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
+        this.floodlightProvider = floodlightProvider;
+    }
+
+    public void setStorageSource(IStorageSourceService storageSource) {
+        this.storageSource = storageSource;
+    }
+
+    /**
+     * Reads from our entriesFromStorage for the specified switch and
+     * sends the FlowMods down to the controller in <b>sorted</b> order.
+     *
+     * Sorted is important to maintain correctness of the switch:
+     * if a packet would match both a lower and a higher priority
+     * rule, then we want it to match the higher priority or nothing,
+     * but never just the lower priority one.  Inserting from high to
+     * low priority fixes this.
+     *
+     * TODO consider adding a "block all" flow mod and then removing it
+     * while starting up.
+     *
+     * @param sw The switch to send entries to
+     */
+    protected void sendEntriesToSwitch(IOFSwitch sw) {
+        String dpid = sw.getStringId();
+
+        if ((entriesFromStorage != null) && (entriesFromStorage.containsKey(dpid))) {
+            Map<String, OFFlowMod> entries = entriesFromStorage.get(dpid);
+            List<String> sortedList = new ArrayList<String>(entries.keySet());
+            // weird that Collections.sort() returns void
+            Collections.sort( sortedList, new FlowModSorter(dpid));
+            for (String entryName : sortedList) {
+                OFFlowMod flowMod = entries.get(entryName);
+                if (flowMod != null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Pushing static entry {} for {}", dpid, entryName);
+                    }
+                    writeFlowModToSwitch(sw, flowMod);
+                }
+            }
+        }
+    }
+    
+    /**
+     * Used only for bundle-local indexing
+     * 
+     * @param map
+     * @return
+     */
+
+    protected Map<String, String> computeEntry2DpidMap(
+                Map<String, Map<String, OFFlowMod>> map) {
+        Map<String, String> ret = new HashMap<String, String>();
+        for(String dpid : map.keySet()) {
+            for( String entry: map.get(dpid).keySet())
+                ret.put(entry, dpid);
+        }
+        return ret;
+    }
+    
+    /**
+     * Read entries from storageSource, and store them in a hash
+     * 
+     * @return
+     */
+    @LogMessageDoc(level="ERROR",
+            message="failed to access storage: {reason}",
+            explanation="Could not retrieve static flows from the system " +
+            		"database",
+            recommendation=LogMessageDoc.CHECK_CONTROLLER)
+    private Map<String, Map<String, OFFlowMod>> readEntriesFromStorage() {
+        Map<String, Map<String, OFFlowMod>> entries = new ConcurrentHashMap<String, Map<String, OFFlowMod>>();
+        try {
+            Map<String, Object> row;
+            // null1=no predicate, null2=no ordering
+            IResultSet resultSet = storageSource.executeQuery(TABLE_NAME,
+                    ColumnNames, null, null);
+            for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) {
+                row = it.next().getRow();
+                parseRow(row, entries);
+            }
+        } catch (StorageException e) {
+            log.error("failed to access storage: {}", e.getMessage());
+            // if the table doesn't exist, then wait to populate later via
+            // setStorageSource()
+        }
+        return entries;
+    }
+
+    /**
+     * Take a single row, turn it into a flowMod, and add it to the
+     * entries{$dpid}.{$entryName}=FlowMod 
+     * 
+     * IF an entry is in active, mark it with FlowMod = null
+     * 
+     * @param row
+     * @param entries
+     */
+
+    void parseRow(Map<String, Object> row,
+            Map<String, Map<String, OFFlowMod>> entries) {
+        String switchName = null;
+        String entryName = null;
+
+        StringBuffer matchString = new StringBuffer();
+        if (ofMessageFactory == null) // lazy init
+            ofMessageFactory = new BasicFactory();
+
+        OFFlowMod flowMod = (OFFlowMod) ofMessageFactory
+                .getMessage(OFType.FLOW_MOD);
+
+        if (!row.containsKey(COLUMN_SWITCH) || !row.containsKey(COLUMN_NAME)) {
+            log.debug(
+                    "skipping entry with missing required 'switch' or 'name' entry: {}",
+                    row);
+            return;
+        }
+        // most error checking done with ClassCastException
+        try {
+            // first, snag the required entries, for debugging info
+            switchName = (String) row.get(COLUMN_SWITCH);
+            entryName = (String) row.get(COLUMN_NAME);
+            if (!entries.containsKey(switchName))
+                entries.put(switchName, new HashMap<String, OFFlowMod>());
+            StaticFlowEntries.initDefaultFlowMod(flowMod, entryName);
+            
+            for (String key : row.keySet()) {
+                if (row.get(key) == null)
+                    continue;
+                if ( key.equals(COLUMN_SWITCH) || key.equals(COLUMN_NAME)
+                        || key.equals("id"))
+                    continue; // already handled
+                // explicitly ignore timeouts and wildcards
+                if ( key.equals(COLUMN_HARD_TIMEOUT) || key.equals(COLUMN_IDLE_TIMEOUT) ||
+                        key.equals(COLUMN_WILDCARD))
+                    continue;
+                if ( key.equals(COLUMN_ACTIVE)) {
+                    if  (! Boolean.valueOf((String) row.get(COLUMN_ACTIVE))) {
+                        log.debug("skipping inactive entry {} for switch {}",
+                                entryName, switchName);
+                        entries.get(switchName).put(entryName, null);  // mark this an inactive
+                        return;
+                    }
+                } else if ( key.equals(COLUMN_ACTIONS)){
+                    StaticFlowEntries.parseActionString(flowMod, (String) row.get(COLUMN_ACTIONS), log);
+                } else if ( key.equals(COLUMN_COOKIE)) {
+                    flowMod.setCookie(
+                            StaticFlowEntries.computeEntryCookie(flowMod, 
+                                    Integer.valueOf((String) row.get(COLUMN_COOKIE)), 
+                                    entryName)
+                        );
+                } else if ( key.equals(COLUMN_PRIORITY)) {
+                    flowMod.setPriority(U16.t(Integer.valueOf((String) row.get(COLUMN_PRIORITY))));
+                } else { // the rest of the keys are for OFMatch().fromString()
+                    if (matchString.length() > 0)
+                        matchString.append(",");
+                    matchString.append(key + "=" + row.get(key).toString());
+                }
+            }
+        } catch (ClassCastException e) {
+            if (entryName != null && switchName != null)
+                log.debug(
+                        "skipping entry {} on switch {} with bad data : "
+                                + e.getMessage(), entryName, switchName);
+            else
+                log.debug("skipping entry with bad data: {} :: {} ",
+                        e.getMessage(), e.getStackTrace());
+        }
+
+        OFMatch ofMatch = new OFMatch();
+        String match = matchString.toString();
+        try {
+            ofMatch.fromString(match);
+        } catch (IllegalArgumentException e) {
+            log.debug(
+                    "ignoring flow entry {} on switch {} with illegal OFMatch() key: "
+                            + match, entryName, switchName);
+            return;
+        }
+        flowMod.setMatch(ofMatch);
+
+        entries.get(switchName).put(entryName, flowMod);
+    }
+    
+    @Override
+    public void addedSwitch(IOFSwitch sw) {
+        log.debug("addedSwitch {}; processing its static entries", sw);
+        sendEntriesToSwitch(sw);
+    }
+
+    @Override
+    public void removedSwitch(IOFSwitch sw) {
+        log.debug("removedSwitch {}", sw);
+        // do NOT delete from our internal state; we're tracking the rules,
+        // not the switches
+    }
+    
+    @Override
+    public void switchPortChanged(Long switchId) {
+        // no-op
+    }
+
+    /**
+     * This handles both rowInsert() and rowUpdate()
+     */
+    
+    @Override
+    public void rowsModified(String tableName, Set<Object> rowKeys) {
+        log.debug("Modifying Table {}", tableName);
+
+        HashMap<String, Map<String, OFFlowMod>> entriesToAdd = 
+            new HashMap<String, Map<String, OFFlowMod>>();
+        // build up list of what was added 
+        for(Object key: rowKeys) {
+            IResultSet resultSet = storageSource.getRow(tableName, key);
+            for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) {
+                Map<String, Object> row = it.next().getRow();
+                parseRow(row, entriesToAdd);
+            }            
+        }
+        // batch updates by switch and blast them out
+        for (String dpid : entriesToAdd.keySet()) {
+            if (!entriesFromStorage.containsKey(dpid))
+                entriesFromStorage.put(dpid, new HashMap<String, OFFlowMod>());
+            List<OFMessage> outQueue = new ArrayList<OFMessage>();
+            for(String entry : entriesToAdd.get(dpid).keySet()) {
+                OFFlowMod newFlowMod = entriesToAdd.get(dpid).get(entry);
+                OFFlowMod oldFlowMod = entriesFromStorage.get(dpid).get(entry);
+                if (oldFlowMod != null) {  // remove any pre-existing rule
+                    oldFlowMod.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
+                    outQueue.add(oldFlowMod);
+                }
+                if (newFlowMod != null) {
+                    entriesFromStorage.get(dpid).put(entry, newFlowMod);
+                    outQueue.add(newFlowMod);
+                    entry2dpid.put(entry, dpid);
+                } else {
+                    entriesFromStorage.get(dpid).remove(entry);
+                    entry2dpid.remove(entry);
+                }
+            }
+            
+            writeOFMessagesToSwitch(HexString.toLong(dpid), outQueue);
+        }
+    }
+
+    @Override
+    public void rowsDeleted(String tableName, Set<Object> rowKeys) {
+        if (log.isDebugEnabled()) {
+            log.debug("deleting from Table {}", tableName);
+        }
+        
+        for(Object obj : rowKeys) {
+            if (!(obj instanceof String)) {
+                log.debug("tried to delete non-string key {}; ignoring", obj);
+                continue;
+            }
+            deleteStaticFlowEntry((String) obj);
+        }
+    }
+    
+    @LogMessageDoc(level="ERROR",
+            message="inconsistent internal state: no switch has rule {rule}",
+            explanation="Inconsistent internat state discovered while " +
+            		"deleting a static flow rule",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    private boolean deleteStaticFlowEntry(String entryName) {
+        String dpid = entry2dpid.get(entryName);
+        if (log.isDebugEnabled()) {
+            log.debug("Deleting flow {} for switch {}", entryName, dpid);
+        }
+        if (dpid == null) {
+            log.error("inconsistent internal state: no switch has rule {}",
+                    entryName);
+            return false;
+        }
+        
+        // send flow_mod delete
+        OFFlowMod flowMod = entriesFromStorage.get(dpid).get(entryName);
+        flowMod.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
+
+        if (entriesFromStorage.containsKey(dpid) && 
+                entriesFromStorage.get(dpid).containsKey(entryName)) {
+            entriesFromStorage.get(dpid).remove(entryName);
+        } else { 
+            log.debug("Tried to delete non-existent entry {} for switch {}", 
+                    entryName, dpid);
+            return false;
+        }
+        
+        writeFlowModToSwitch(HexString.toLong(dpid), flowMod);
+        return true;
+    }
+    
+    /**
+     * Writes a list of OFMessages to a switch
+     * @param dpid The datapath ID of the switch to write to
+     * @param messages The list of OFMessages to write.
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Tried to write to switch {switch} but got {error}",
+            explanation="An I/O error occured while trying to write a " +
+            		"static flow to a switch",
+            recommendation=LogMessageDoc.CHECK_SWITCH)
+    private void writeOFMessagesToSwitch(long dpid, List<OFMessage> messages) {
+        IOFSwitch ofswitch = floodlightProvider.getSwitches().get(dpid);
+        if (ofswitch != null) {  // is the switch connected
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("Sending {} new entries to {}", messages.size(), dpid);
+                }
+                ofswitch.write(messages, null);
+                ofswitch.flush();
+            } catch (IOException e) {
+                log.error("Tried to write to switch {} but got {}", dpid, e.getMessage());
+            }
+        }
+    }
+    
+    /**
+     * Writes an OFFlowMod to a switch. It checks to make sure the switch
+     * exists before it sends
+     * @param dpid The data  to write the flow mod to
+     * @param flowMod The OFFlowMod to write
+     */
+    private void writeFlowModToSwitch(long dpid, OFFlowMod flowMod) {
+        Map<Long,IOFSwitch> switches = floodlightProvider.getSwitches();
+        IOFSwitch ofSwitch = switches.get(dpid);
+        if (ofSwitch == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("Not deleting key {} :: switch {} not connected", 
+                          dpid);
+            }
+            return;
+        }
+        writeFlowModToSwitch(ofSwitch, flowMod);
+    }
+    
+    /**
+     * Writes an OFFlowMod to a switch
+     * @param sw The IOFSwitch to write to
+     * @param flowMod The OFFlowMod to write
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Tried to write OFFlowMod to {switch} but got {error}",
+            explanation="An I/O error occured while trying to write a " +
+                    "static flow to a switch",
+            recommendation=LogMessageDoc.CHECK_SWITCH)
+    private void writeFlowModToSwitch(IOFSwitch sw, OFFlowMod flowMod) {
+        try {
+            sw.write(flowMod, null);
+            sw.flush();
+        } catch (IOException e) {
+            log.error("Tried to write OFFlowMod to {} but failed: {}", 
+                    HexString.toHexString(sw.getId()), e.getMessage());
+        }
+    }
+
+    @Override
+    public String getName() {
+        return StaticFlowName;
+    }
+    
+    @Override
+    @LogMessageDoc(level="ERROR",
+        message="Got a FlowRemove message for a infinite " +
+                "timeout flow: {flow} from switch {switch}",
+        explanation="Flows with infinite timeouts should not expire. " +
+        		"The switch has expired the flow anyway.",
+        recommendation=LogMessageDoc.REPORT_SWITCH_BUG)
+    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+        switch (msg.getType()) {
+        case FLOW_REMOVED:
+            break;
+        default:
+            return Command.CONTINUE;
+        }
+        OFFlowRemoved flowRemoved = (OFFlowRemoved) msg;
+        long cookie = flowRemoved.getCookie();
+        /**
+         * This is just to sanity check our assumption that static flows 
+         * never expire.
+         */
+        if( AppCookie.extractApp(cookie) == STATIC_FLOW_APP_ID) {
+            if (flowRemoved.getReason() != 
+                    OFFlowRemoved.OFFlowRemovedReason.OFPRR_DELETE)
+                log.error("Got a FlowRemove message for a infinite " +
+                		  "timeout flow: {} from switch {}", msg, sw);
+            return Command.STOP;    // only for us
+        } else
+            return Command.CONTINUE;
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        return false;  // no dependency for non-packet in
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return false;  // no dependency for non-packet in
+    }
+
+    // IFloodlightModule
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IStaticFlowEntryPusherService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                    IFloodlightService>();
+        m.put(IStaticFlowEntryPusherService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        l.add(IStorageSourceService.class);
+        l.add(IRestApiService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        floodlightProvider =
+            context.getServiceImpl(IFloodlightProviderService.class);
+        storageSource =
+            context.getServiceImpl(IStorageSourceService.class);
+        restApi =
+            context.getServiceImpl(IRestApiService.class);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {        
+        floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this);
+        floodlightProvider.addOFSwitchListener(this);
+        floodlightProvider.addHAListener(this);
+        
+        // assumes no switches connected at startup()
+        storageSource.createTable(TABLE_NAME, null);
+        storageSource.setTablePrimaryKeyName(TABLE_NAME, COLUMN_NAME);
+        storageSource.addListener(TABLE_NAME, this);
+        entriesFromStorage = readEntriesFromStorage(); 
+        entry2dpid = computeEntry2DpidMap(entriesFromStorage);
+        restApi.addRestletRoutable(new StaticFlowEntryWebRoutable());
+    }
+
+    // IStaticFlowEntryPusherService methods
+    
+    @Override
+    public void addFlow(String name, OFFlowMod fm, String swDpid) {
+        Map<String, Object> fmMap = StaticFlowEntries.flowModToStorageEntry(fm, swDpid, name);
+        entry2dpid.put(name, swDpid);
+        Map<String, OFFlowMod> switchEntries = entriesFromStorage.get(swDpid);
+        if (switchEntries == null) {
+            switchEntries = new HashMap<String, OFFlowMod>();
+            entriesFromStorage.put(swDpid, switchEntries);
+        }
+        switchEntries.put(name, fm);
+        storageSource.insertRowAsync(TABLE_NAME, fmMap);
+    }
+
+    @Override
+    public void deleteFlow(String name) {
+        storageSource.deleteRowAsync(TABLE_NAME, name);
+        // TODO - What if there is a delay in storage?
+    }
+    
+    @Override
+    public void deleteAllFlows() {
+        for (String entry : entry2dpid.keySet()) {
+            deleteFlow(entry);
+        }
+    }
+    
+    @Override
+    public void deleteFlowsForSwitch(long dpid) {
+        String sDpid = HexString.toHexString(dpid);
+        
+        for (Entry<String, String> e : entry2dpid.entrySet()) {
+            if (e.getValue().equals(sDpid))
+                deleteFlow(e.getKey());
+        }
+    }
+    
+    @Override
+    public Map<String, Map<String, OFFlowMod>> getFlows() {
+        return entriesFromStorage;
+    }
+    
+    @Override
+    public Map<String, OFFlowMod> getFlows(String dpid) {
+        return entriesFromStorage.get(dpid);
+    }
+
+    
+    // IHAListener
+    
+    @Override
+    public void roleChanged(Role oldRole, Role newRole) {
+        switch(newRole) {
+            case MASTER:
+                if (oldRole == Role.SLAVE) {
+                    log.debug("Re-reading static flows from storage due " +
+                            "to HA change from SLAVE->MASTER");
+                    entriesFromStorage = readEntriesFromStorage(); 
+                    entry2dpid = computeEntry2DpidMap(entriesFromStorage);
+                }
+                break;
+            case SLAVE:
+                log.debug("Clearing in-memory flows due to " +
+                        "HA change to SLAVE");
+                entry2dpid.clear();
+                entriesFromStorage.clear();
+                break;
+            default:
+            	break;
+        }
+    }
+    
+    @Override
+    public void controllerNodeIPsChanged(
+            Map<String, String> curControllerNodeIPs,
+            Map<String, String> addedControllerNodeIPs,
+            Map<String, String> removedControllerNodeIPs) {
+        // ignore
+    }
+     
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java
new file mode 100644
index 0000000..c1d826a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java
@@ -0,0 +1,38 @@
+package net.floodlightcontroller.staticflowentry.web;
+
+import net.floodlightcontroller.core.web.ControllerSwitchesResource;
+import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService;
+
+import org.openflow.util.HexString;
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClearStaticFlowEntriesResource extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(ClearStaticFlowEntriesResource.class);
+    
+    @Get
+    public void ClearStaticFlowEntries() {
+        IStaticFlowEntryPusherService sfpService =
+                (IStaticFlowEntryPusherService)getContext().getAttributes().
+                    get(IStaticFlowEntryPusherService.class.getCanonicalName());
+        
+        String param = (String) getRequestAttributes().get("switch");
+        if (log.isDebugEnabled())
+            log.debug("Clearing all static flow entires for switch: " + param);
+        
+        if (param.toLowerCase().equals("all")) {
+            sfpService.deleteAllFlows();
+        } else {
+            try {
+                sfpService.deleteFlowsForSwitch(HexString.toLong(param));
+            } catch (NumberFormatException e){
+                setStatus(Status.CLIENT_ERROR_BAD_REQUEST, 
+                          ControllerSwitchesResource.DPID_ERROR);
+                return;
+            }
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java
new file mode 100644
index 0000000..0ad778f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java
@@ -0,0 +1,45 @@
+package net.floodlightcontroller.staticflowentry.web;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.core.web.ControllerSwitchesResource;
+import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService;
+
+import org.openflow.protocol.OFFlowMod;
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ListStaticFlowEntriesResource extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(ListStaticFlowEntriesResource.class);
+    
+    @Get
+    public Map<String, Map<String, OFFlowMod>> ListStaticFlowEntries() {
+        IStaticFlowEntryPusherService sfpService =
+                (IStaticFlowEntryPusherService)getContext().getAttributes().
+                    get(IStaticFlowEntryPusherService.class.getCanonicalName());
+        
+        String param = (String) getRequestAttributes().get("switch");
+        if (log.isDebugEnabled())
+            log.debug("Listing all static flow entires for switch: " + param);
+        
+        if (param.toLowerCase().equals("all")) {
+            return sfpService.getFlows();
+        } else {
+            try {
+                Map<String, Map<String, OFFlowMod>> retMap = 
+                        new HashMap<String, Map<String, OFFlowMod>>();
+                retMap.put(param, sfpService.getFlows(param));
+                return retMap;
+                
+            } catch (NumberFormatException e){
+                setStatus(Status.CLIENT_ERROR_BAD_REQUEST, 
+                          ControllerSwitchesResource.DPID_ERROR);
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java
new file mode 100644
index 0000000..3b750ae
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java
@@ -0,0 +1,142 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.staticflowentry.web;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.restlet.resource.Delete;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.staticflowentry.StaticFlowEntries;
+import net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher;
+import net.floodlightcontroller.storage.IStorageSourceService;
+
+/**
+ * Pushes a static flow entry to the storage source
+ * @author alexreimers
+ *
+ */
+@LogMessageCategory("Static Flow Pusher")
+public class StaticFlowEntryPusherResource extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(StaticFlowEntryPusherResource.class);
+    
+    /**
+     * Checks to see if the user matches IP information without
+     * checking for the correct ether-type (2048).
+     * @param rows The Map that is a string representation of
+     * the static flow.
+     * @reutrn True if they checked the ether-type, false otherwise
+     */
+    private boolean checkMatchIp(Map<String, Object> rows) {
+        boolean matchEther = false;
+        String val = (String) rows.get(StaticFlowEntryPusher.COLUMN_DL_TYPE);
+        if (val != null) {
+            int type = 0;
+            // check both hex and decimal
+            if (val.startsWith("0x")) {
+                type = Integer.parseInt(val.substring(2), 16);
+            } else {
+                try {
+                    type = Integer.parseInt(val);
+                } catch (NumberFormatException e) { /* fail silently */}
+            }
+            if (type == 2048) matchEther = true;
+        }
+        
+        if ((rows.containsKey(StaticFlowEntryPusher.COLUMN_NW_DST) || 
+                rows.containsKey(StaticFlowEntryPusher.COLUMN_NW_SRC) ||
+                rows.containsKey(StaticFlowEntryPusher.COLUMN_NW_PROTO) ||
+                rows.containsKey(StaticFlowEntryPusher.COLUMN_NW_TOS)) &&
+                (matchEther == false))
+            return false;
+        
+        return true;
+    }
+    
+    /**
+     * Takes a Static Flow Pusher string in JSON format and parses it into
+     * our database schema then pushes it to the database.
+     * @param fmJson The Static Flow Pusher entry in JSON format.
+     * @return A string status message
+     */
+    @Post
+    @LogMessageDoc(level="ERROR",
+        message="Error parsing push flow mod request: {request}",
+        explanation="An invalid request was sent to static flow pusher",
+        recommendation="Fix the format of the static flow mod request")
+    public String store(String fmJson) {
+        IStorageSourceService storageSource =
+                (IStorageSourceService)getContext().getAttributes().
+                    get(IStorageSourceService.class.getCanonicalName());
+        
+        Map<String, Object> rowValues;
+        try {
+            rowValues = StaticFlowEntries.jsonToStorageEntry(fmJson);
+            String status = null;
+            if (!checkMatchIp(rowValues)) {
+                status = "Warning! Pushing a static flow entry that matches IP " +
+                        "fields without matching for IP payload (ether-type 2048) will cause " +
+                        "the switch to wildcard higher level fields.";
+                log.error(status);
+            } else {
+                status = "Entry pushed";
+            }
+            storageSource.insertRowAsync(StaticFlowEntryPusher.TABLE_NAME, rowValues);
+            return ("{\"status\" : \"" + status + "\"}");
+        } catch (IOException e) {
+            log.error("Error parsing push flow mod request: " + fmJson, e);
+            e.printStackTrace();
+            return "{\"status\" : \"Error! Could not parse flod mod, see log for details.\"}";
+        }
+    }
+    
+    @Delete
+    @LogMessageDoc(level="ERROR",
+        message="Error deleting flow mod request: {request}",
+        explanation="An invalid delete request was sent to static flow pusher",
+        recommendation="Fix the format of the static flow mod request")
+    public String del(String fmJson) {
+        IStorageSourceService storageSource =
+                (IStorageSourceService)getContext().getAttributes().
+                    get(IStorageSourceService.class.getCanonicalName());
+        String fmName = null;
+        if (fmJson == null) {
+            return "{\"status\" : \"Error! No data posted.\"}";
+        }
+        try {
+            fmName = StaticFlowEntries.getEntryNameFromJson(fmJson);
+            if (fmName == null) {
+                return "{\"status\" : \"Error deleting entry, no name provided\"}";
+            }
+        } catch (IOException e) {
+            log.error("Error deleting flow mod request: " + fmJson, e);
+            e.printStackTrace();
+            return "{\"status\" : \"Error deleting entry, see log for details\"}";
+        }
+        
+        storageSource.deleteRowAsync(StaticFlowEntryPusher.TABLE_NAME, fmName);
+        return "{\"status\" : \"Entry " + fmName + " deleted\"}";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryWebRoutable.java
new file mode 100644
index 0000000..b5a6fe1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryWebRoutable.java
@@ -0,0 +1,29 @@
+package net.floodlightcontroller.staticflowentry.web;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+public class StaticFlowEntryWebRoutable implements RestletRoutable {
+    /**
+     * Create the Restlet router and bind to the proper resources.
+     */
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/json", StaticFlowEntryPusherResource.class);
+        router.attach("/clear/{switch}/json", ClearStaticFlowEntriesResource.class);
+        router.attach("/list/{switch}/json", ListStaticFlowEntriesResource.class);
+        return router;
+    }
+
+    /**
+     * Set the base path for the Topology
+     */
+    @Override
+    public String basePath() {
+        return "/wm/staticflowentrypusher";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java
new file mode 100644
index 0000000..aae3962
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java
@@ -0,0 +1,534 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.counter.ICounter;
+import net.floodlightcontroller.counter.CounterStore;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.counter.CounterValue.CounterType;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.storage.web.StorageWebRoutable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@LogMessageCategory("System Database")
+public abstract class AbstractStorageSource 
+    implements IStorageSourceService, IFloodlightModule {
+    protected static Logger logger = LoggerFactory.getLogger(AbstractStorageSource.class);
+
+    // Shared instance of the executor to use to execute the storage tasks.
+    // We make this a single threaded executor, because if we used a thread pool
+    // then storage operations could be executed out of order which would cause
+    // problems in some cases (e.g. delete and update of a row getting reordered).
+    // If we wanted to make this more multi-threaded we could have multiple
+    // worker threads/executors with affinity of operations on a given table
+    // to a single worker thread. But for now, we'll keep it simple and just have
+    // a single thread for all operations.
+    protected static ExecutorService defaultExecutorService = Executors.newSingleThreadExecutor();
+
+    protected final static String STORAGE_QUERY_COUNTER_NAME = "StorageQuery";
+    protected final static String STORAGE_UPDATE_COUNTER_NAME = "StorageUpdate";
+    protected final static String STORAGE_DELETE_COUNTER_NAME = "StorageDelete";
+    
+    protected Set<String> allTableNames = new CopyOnWriteArraySet<String>();
+    protected ICounterStoreService counterStore;
+    protected ExecutorService executorService = defaultExecutorService;
+    protected IStorageExceptionHandler exceptionHandler;
+
+    private Map<String, Set<IStorageSourceListener>> listeners =
+        new ConcurrentHashMap<String, Set<IStorageSourceListener>>();
+
+    // Our dependencies
+    protected IRestApiService restApi = null;
+    
+    protected static final String DB_ERROR_EXPLANATION =
+            "An unknown error occurred while executing asynchronous " +
+            "database operation";
+    
+    @LogMessageDoc(level="ERROR",
+            message="Failure in asynchronous call to executeQuery",
+            explanation=DB_ERROR_EXPLANATION,
+            recommendation=LogMessageDoc.GENERIC_ACTION)
+    abstract class StorageCallable<V> implements Callable<V> {
+        public V call() {
+            try {
+                return doStorageOperation();
+            }
+            catch (StorageException e) {
+                logger.error("Failure in asynchronous call to executeQuery", e);
+                if (exceptionHandler != null)
+                    exceptionHandler.handleException(e);
+                throw e;
+            }
+        }
+        abstract protected V doStorageOperation();
+    }
+    
+    @LogMessageDoc(level="ERROR",
+            message="Failure in asynchronous call to updateRows",
+            explanation=DB_ERROR_EXPLANATION,
+            recommendation=LogMessageDoc.GENERIC_ACTION)
+    abstract class StorageRunnable implements Runnable {
+        public void run() {
+            try {
+                doStorageOperation();
+            }
+            catch (StorageException e) {
+                logger.error("Failure in asynchronous call to updateRows", e);
+                if (exceptionHandler != null)
+                    exceptionHandler.handleException(e);
+                throw e;
+            }
+        }
+        abstract void doStorageOperation();
+    }
+    
+    public AbstractStorageSource() {
+        this.executorService = defaultExecutorService;
+    }
+
+    public void setExecutorService(ExecutorService executorService) {
+        this.executorService = (executorService != null) ?
+                executorService : defaultExecutorService;
+    }
+    
+    @Override
+    public void setExceptionHandler(IStorageExceptionHandler exceptionHandler) {
+        this.exceptionHandler = exceptionHandler;
+    }
+    
+    @Override
+    public abstract void setTablePrimaryKeyName(String tableName, String primaryKeyName);
+
+    @Override
+    public void createTable(String tableName, Set<String> indexedColumns) {
+        allTableNames.add(tableName);
+    }
+
+    @Override
+    public Set<String> getAllTableNames() {
+        return allTableNames;
+    }
+    
+    public void setCounterStore(CounterStore counterStore) {
+        this.counterStore = counterStore;
+    }
+    
+    protected void updateCounters(String baseName, String tableName) {
+        if (counterStore != null) {
+            String counterName;
+            if (tableName != null) {
+                updateCounters(baseName, null);
+                counterName = baseName + CounterStore.TitleDelimitor + tableName;
+            } else {
+                counterName = baseName;
+            }
+            ICounter counter = counterStore.getCounter(counterName);
+            if (counter == null) {
+                counter = counterStore.createCounter(counterName, CounterType.LONG);
+            }
+            counter.increment();
+        }
+    }
+    
+    @Override
+    public abstract IQuery createQuery(String tableName, String[] columnNames,
+            IPredicate predicate, RowOrdering ordering);
+
+    @Override
+    public IResultSet executeQuery(IQuery query) {
+        updateCounters(STORAGE_QUERY_COUNTER_NAME, query.getTableName());
+        return executeQueryImpl(query);
+    }
+    
+    protected abstract IResultSet executeQueryImpl(IQuery query);
+
+    @Override
+    public IResultSet executeQuery(String tableName, String[] columnNames,
+            IPredicate predicate, RowOrdering ordering) {
+        IQuery query = createQuery(tableName, columnNames, predicate, ordering);
+        IResultSet resultSet = executeQuery(query);
+        return resultSet;
+    }
+
+    @Override
+    public Object[] executeQuery(String tableName, String[] columnNames,
+            IPredicate predicate, RowOrdering ordering, IRowMapper rowMapper) {
+        List<Object> objectList = new ArrayList<Object>();
+        IResultSet resultSet = executeQuery(tableName, columnNames, predicate, ordering);
+        while (resultSet.next()) {
+            Object object = rowMapper.mapRow(resultSet);
+            objectList.add(object);
+        }
+        return objectList.toArray();
+    }
+    
+    @Override
+    public Future<IResultSet> executeQueryAsync(final IQuery query) {
+        Future<IResultSet> future = executorService.submit(
+            new StorageCallable<IResultSet>() {
+                public IResultSet doStorageOperation() {
+                    return executeQuery(query);
+                }
+            });
+        return future;
+    }
+
+    @Override
+    public Future<IResultSet> executeQueryAsync(final String tableName,
+            final String[] columnNames,  final IPredicate predicate,
+            final RowOrdering ordering) {
+        Future<IResultSet> future = executorService.submit(
+            new StorageCallable<IResultSet>() {
+                public IResultSet doStorageOperation() {
+                    return executeQuery(tableName, columnNames,
+                            predicate, ordering);
+                }
+            });
+        return future;
+    }
+
+    @Override
+    public Future<Object[]> executeQueryAsync(final String tableName,
+            final String[] columnNames,  final IPredicate predicate,
+            final RowOrdering ordering, final IRowMapper rowMapper) {
+        Future<Object[]> future = executorService.submit(
+            new StorageCallable<Object[]>() {
+                public Object[] doStorageOperation() {
+                    return executeQuery(tableName, columnNames, predicate,
+                            ordering, rowMapper);
+                }
+            });
+        return future;
+    }
+
+    @Override
+    public Future<?> insertRowAsync(final String tableName,
+            final Map<String,Object> values) {
+        Future<?> future = executorService.submit(
+            new StorageRunnable() {
+                public void doStorageOperation() {
+                    insertRow(tableName, values);
+                }
+            }, null);
+        return future;
+    }
+
+    @Override
+    public Future<?> updateRowsAsync(final String tableName, final List<Map<String,Object>> rows) {
+        Future<?> future = executorService.submit(    
+            new StorageRunnable() {
+                public void doStorageOperation() {
+                    updateRows(tableName, rows);
+                }
+            }, null);
+        return future;
+    }
+
+    @Override
+    public Future<?> updateMatchingRowsAsync(final String tableName,
+            final IPredicate predicate, final Map<String,Object> values) {
+        Future<?> future = executorService.submit(    
+            new StorageRunnable() {
+                public void doStorageOperation() {
+                    updateMatchingRows(tableName, predicate, values);
+                }
+            }, null);
+        return future;
+    }
+
+    @Override
+    public Future<?> updateRowAsync(final String tableName,
+            final Object rowKey, final Map<String,Object> values) {
+        Future<?> future = executorService.submit(
+            new StorageRunnable() {
+                public void doStorageOperation() {
+                    updateRow(tableName, rowKey, values);
+                }
+            }, null);
+        return future;
+    }
+
+    @Override
+    public Future<?> updateRowAsync(final String tableName,
+            final Map<String,Object> values) {
+        Future<?> future = executorService.submit(
+            new StorageRunnable() {
+                public void doStorageOperation() {
+                    updateRow(tableName, values);
+                }
+            }, null);
+        return future;
+    }
+
+    @Override
+    public Future<?> deleteRowAsync(final String tableName, final Object rowKey) {
+        Future<?> future = executorService.submit(
+            new StorageRunnable() {
+                public void doStorageOperation() {
+                    deleteRow(tableName, rowKey);
+                }
+            }, null);
+        return future;
+    }
+
+    @Override
+    public Future<?> deleteRowsAsync(final String tableName, final Set<Object> rowKeys) {
+        Future<?> future = executorService.submit(
+                new StorageRunnable() {
+                    public void doStorageOperation() {
+                        deleteRows(tableName, rowKeys);
+                    }
+                }, null);
+        return future;
+    }
+
+    @Override
+    public Future<?> deleteMatchingRowsAsync(final String tableName, final IPredicate predicate) {
+        Future<?> future = executorService.submit(
+                new StorageRunnable() {
+                    public void doStorageOperation() {
+                        deleteMatchingRows(tableName, predicate);
+                    }
+                }, null);
+        return future;
+    }
+
+    @Override
+    public Future<?> getRowAsync(final String tableName, final Object rowKey) {
+        Future<?> future = executorService.submit(
+            new StorageRunnable() {
+                public void doStorageOperation() {
+                    getRow(tableName, rowKey);
+                }
+            }, null);
+        return future;
+    }
+    
+    @Override
+    public Future<?> saveAsync(final IResultSet resultSet) {
+        Future<?> future = executorService.submit(
+            new StorageRunnable() {
+                public void doStorageOperation() {
+                    resultSet.save();
+                }
+            }, null);
+        return future;
+    }
+
+    @Override
+    public void insertRow(String tableName, Map<String, Object> values) {
+        updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+        insertRowImpl(tableName, values);
+    }
+
+    protected abstract void insertRowImpl(String tableName, Map<String, Object> values);
+
+    
+    @Override
+    public void updateRows(String tableName, List<Map<String,Object>> rows) {
+        updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+        updateRowsImpl(tableName, rows);
+    }
+
+    protected abstract void updateRowsImpl(String tableName, List<Map<String,Object>> rows);
+
+    @Override
+    public void updateMatchingRows(String tableName, IPredicate predicate,
+            Map<String, Object> values) {
+        updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+        updateMatchingRowsImpl(tableName, predicate, values);
+    }
+    
+    protected abstract void updateMatchingRowsImpl(String tableName, IPredicate predicate,
+                                    Map<String, Object> values);
+
+    @Override
+    public void updateRow(String tableName, Object rowKey,
+            Map<String, Object> values) {
+        updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+        updateRowImpl(tableName, rowKey, values);
+    }
+    
+    protected abstract void updateRowImpl(String tableName, Object rowKey,
+                                   Map<String, Object> values);
+
+    @Override
+    public void updateRow(String tableName, Map<String, Object> values) {
+        updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+        updateRowImpl(tableName, values);
+    }
+    
+    protected abstract void updateRowImpl(String tableName, Map<String, Object> values);
+
+    @Override
+    public void deleteRow(String tableName, Object rowKey) {
+        updateCounters(STORAGE_DELETE_COUNTER_NAME, tableName);
+        deleteRowImpl(tableName, rowKey);
+    }
+    
+    protected abstract void deleteRowImpl(String tableName, Object rowKey);
+
+    @Override
+    public void deleteRows(String tableName, Set<Object> rowKeys) {
+        updateCounters(STORAGE_DELETE_COUNTER_NAME, tableName);
+        deleteRowsImpl(tableName, rowKeys);
+    }
+
+    protected abstract void deleteRowsImpl(String tableName, Set<Object> rowKeys);
+
+    @Override
+    public void deleteMatchingRows(String tableName, IPredicate predicate) {
+        IResultSet resultSet = null;
+        try {
+            resultSet = executeQuery(tableName, null, predicate, null);
+            while (resultSet.next()) {
+                resultSet.deleteRow();
+            }
+            resultSet.save();
+        }
+        finally {
+            if (resultSet != null)
+                resultSet.close();
+        }
+    }
+    
+    @Override
+    public IResultSet getRow(String tableName, Object rowKey) {
+        updateCounters(STORAGE_QUERY_COUNTER_NAME, tableName);
+        return getRowImpl(tableName, rowKey);
+    }
+
+    protected abstract IResultSet getRowImpl(String tableName, Object rowKey);
+
+    @Override
+    public synchronized void addListener(String tableName, IStorageSourceListener listener) {
+        Set<IStorageSourceListener> tableListeners = listeners.get(tableName);
+        if (tableListeners == null) {
+            tableListeners = new CopyOnWriteArraySet<IStorageSourceListener>();
+            listeners.put(tableName, tableListeners);
+        }
+        tableListeners.add(listener);
+    }
+  
+    @Override
+    public synchronized void removeListener(String tableName, IStorageSourceListener listener) {
+        Set<IStorageSourceListener> tableListeners = listeners.get(tableName);
+        if (tableListeners != null) {
+            tableListeners.remove(listener);
+        }
+    }
+
+    @LogMessageDoc(level="ERROR",
+            message="Exception caught handling storage notification",
+            explanation="An unknown error occured while trying to notify" +
+            		" storage listeners",
+            recommendation=LogMessageDoc.GENERIC_ACTION)
+    protected synchronized void notifyListeners(StorageSourceNotification notification) {
+        String tableName = notification.getTableName();
+        Set<Object> keys = notification.getKeys();
+        Set<IStorageSourceListener> tableListeners = listeners.get(tableName);
+        if (tableListeners != null) {
+            for (IStorageSourceListener listener : tableListeners) {
+                try {
+                    switch (notification.getAction()) {
+                        case MODIFY:
+                            listener.rowsModified(tableName, keys);
+                            break;
+                        case DELETE:
+                            listener.rowsDeleted(tableName, keys);
+                            break;
+                    }
+                }
+                catch (Exception e) {
+                    logger.error("Exception caught handling storage notification", e);
+                }
+            }
+        }
+    }
+    
+    @Override
+    public void notifyListeners(List<StorageSourceNotification> notifications) {
+        for (StorageSourceNotification notification : notifications)
+            notifyListeners(notification);
+    }
+    
+    // IFloodlightModule
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IStorageSourceService.class);
+        return l;
+    }
+    
+    @Override
+    public Map<Class<? extends IFloodlightService>,
+               IFloodlightService> getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                            IFloodlightService>();
+        m.put(IStorageSourceService.class, this);
+        return m;
+    }
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IRestApiService.class);
+        l.add(ICounterStoreService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        restApi =
+           context.getServiceImpl(IRestApiService.class);
+        counterStore =
+            context.getServiceImpl(ICounterStoreService.class);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        restApi.addRestletRoutable(new StorageWebRoutable());
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/CompoundPredicate.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/CompoundPredicate.java
new file mode 100644
index 0000000..a23e560
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/CompoundPredicate.java
@@ -0,0 +1,52 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+/** Predicate class to handle AND and OR combinations of a number
+ * of child predicates. The result of the logical combination of the
+ * child predicates can also be negated to support a NOT operation.
+ * 
+ * @author rob
+ *
+ */
+public class CompoundPredicate implements IPredicate {
+
+    public enum Operator { AND, OR };
+    
+    private Operator operator;
+    private boolean negated;
+    private IPredicate[] predicateList;
+    
+    public CompoundPredicate(Operator operator, boolean negated, IPredicate... predicateList) {
+        this.operator = operator;
+        this.negated = negated;
+        this.predicateList = predicateList;
+    }
+    
+    public Operator getOperator() {
+        return operator;
+    }
+    
+    public boolean isNegated() {
+        return negated;
+    }
+    
+    public IPredicate[] getPredicateList() {
+        return predicateList;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IPredicate.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IPredicate.java
new file mode 100644
index 0000000..291edff
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IPredicate.java
@@ -0,0 +1,26 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+/** Common base interface for the OperatorPredicate and CompoundPredicate classes.
+ * It's necessary so that you can use either type of predicate as child
+ * predicates of a CompoundPredicate.
+ * @author rob
+ */
+public interface IPredicate {
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IQuery.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IQuery.java
new file mode 100644
index 0000000..b75b8ae
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IQuery.java
@@ -0,0 +1,39 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+/** Representation of a database query. For SQL queries this maps to
+ * a prepared statement, so it will be more efficient than if you use the
+ * methods in IStorageSource that bypass the IQuery. For many NoSQL
+ * storage sources there won't be any performance improvement from keeping
+ * around the query.
+ * 
+ * The query interface also supports parameterized queries (i.e. which maps
+ * to using ? values in a SQL query). The values of the parameters are set
+ * using the setParameter method. In the storage source API the parameters
+ * are named rather than positional. The format of the parameterized values
+ * in the query predicates is the parameter name bracketed with question marks
+ * (e.g. ?MinimumSalary? ).
+ * 
+ * @author rob
+ *
+ */
+public interface IQuery {
+    String getTableName();
+    void setParameter(String name, Object value);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IResultSet.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IResultSet.java
new file mode 100644
index 0000000..fbd2a4a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IResultSet.java
@@ -0,0 +1,106 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+import java.util.Date;
+import java.util.Map;
+
+/** Interface to iterate over the results from a storage query.
+ * 
+ * @author rob
+ *
+ */
+public interface IResultSet extends Iterable<IResultSet> {
+    
+    /** This should be called when the client is done using the result set.
+     * This will release any underlying resources (e.g. a database connection),
+     * which you don't want to wait for or rely on finalizers to release.
+     */
+    public void close();
+    
+    /** Advance to the next row in the result set. 
+     * @return Returns true if there are more rows to process
+     * (i.e. if there's a valid current row) and false if there are no more
+     * rows in the result set.
+     */
+    public boolean next();
+    
+    /** Save/commit any pending updates to the data in the result set.
+     * This must be called after any calls to the set methods or deleting rows
+     * for the changes to be applied/committed to the storage source. Note that
+     * this doesn't need to be called after each set method or even after each
+     * row. It is typically called at the end after updating all of the
+     * rows in the result set.
+     */
+    public void save();
+    
+    /** Get the current row in the result set. This returns all of the
+     * columns in the current row.
+     * @return Map containing all of the columns in the current row, indexed
+     * by the column name.
+     */
+    public Map<String,Object> getRow();
+    
+    /** Delete the current row in the result set.
+     */
+    public void deleteRow();
+    
+    public boolean containsColumn(String columnName);
+    
+    public String getString(String columnName);
+    public short getShort(String columnName);
+    public int getInt(String columnName);
+    public long getLong(String columnName);
+    public float getFloat(String columnName);
+    public double getDouble(String columnName);
+    public boolean getBoolean(String columnName);
+    public byte getByte(String columnName);
+    public byte[] getByteArray(String columnName);
+    public Date getDate(String columnName);
+    
+    public Short getShortObject(String columnName);
+    public Integer getIntegerObject(String columnName);
+    public Long getLongObject(String columnName);
+    public Float getFloatObject(String columnName);
+    public Double getDoubleObject(String columnName);
+    public Boolean getBooleanObject(String columnName);
+    public Byte getByteObject(String columnName);
+    
+    public boolean isNull(String columnName);
+    
+    public void setString(String columnName, String value);
+    public void setShort(String columnName, short value);
+    public void setInt(String columnName, int value);
+    public void setLong(String columnName, long value);
+    public void setFloat(String columnName, float value);
+    public void setDouble(String columnName, double value);
+    public void setBoolean(String columnName, boolean value);
+    public void setByte(String columnName, byte value);
+    public void setByteArray(String columnName, byte[] byteArray);
+    public void setDate(String columnName, Date date);
+    
+    public void setShortObject(String columnName, Short value);
+    public void setIntegerObject(String columnName, Integer value);
+    public void setLongObject(String columnName, Long value);
+    public void setFloatObject(String columnName, Float value);
+    public void setDoubleObject(String columnName, Double value);
+    public void setBooleanObject(String columnName, Boolean value);
+    public void setByteObject(String columnName, Byte value);
+    
+    public void setNull(String columnName);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IRowMapper.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IRowMapper.java
new file mode 100644
index 0000000..6c4502b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IRowMapper.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+/**
+ * Interface for mapping the current row in a result set to an object.
+ * This is based on the Spring JDBC support.
+ * 
+ * @author rob
+ */
+public interface IRowMapper {
+
+    /** This method must be implemented by the client of the storage API
+     * to map the current row in the result set to a Java object.
+     * 
+     * @param resultSet The result set obtained from a storage source query
+     * @return The object created from the data in the result set
+     */
+    Object mapRow(IResultSet resultSet);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageExceptionHandler.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageExceptionHandler.java
new file mode 100644
index 0000000..e3c8e94
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageExceptionHandler.java
@@ -0,0 +1,22 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+public interface IStorageExceptionHandler {
+    public void handleException(Exception exc);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceListener.java
new file mode 100644
index 0000000..ea3764d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceListener.java
@@ -0,0 +1,39 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+import java.util.Set;
+
+public interface IStorageSourceListener {
+
+    /**
+     * Called when rows are inserted or updated in the table.
+     * 
+     * @param tableName The table where the rows were inserted
+     * @param rowKeys The keys of the rows that were inserted
+     */
+    public void rowsModified(String tableName, Set<Object> rowKeys);
+    
+    /**
+     * Called when a new row is deleted from the table.
+     * 
+     * @param tableName The table where the rows were deleted
+     * @param rowKeys The keys of the rows that were deleted
+     */
+    public void rowsDeleted(String tableName, Set<Object> rowKeys);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceService.java
new file mode 100644
index 0000000..b8a1be8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceService.java
@@ -0,0 +1,331 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IStorageSourceService extends IFloodlightService {
+
+    /** Set the column to be used as the primary key for a table. This should
+     * be guaranteed to be unique for all of the rows in the table, although the
+     * storage API does not necessarily enforce this requirement. If no primary
+     * key name is specified for a table then the storage API assumes there is
+     * a column named "id" that is used as the primary key. In this case when
+     * a new row is inserted using the storage API and no id is specified
+     * explictly in the row data, the storage API automatically generates a
+     * unique ID (typically a UUID) for the id column. To work across all
+     * possible implementations of the storage API it is safest, though, to
+     * specify the primary key column explicitly.
+     * FIXME: It's sort of a kludge to have to specify the primary key column
+     * here. Ideally there would be some sort of metadata -- perhaps stored
+     * directly in the table, at least in the NoSQL case -- that the
+     * storage API could query to obtain the primary key info.
+     * @param tableName The name of the table for which we're setting the key
+     * @param primaryKeyName The name of column to be used as the primary key
+     */
+    public void setTablePrimaryKeyName(String tableName, String primaryKeyName);
+
+    /** Create a new table if one does not already exist with the given name.
+     * 
+     * @param tableName The name of the table to create.
+     * @param indexedColumns Which columns should be indexed
+     */
+    void createTable(String tableName, Set<String> indexedColumns);
+    
+    /**
+     * @return the set of all tables that have been created via createTable
+     */
+    Set<String> getAllTableNames();
+    
+    /** Create a query object representing the given query parameters. The query
+     * object can be passed to executeQuery to actually perform the query and obtain
+     * a result set.
+     * 
+     * @param tableName The name of the table to query.
+     * @param columnNames The list of columns to return in the result set.
+     * @param predicate The predicate that specifies which rows to return in the result set.
+     * @param ordering Specification of order that rows are returned from the result set
+     * returned from executing the query. If the ordering is null, then rows are returned
+     * in an implementation-specific order.
+     * @return Query object to be passed to executeQuery.
+     */
+    IQuery createQuery(String tableName, String[] columnNames, IPredicate predicate, RowOrdering ordering);
+    
+    /** Execute a query created with createQuery.
+     * 
+     * @param query The query to execute
+     * @return The result set containing the rows/columns specified in the query.
+     */
+    IResultSet executeQuery(IQuery query);
+
+    /** Execute a query created with the given query parameters.
+     *
+     * @param tableName The name of the table to query.
+     * @param columnNames The list of columns to return in the result set.
+     * @param predicate The predicate that specifies which rows to return in the result set.
+     * @param ordering Specification of order that rows are returned from the result set
+     * returned from executing the query. If the ordering is null, then rows are returned
+     * in an implementation-specific order.
+     * @return The result set containing the rows/columns specified in the query.
+     */
+    IResultSet executeQuery(String tableName, String[] columnNames, IPredicate predicate,
+            RowOrdering ordering);
+    
+    /** Execute a query and call the row mapper to map the results to Java objects.
+     * 
+     * @param tableName The name of the table to query.
+     * @param columnNames The list of columns to return in the result set.
+     * @param predicate The predicate that specifies which rows to return in the result set.
+     * @param ordering Specification of order that rows are returned from the result set
+     * returned from executing the query. If the ordering is null, then rows are returned
+     * in an implementation-specific order.
+     * @param rowMapper The client-supplied object that maps the data in a row in the result
+     * set to a client object.
+     * @return The result set containing the rows/columns specified in the query.
+     */
+    Object[] executeQuery(String tableName, String[] columnNames, IPredicate predicate,
+            RowOrdering ordering, IRowMapper rowMapper);
+    
+    /** Insert a new row in the table with the given column data.
+     * If the primary key is the default value of "id" and is not specified in the
+     * then a unique id will be automatically assigned to the row.
+     * @param tableName The name of the table to which to add the row
+     * @param values The map of column names/values to add to the table.
+     */
+    void insertRow(String tableName, Map<String,Object> values);
+
+    /** Update or insert a list of rows in the table.
+     * The primary key must be included in the map of values for each row.
+     * @param tableName The table to update or insert into
+     * @param values The map of column names/values to update the rows
+     */
+    void updateRows(String tableName, List<Map<String,Object>> rows);
+    
+    /** Update the rows in the given table. Any rows matching the predicate
+     * are updated with the column names/values specified in the values map.
+     * (The values map should not contain the special column "id".)
+     * @param tableName The table to update
+     * @param predicate The predicate to use to select which rows to update
+     * @param values The map of column names/values to update the rows.
+     */
+    void updateMatchingRows(String tableName, IPredicate predicate, Map<String,Object> values);
+    
+    /** Update or insert a row in the table with the given row key (primary
+     * key) and column names/values. (If the values map contains the special
+     * column "id", its value must match rowId.)
+     * @param tableName The table to update or insert into
+     * @param rowKey The ID (primary key) of the row to update
+     * @param values The map of column names/values to update the rows
+     */
+    void updateRow(String tableName, Object rowKey, Map<String,Object> values);
+    
+    /** Update or insert a row in the table with the given column data.
+     * The primary key must be included in the map of values.
+     * @param tableName The table to update or insert into
+     * @param values The map of column names/values to update the rows
+     */
+    void updateRow(String tableName, Map<String,Object> values);
+    
+    /** Delete the row with the given primary key.
+     * 
+     * @param tableName The table from which to delete the row
+     * @param rowKey The primary key of the row to delete.
+     */
+    void deleteRow(String tableName, Object rowKey);
+
+    /** Delete the rows with the given keys.
+     * 
+     * @param tableName The table from which to delete the rows
+     * @param rowKeys The set of primary keys of the rows to delete.
+     */
+    void deleteRows(String tableName, Set<Object> rowKeys);
+    
+    /**
+     * Delete the rows that match the predicate
+     * @param tableName
+     * @param predicate
+     */
+    void deleteMatchingRows(String tableName, IPredicate predicate);
+    
+    /** Query for a row with the given ID (primary key).
+     * 
+     * @param tableName The name of the table to query
+     * @param rowKey The primary key of the row
+     * @return The result set containing the row with the given ID
+     */
+    IResultSet getRow(String tableName, Object rowKey);
+    
+    /**
+     * Set exception handler to use for asynchronous operations.
+     * @param exceptionHandler
+     */
+    void setExceptionHandler(IStorageExceptionHandler exceptionHandler);
+    
+    /**
+     * Asynchronous variant of executeQuery.
+     * 
+     * @param query
+     * @return
+     */
+    public Future<IResultSet> executeQueryAsync(final IQuery query);
+    
+    /**
+     * Asynchronous variant of executeQuery.
+     * 
+     * @param tableName
+     * @param columnNames
+     * @param predicate
+     * @param ordering
+     * @return
+     */
+    public Future<IResultSet> executeQueryAsync(final String tableName,
+            final String[] columnNames,  final IPredicate predicate,
+            final RowOrdering ordering);
+    
+    /**
+     * Asynchronous variant of executeQuery
+     * 
+     * @param tableName
+     * @param columnNames
+     * @param predicate
+     * @param ordering
+     * @param rowMapper
+     * @return
+     */
+    public Future<Object[]> executeQueryAsync(final String tableName,
+            final String[] columnNames,  final IPredicate predicate,
+            final RowOrdering ordering, final IRowMapper rowMapper);
+    
+    /**
+     * Asynchronous variant of insertRow.
+     * 
+     * @param tableName
+     * @param values
+     * @return
+     */
+    public Future<?> insertRowAsync(final String tableName, final Map<String,Object> values);
+
+    /**
+     * Asynchronous variant of updateRows
+     * @param tableName
+     * @param rows
+     */
+    public Future<?> updateRowsAsync(final String tableName, final List<Map<String,Object>> rows);
+
+    /**
+     * Asynchronous variant of updateMatchingRows
+     * 
+     * @param tableName
+     * @param predicate
+     * @param values
+     * @return
+     */
+    public Future<?> updateMatchingRowsAsync(final String tableName, final IPredicate predicate,
+            final Map<String,Object> values);
+
+    /**
+     * Asynchronous variant of updateRow
+     * 
+     * @param tableName
+     * @param rowKey
+     * @param values
+     * @return
+     */
+    public Future<?> updateRowAsync(final String tableName, final Object rowKey,
+            final Map<String,Object> values);
+            
+    /**
+     * Asynchronous version of updateRow
+     * 
+     * @param tableName
+     * @param values
+     * @return
+     */
+    public Future<?> updateRowAsync(final String tableName, final Map<String,Object> values);
+    
+    /**
+     * Asynchronous version of deleteRow
+     * 
+     * @param tableName
+     * @param rowKey
+     * @return
+     */
+    public Future<?> deleteRowAsync(final String tableName, final Object rowKey);
+
+    /**
+     * Asynchronous version of deleteRows
+     * 
+     * @param tableName
+     * @param rowKeys
+     * @return
+     */
+    public Future<?> deleteRowsAsync(final String tableName, final Set<Object> rowKeys);
+
+    /**
+     * Asynchronous version of deleteRows
+     * 
+     * @param tableName
+     * @param predicate
+     * @return
+     */
+    public Future<?> deleteMatchingRowsAsync(final String tableName, final IPredicate predicate);
+    
+    /**
+     * Asynchronous version of getRow
+     * 
+     * @param tableName
+     * @param rowKey
+     * @return
+     */
+    public Future<?> getRowAsync(final String tableName, final Object rowKey);
+    
+    /**
+     * Asynchronous version of save
+     * 
+     * @param resultSet
+     * @return
+     */
+    public Future<?> saveAsync(final IResultSet resultSet);
+    
+    /** Add a listener to the specified table. The listener is called
+     * when any modifications are made to the table. You can add the same
+     * listener instance to multiple tables, since the table name is
+     * included as a parameter in the listener methods.
+     * @param tableName The name of the table to listen for modifications
+     * @param listener The listener instance to call
+     */
+    public void addListener(String tableName, IStorageSourceListener listener);
+    
+    /** Remove a listener from the specified table. The listener should
+     * have been previously added to the table with addListener.
+     * @param tableName The name of the table with the listener
+     * @param listener The previously installed listener instance
+     */
+    public void removeListener(String tableName, IStorageSourceListener listener);
+    
+    /** This is logically a private method and should not be called by
+     * clients of this interface.
+     * @param notifications the notifications to dispatch
+     */
+    public void notifyListeners(List<StorageSourceNotification> notifications);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/NullValueStorageException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/NullValueStorageException.java
new file mode 100644
index 0000000..0c148b8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/NullValueStorageException.java
@@ -0,0 +1,44 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+public class NullValueStorageException extends StorageException {
+
+    private static final long serialVersionUID = 897572085681189926L;
+
+    private static String makeExceptionMessage(String columnName) {
+        String message = "Null column value could not be converted to built-in type";
+        if (columnName != null) {
+            message += ": column name = ";
+            message += columnName;
+        }
+        return message;
+    }
+    
+    public NullValueStorageException() {
+        super(makeExceptionMessage(null));
+    }
+    
+    public NullValueStorageException(String columnName) {
+        super(makeExceptionMessage(columnName));
+    }
+    
+    public NullValueStorageException(String columnName, Throwable exc) {
+        super(makeExceptionMessage(columnName), exc);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/OperatorPredicate.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/OperatorPredicate.java
new file mode 100644
index 0000000..dc78260
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/OperatorPredicate.java
@@ -0,0 +1,51 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+/** Predicate class to specify rows by equality or comparison operations
+ * of column values. The Storage API uses the special column name of "id"
+ * to specify the primary key values for the row.
+ * 
+ * @author rob
+ */
+public class OperatorPredicate implements IPredicate {
+    
+    public enum Operator { EQ, LT, LTE, GT, GTE };
+    
+    private String columnName;
+    private Operator operator;
+    private Comparable<?> value;
+    
+    public OperatorPredicate(String columnName, Operator operator, Comparable<?> value) {
+        this.columnName = columnName;
+        this.operator = operator;
+        this.value = value;
+    }
+    
+    public String getColumnName() {
+        return columnName;
+    }
+    
+    public Operator getOperator() {
+        return operator;
+    }
+    
+    public Comparable<?> getValue() {
+        return value;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/ResultSetIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/ResultSetIterator.java
new file mode 100644
index 0000000..669833d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/ResultSetIterator.java
@@ -0,0 +1,64 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/** Iterator wrapper for an IResultSet, useful for iterating through query
+ * results in an enhanced for (foreach) loop.
+ * 
+ * Note that the iterator manipulates the state of the underlying IResultSet.
+ */
+public class ResultSetIterator implements Iterator<IResultSet> {
+    private IResultSet resultSet;
+    private boolean hasAnother;
+    private boolean peekedAtNext;
+    
+    public ResultSetIterator(IResultSet resultSet) {
+        this.resultSet = resultSet;
+        this.peekedAtNext = false;
+    }
+    
+    @Override
+    public IResultSet next() {
+        if (!peekedAtNext) {
+            hasAnother = resultSet.next();
+        }
+        peekedAtNext = false;
+        if (!hasAnother)
+            throw new NoSuchElementException();
+        return resultSet;
+    }
+    
+    @Override
+    public boolean hasNext() {
+        if (!peekedAtNext) {
+            hasAnother = resultSet.next();
+            peekedAtNext = true;
+        }
+        return hasAnother;
+    }
+    
+    /** Row removal is not supported; use IResultSet.deleteRow instead.
+     */
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/RowOrdering.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/RowOrdering.java
new file mode 100644
index 0000000..f9e61ed
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/RowOrdering.java
@@ -0,0 +1,119 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RowOrdering {
+    
+    public enum Direction { ASCENDING, DESCENDING };
+    
+    public class Item {
+        
+        private String column;
+        private Direction direction;
+        
+        public Item(String column, Direction direction) {
+            assert(column != null);
+            assert(direction != null);
+            this.column = column;
+            this.direction = direction;
+        }
+        
+        public String getColumn() {
+            return column;
+        }
+        
+        public Direction getDirection() {
+            return direction;
+        }
+    }
+    
+    private List<Item> itemList = new ArrayList<Item>();
+    
+    public RowOrdering() {
+    }
+    
+    public RowOrdering(String column) {
+        add(column);
+    }
+    
+    public RowOrdering(String column, Direction direction) {
+        add(column, direction);
+    }
+    
+    public RowOrdering(Item item) {
+        add(item);
+    }
+    
+    public RowOrdering(Item[] itemArray) {
+        add(itemArray);
+    }
+    
+    public RowOrdering(List<Item> itemList) {
+        add(itemList);
+    }
+    
+    public void add(String column) {
+        itemList.add(new Item(column, Direction.ASCENDING));
+    }
+    
+    public void add(String column, Direction direction) {
+        itemList.add(new Item(column, direction));
+    }
+    
+    public void add(Item item) {
+        assert(item != null);
+        itemList.add(item);
+    }
+    
+    public void add(Item[] itemArray) {
+        for (Item item: itemArray) {
+            itemList.add(item);
+        }
+    }
+    
+    public void add(List<Item> itemList) {
+        this.itemList.addAll(itemList);
+    }
+    
+    public List<Item> getItemList() {
+        return itemList;
+    }
+    
+    public boolean equals(RowOrdering rowOrdering) {
+        if (rowOrdering == null)
+            return false;
+        
+        int len1 = itemList.size();
+        int len2 = rowOrdering.getItemList().size();
+        if (len1 != len2)
+            return false;
+        
+        for (int i = 0; i < len1; i++) {
+            Item item1 = itemList.get(i);
+            Item item2 = rowOrdering.getItemList().get(i);
+            if (!item1.getColumn().equals(item2.getColumn()) ||
+                    item1.getDirection() != item2.getDirection())
+                return false;
+        }
+        
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageException.java
new file mode 100644
index 0000000..f5dea23
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageException.java
@@ -0,0 +1,44 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+public class StorageException extends RuntimeException {
+
+    static final long serialVersionUID = 7839989010156155681L;
+    
+    static private String makeExceptionMessage(String s) {
+        String message = "Storage Exception";
+        if (s != null) {
+            message += ": ";
+            message += s;
+        }
+        return message;
+    }
+
+    public StorageException() {
+        super(makeExceptionMessage(null));
+    }
+    
+    public StorageException(String s) {
+        super(makeExceptionMessage(s));
+    }
+    
+    public StorageException(String s, Throwable exc) {
+        super(makeExceptionMessage(s), exc);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageNotificationFormatException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageNotificationFormatException.java
new file mode 100644
index 0000000..f6ce565
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageNotificationFormatException.java
@@ -0,0 +1,26 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+public class StorageNotificationFormatException extends StorageException {
+    private static final long serialVersionUID = 504758477518283156L;
+
+    public StorageNotificationFormatException() {
+        super("Invalid storage notification format");
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageSourceNotification.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageSourceNotification.java
new file mode 100644
index 0000000..c9a5450
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageSourceNotification.java
@@ -0,0 +1,108 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+import java.util.Set;
+
+public class StorageSourceNotification {
+    
+    public enum Action { MODIFY, DELETE };
+    
+    private String tableName;
+    private Action action;
+    private Set<Object> keys;
+    
+    public StorageSourceNotification() {
+    }
+    
+    public StorageSourceNotification(String tableName, Action action, Set<Object> keys) {
+        this.tableName = tableName;
+        this.action = action;
+        this.keys = keys;
+    }
+    
+    public String getTableName() {
+        return tableName;
+    }
+    
+    public Action getAction() {
+        return action;
+    }
+    
+    public Set<Object> getKeys() {
+        return keys;
+    }
+    
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
+    }
+    
+    public void setAction(Action action) {
+        this.action = action;
+    }
+    
+    public void setKeys(Set<Object> keys) {
+        this.keys = keys;
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 7867;
+        int result = 1;
+        result = prime * result + tableName.hashCode();
+        result = prime * result + action.hashCode();
+        result = prime * result + keys.hashCode();
+        return result;
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (!(obj instanceof StorageSourceNotification))
+            return false;
+        StorageSourceNotification other = (StorageSourceNotification) obj;
+        if (tableName == null) {
+            if (other.tableName != null)
+                return false;
+        } else if (!tableName.equals(other.tableName))
+            return false;
+        if (action == null) {
+            if (other.action != null)
+                return false;
+        } else if (action != other.action)
+            return false;
+        if (keys == null) {
+            if (other.keys != null)
+                return false;
+        } else if (!keys.equals(other.keys))
+            return false;
+        return true;
+    }
+    
+    @Override
+    public String toString() {
+        return ("StorageNotification[table=" + tableName + "; action=" +
+                 action.toString() + "; keys=" + keys.toString() + "]");
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/SynchronousExecutorService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/SynchronousExecutorService.java
new file mode 100644
index 0000000..f1e7cd3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/SynchronousExecutorService.java
@@ -0,0 +1,177 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class SynchronousExecutorService implements ExecutorService {
+
+    class SynchronousFuture<T> implements Future<T> {
+
+        T result;
+        Exception exc;
+        
+        public SynchronousFuture() {
+        }
+        
+        public SynchronousFuture(T result) {
+            this.result = result;
+        }
+        
+        public SynchronousFuture(Exception exc) {
+            this.exc = exc;
+        }
+        
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            return false;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return false;
+        }
+
+        @Override
+        public boolean isDone() {
+            return true;
+        }
+
+        @Override
+        public T get() throws InterruptedException, ExecutionException {
+            if (exc != null)
+                throw new ExecutionException(exc);
+            return result;
+        }
+
+        @Override
+        public T get(long timeout, TimeUnit unit) throws InterruptedException,
+                ExecutionException, TimeoutException {
+            return get();
+        }
+    }
+    
+    @Override
+    public void shutdown() {
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+        return null;
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return false;
+    }
+
+    @Override
+    public boolean isTerminated() {
+        return false;
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit)
+            throws InterruptedException {
+        return false;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+            throws InterruptedException {
+        List<Future<T>> l = new ArrayList<Future<T>>();
+        for (Callable<T> task : tasks) {
+            Future<T> future = submit(task);
+            l.add(future);
+        }
+        return l;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(
+            Collection<? extends Callable<T>> tasks, long timeout, TimeUnit units)
+            throws InterruptedException {
+        return invokeAll(tasks);
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+            throws InterruptedException, ExecutionException {
+        for (Callable<T> task : tasks) {
+            try {
+                task.call();
+            } catch (Exception e) {
+
+            }
+        }
+        throw new ExecutionException(new Exception("no task completed successfully"));
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout,
+            TimeUnit units) throws InterruptedException, ExecutionException,
+            TimeoutException {
+        return invokeAny(tasks);
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> callable) {
+        try {
+            T result = callable.call();
+            return new SynchronousFuture<T>(result);
+        }
+        catch (Exception exc) {
+            return new SynchronousFuture<T>(exc);
+        }
+    }
+    
+    @Override
+    public Future<?> submit(Runnable runnable) {
+        try {
+            runnable.run();
+            return new SynchronousFuture<Void>();
+        }
+        catch (Exception exc) {
+            return new SynchronousFuture<Void>(exc);
+        }
+    }
+    
+    @Override
+    public <T> Future<T> submit(Runnable runnable, T result) {
+        try {
+            runnable.run();
+            return new SynchronousFuture<T>(result);
+        }
+        catch (Exception exc) {
+            return new SynchronousFuture<T>(exc);
+        }
+    }
+    
+    @Override
+    public void execute(Runnable command) {
+        command.run();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/TypeMismatchStorageException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/TypeMismatchStorageException.java
new file mode 100644
index 0000000..5643140
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/TypeMismatchStorageException.java
@@ -0,0 +1,42 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage;
+
+public class TypeMismatchStorageException extends StorageException {
+
+    private static final long serialVersionUID = -7923586656854871345L;
+
+    private static String makeExceptionMessage(String requestedType, String actualType, String columnName) {
+        if (requestedType == null)
+            requestedType = "???";
+        if (actualType == null)
+            actualType = "???";
+        if (columnName == null)
+            columnName = "???";
+        String message = "The requested type (" + requestedType + ") does not match the actual type (" + actualType + ") of the value for column \"" + columnName + "\".";
+        return message;
+    }
+    
+    public TypeMismatchStorageException() {
+        super(makeExceptionMessage(null, null, null));
+    }
+    
+    public TypeMismatchStorageException(String requestedType, String actualType, String columnName) {
+        super(makeExceptionMessage(requestedType, actualType, columnName));
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryStorageSource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryStorageSource.java
new file mode 100644
index 0000000..8a69eca
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryStorageSource.java
@@ -0,0 +1,198 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage.memory;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.perfmon.IPktInProcessingTimeService;
+import net.floodlightcontroller.storage.nosql.NoSqlStorageSource;
+import net.floodlightcontroller.storage.SynchronousExecutorService;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.storage.StorageException;
+
+public class MemoryStorageSource extends NoSqlStorageSource {
+    
+    private Map<String, MemoryTable> tableMap = new HashMap<String,MemoryTable>();
+    IPktInProcessingTimeService pktinProcessingTime;
+    
+    synchronized private MemoryTable getTable(String tableName, boolean create) {
+        MemoryTable table = tableMap.get(tableName);
+        if (table == null) {
+            if (!create)
+                throw new StorageException("Table " + tableName + " does not exist");
+            table = new MemoryTable(tableName);
+            tableMap.put(tableName, table);
+        }
+        return table;
+    }
+    
+    @Override
+    protected Collection<Map<String,Object>> getAllRows(String tableName, String[] columnNameList) {
+        MemoryTable table = getTable(tableName, false);
+        return table.getAllRows();
+    }
+    
+    @Override
+    protected Map<String,Object> getRow(String tableName, String[] columnNameList, Object rowKey) {
+        MemoryTable table = getTable(tableName, false);
+        return table.getRow(rowKey);
+    }
+    
+    @Override
+    protected List<Map<String,Object>> executeEqualityQuery(String tableName,
+            String[] columnNameList, String predicateColumnName, Comparable<?> value) {
+        MemoryTable table = getTable(tableName, false);
+        List<Map<String,Object>> result = new ArrayList<Map<String,Object>>();
+        synchronized (table) {
+            Collection<Map<String,Object>> allRows = table.getAllRows();
+            for (Map<String,Object> row : allRows) {
+                Object v = row.get(predicateColumnName);
+                if (value.equals(v)) {
+                    result.add(row);
+                }
+            }
+        }
+        return result;
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    protected List<Map<String,Object>> executeRangeQuery(String tableName,
+            String[] columnNameList, String predicateColumnName,
+            Comparable<?> startValue, boolean startInclusive, Comparable<?> endValue, boolean endInclusive) {
+        MemoryTable table = getTable(tableName, false);
+        List<Map<String,Object>> result = new ArrayList<Map<String,Object>>();
+        synchronized (table) {
+            Collection<Map<String,Object>> allRows = table.getAllRows();
+            for (Map<String,Object> row : allRows) {
+                Comparable value = (Comparable) row.get(predicateColumnName);
+                if (value != null) {
+                    int compareResult = value.compareTo(startValue);
+                    if ((compareResult > 0) || (startInclusive && (compareResult >= 0))) {
+                        compareResult = value.compareTo(endValue);
+                        if ((compareResult < 0) || (startInclusive && (compareResult <= 0))) {
+                            result.add(row);
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+    }
+    
+    @Override
+    protected void insertRows(String tableName, List<Map<String,Object>> insertRowList) {
+        MemoryTable table = getTable(tableName, false);
+        String primaryKeyName = getTablePrimaryKeyName(tableName);
+        synchronized (table) {
+            for (Map<String,Object> row : insertRowList) {
+                Object primaryKey = row.get(primaryKeyName);
+                if (primaryKey == null) {
+                    if (primaryKeyName.equals(DEFAULT_PRIMARY_KEY_NAME)) {
+                        row = new HashMap<String,Object>(row);
+                        primaryKey = table.getNextId();
+                        row.put(primaryKeyName, primaryKey);
+                    }
+                }
+                table.insertRow(primaryKey, row);
+            }
+        }
+    }
+    
+    @Override
+    protected void updateRows(String tableName, Set<Object> rowKeys, Map<String,Object> updateRowList) {
+        MemoryTable table = getTable(tableName, false);
+        synchronized (table) {
+            for (Object rowKey : rowKeys) {
+                Map<String,Object> row = table.getRow(rowKey);
+                if (row == null)
+                    row = table.newRow(rowKey);
+                for (Map.Entry<String,Object> entry: updateRowList.entrySet()) {
+                    row.put(entry.getKey(), entry.getValue());
+                }
+            }
+        }
+    }
+    
+    @Override
+    protected void updateRowsImpl(String tableName, List<Map<String,Object>> updateRowList) {
+        MemoryTable table = getTable(tableName, false);
+        String primaryKeyName = getTablePrimaryKeyName(tableName);
+        synchronized (table) {
+            for (Map<String,Object> updateRow : updateRowList) {
+                Object rowKey = updateRow.get(primaryKeyName);
+                if (rowKey == null)
+                    throw new StorageException("Primary key not found.");
+                Map<String,Object> row = table.getRow(rowKey);
+                if (row == null)
+                    row = table.newRow(rowKey);
+                for (Map.Entry<String,Object> entry: updateRow.entrySet()) {
+                    row.put(entry.getKey(), entry.getValue());
+                }
+            }
+        }
+    }
+    
+    @Override
+    protected void deleteRowsImpl(String tableName, Set<Object> rowKeys) {
+        MemoryTable table = getTable(tableName, false);
+        synchronized (table) {
+            for (Object rowKey : rowKeys) {
+                table.deleteRow(rowKey);
+            }
+        }
+    }
+    
+    @Override
+    public void createTable(String tableName, Set<String> indexedColumnNames) {
+        super.createTable(tableName, indexedColumnNames);
+        getTable(tableName, true);
+    }
+    
+    public void setPktinProcessingTime(
+            IPktInProcessingTimeService pktinProcessingTime) {
+        this.pktinProcessingTime = pktinProcessingTime;
+    }
+
+    // IFloodlightModule methods
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        super.startUp(context);
+        executorService = new SynchronousExecutorService();
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>,
+               IFloodlightService> getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m =
+                new HashMap<Class<? extends IFloodlightService>,
+                            IFloodlightService>();
+        m.put(IStorageSourceService.class, this);
+        return m;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryTable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryTable.java
new file mode 100644
index 0000000..f87ee45
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryTable.java
@@ -0,0 +1,72 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage.memory;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class MemoryTable {
+
+    private String tableName;
+    private Map<Object,Map<String,Object>> rowMap;
+    private int nextId;
+    
+    MemoryTable(String tableName) {
+        this.tableName = tableName;
+        rowMap = new TreeMap<Object,Map<String,Object>>();
+        nextId = 0;
+    }
+    
+    String getTableName() {
+        return tableName;
+    }
+    
+    Collection<Map<String,Object>> getAllRows() {
+        return rowMap.values();
+    }
+    
+    Map<String,Object> getRow(Object key) {
+        Map<String,Object> row = rowMap.get(key);
+        return row;
+    }
+    
+    // rkv: Do we still need this? Probably needs to be tweaked a bit
+    // to work with the support for specifying which column to use as the
+    // primary key
+    Map<String,Object> newRow(Object key) {
+        Map<String,Object> row = new HashMap<String, Object>();
+        row.put("id", key);
+        rowMap.put(key, row);
+        return row;
+    }
+    
+    void insertRow(Object key, Map<String,Object> rowValues) {
+        assert(key != null);
+        rowMap.put(key, rowValues);
+    }
+    
+    void deleteRow(Object rowKey) {
+        rowMap.remove(rowKey);
+    }
+    
+    Integer getNextId() {
+        return new Integer(++nextId);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlQuery.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlQuery.java
new file mode 100644
index 0000000..05f8fc7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlQuery.java
@@ -0,0 +1,77 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage.nosql;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.storage.IPredicate;
+import net.floodlightcontroller.storage.IQuery;
+import net.floodlightcontroller.storage.RowOrdering;
+
+public class NoSqlQuery implements IQuery {
+
+    private String tableName;
+    private String[] columnNameList;
+    private IPredicate predicate;
+    private RowOrdering rowOrdering;
+    private Map<String,Comparable<?>> parameterMap;
+    
+    NoSqlQuery(String className, String[] columnNameList, IPredicate predicate, RowOrdering rowOrdering) {
+        this.tableName = className;
+        this.columnNameList = columnNameList;
+        this.predicate = predicate;
+        this.rowOrdering = rowOrdering;
+    }
+    
+    @Override
+    public void setParameter(String name, Object value) {
+        if (parameterMap == null)
+            parameterMap = new HashMap<String,Comparable<?>>();
+        parameterMap.put(name, (Comparable<?>)value);
+    }
+
+    @Override
+    public String getTableName() {
+        return tableName;
+    }
+    
+    String[] getColumnNameList() {
+        return columnNameList;
+    }
+    
+    IPredicate getPredicate() {
+        return predicate;
+    }
+    
+    RowOrdering getRowOrdering() {
+        return rowOrdering;
+    }
+    
+    Comparable<?> getParameter(String name) {
+        Comparable<?> value = null;
+        if (parameterMap != null) {
+            value = parameterMap.get(name);
+        }
+        return value;
+    }
+    
+    Map<String,Comparable<?>> getParameterMap() {
+        return parameterMap;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlResultSet.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlResultSet.java
new file mode 100644
index 0000000..b3a8c20
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlResultSet.java
@@ -0,0 +1,487 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage.nosql;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TimeZone;
+
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.NullValueStorageException;
+import net.floodlightcontroller.storage.ResultSetIterator;
+import net.floodlightcontroller.storage.StorageException;
+import net.floodlightcontroller.storage.TypeMismatchStorageException;
+
+public class NoSqlResultSet implements IResultSet {
+
+    NoSqlStorageSource storageSource;
+    String tableName;
+    String primaryKeyName;
+    List<Map<String,Object>> rowList;
+    int currentIndex;
+    Map<String,Object> currentRowUpdate;
+    List<Map<String,Object>> rowUpdateList;
+    Set<Object> rowDeleteSet;
+    Iterator<IResultSet> resultSetIterator;
+    
+    NoSqlResultSet(NoSqlStorageSource storageSource, String tableName, List<Map<String,Object>> rowList) {
+        this.storageSource = storageSource;
+        this.primaryKeyName = storageSource.getTablePrimaryKeyName(tableName);
+        this.tableName = tableName;
+        if (rowList == null)
+            rowList = new ArrayList<Map<String,Object>>();
+        this.rowList = rowList;
+        currentIndex = -1;
+    }
+    
+    void addRow(Map<String,Object> row) {
+        rowList.add(row);
+    }
+    
+    @Override
+    public Map<String,Object> getRow() {
+        if ((currentIndex < 0) || (currentIndex >= rowList.size())) {
+            throw new StorageException("No current row in result set.");
+        }
+        
+        return rowList.get(currentIndex);
+    }
+
+    @Override
+    public boolean containsColumn(String columnName) {
+        return getObject(columnName) != null;
+    }
+        
+    @Override
+    public void close() {
+    }
+
+    private void endCurrentRowUpdate() {
+        if (currentRowUpdate != null) {
+            if (rowUpdateList == null)
+                rowUpdateList = new ArrayList<Map<String,Object>>();
+            rowUpdateList.add(currentRowUpdate);
+            currentRowUpdate = null;
+        }
+    }
+    
+    @Override
+    public boolean next() {
+        endCurrentRowUpdate();
+        currentIndex++;
+        return currentIndex < rowList.size();
+    }
+
+    @Override
+    public void save() {
+        endCurrentRowUpdate();
+        
+        if (rowUpdateList != null) {
+            storageSource.updateRows(tableName, rowUpdateList);
+            rowUpdateList = null;
+        }
+        
+        if (rowDeleteSet != null) {
+            storageSource.deleteRows(tableName, rowDeleteSet);
+            rowDeleteSet = null;
+        }
+    }
+
+    Object getObject(String columnName) {
+        Map<String,Object> row = rowList.get(currentIndex);
+        Object value = row.get(columnName);
+        return value;
+    }
+    
+    @Override
+    public boolean getBoolean(String columnName) {
+        Boolean b = getBooleanObject(columnName);
+        if (b == null)
+            throw new NullValueStorageException(columnName);
+        return b.booleanValue();
+    }
+
+    @Override
+    public byte getByte(String columnName) {
+        Byte b = getByteObject(columnName);
+        if (b == null)
+            throw new NullValueStorageException(columnName);
+        return b.byteValue();
+    }
+
+    @Override
+    public byte[] getByteArray(String columnName) {
+        byte[] b = null;
+        Object obj = getObject(columnName);
+        if (obj != null) {
+            if (!(obj instanceof byte[]))
+                throw new StorageException("Invalid byte array value");
+            b = (byte[])obj;
+        }
+        return b;
+    }
+
+    @Override
+    public double getDouble(String columnName) {
+        Double d = getDoubleObject(columnName);
+        if (d == null)
+            throw new NullValueStorageException(columnName);
+        return d.doubleValue();
+    }
+
+    @Override
+    public float getFloat(String columnName) {
+        Float f = getFloatObject(columnName);
+        if (f == null)
+            throw new NullValueStorageException(columnName);
+        return f.floatValue();
+    }
+
+    @Override
+    public int getInt(String columnName) {
+        Integer i = getIntegerObject(columnName);
+        if (i == null)
+            throw new NullValueStorageException(columnName);
+        return i.intValue();
+    }
+
+    @Override
+    public long getLong(String columnName) {
+        Long l = getLongObject(columnName);
+        if (l == null)
+            throw new NullValueStorageException(columnName);
+        return l.longValue();
+    }
+
+    @Override
+    public short getShort(String columnName) {
+        Short s = getShortObject(columnName);
+        if (s == null)
+            throw new NullValueStorageException(columnName);
+        return s.shortValue();
+    }
+
+    @Override
+    public String getString(String columnName) {
+        Object obj = getObject(columnName);
+        if (obj == null)
+            return null;
+        return obj.toString();
+    }
+
+    @Override
+    public Date getDate(String column) {
+        Date d;
+        Object obj = getObject(column);
+        if (obj == null) {
+            d = null;
+        } else if (obj instanceof Date) {
+            d = (Date) obj;
+        } else {
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+            dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+            try {
+                d = dateFormat.parse(obj.toString());
+            }
+            catch (ParseException exc) {
+                throw new TypeMismatchStorageException(Date.class.getName(), obj.getClass().getName(), column);
+            }
+        }
+        return d;
+    }
+
+
+    @Override
+    public Short getShortObject(String columnName)
+    {
+        Short s;
+        Object obj = getObject(columnName);
+        if (obj instanceof Short) {
+            s = (Short)obj;
+        } else if (obj != null) {
+            try {
+                s = Short.parseShort(obj.toString());
+            }
+            catch (NumberFormatException exc) {
+                throw new TypeMismatchStorageException(Short.class.getName(), obj.getClass().getName(), columnName);
+            }
+        } else {
+            s = null;
+        }
+        return s;
+    }
+    
+    @Override
+    public Integer getIntegerObject(String columnName)
+    {
+        Integer i;
+        Object obj = getObject(columnName);
+        if (obj instanceof Integer) {
+            i = (Integer)obj;
+        } else if (obj != null) {
+            try {
+                i = Integer.parseInt(obj.toString());
+            }
+            catch (NumberFormatException exc) {
+                throw new TypeMismatchStorageException(Integer.class.getName(), obj.getClass().getName(), columnName);
+            }
+        } else {
+            i = null;
+        }
+        return i;
+    }
+
+    @Override
+    public Long getLongObject(String columnName)
+    {
+        Long l;
+        Object obj = getObject(columnName);
+        if (obj instanceof Long) {
+            l = (Long)obj;
+        } else if (obj != null) {
+            try {
+                l = Long.parseLong(obj.toString());
+            }
+            catch (NumberFormatException exc) {
+                throw new TypeMismatchStorageException(Long.class.getName(), obj.getClass().getName(), columnName);
+            }
+        } else {
+            l = null;
+        }
+        return l;
+    }
+
+    @Override
+    public Float getFloatObject(String columnName)
+    {
+        Float f;
+        Object obj = getObject(columnName);
+        if (obj instanceof Float) {
+            f = (Float)obj;
+        } else if (obj != null) {
+            try {
+                f = Float.parseFloat(obj.toString());
+            }
+            catch (NumberFormatException exc) {
+                throw new TypeMismatchStorageException(Float.class.getName(), obj.getClass().getName(), columnName);
+            }
+        } else {
+            f = null;
+        }
+        return f;
+    }
+
+    @Override
+    public Double getDoubleObject(String columnName)
+    {
+        Double d;
+        Object obj = getObject(columnName);
+        if (obj instanceof Double) {
+            d = (Double)obj;
+        } else if (obj != null) {
+            try {
+                d = Double.parseDouble(obj.toString());
+            }
+            catch (NumberFormatException exc) {
+                throw new TypeMismatchStorageException(Double.class.getName(), obj.getClass().getName(), columnName);
+            }
+        } else {
+            d = null;
+        }
+        return d;
+    }
+
+    @Override
+    public Boolean getBooleanObject(String columnName)
+    {
+        Boolean b;
+        Object obj = getObject(columnName);
+        if (obj instanceof Boolean) {
+            b = (Boolean)obj;
+        } else if (obj != null) {
+            try {
+                b = Boolean.parseBoolean(obj.toString());
+            }
+            catch (NumberFormatException exc) {
+                throw new TypeMismatchStorageException(Boolean.class.getName(), obj.getClass().getName(), columnName);
+            }
+        } else {
+            b = null;
+        }
+        return b;
+    }
+
+    @Override
+    public Byte getByteObject(String columnName)
+    {
+        Byte b;
+        Object obj = getObject(columnName);
+        if (obj instanceof Byte) {
+            b = (Byte)obj;
+        } else if (obj != null) {
+            try {
+                b = Byte.parseByte(obj.toString());
+            }
+            catch (NumberFormatException exc) {
+                throw new TypeMismatchStorageException(Byte.class.getName(), obj.getClass().getName(), columnName);
+            }
+        } else {
+            b = null;
+        }
+        return b;
+    }
+
+    
+    @Override
+    public boolean isNull(String columnName)
+    {
+        Object obj = getObject(columnName);
+        return (obj == null);
+    }
+
+    private void addRowUpdate(String column, Object value) {
+        if (currentRowUpdate == null) {
+            currentRowUpdate = new HashMap<String,Object>();
+            Object key = rowList.get(currentIndex).get(primaryKeyName);
+            currentRowUpdate.put(primaryKeyName, key);
+        }
+        currentRowUpdate.put(column, value);
+    }
+    
+    @Override
+    public void setBoolean(String columnName, boolean value) {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setByte(String columnName, byte value) {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setByteArray(String columnName, byte[] byteArray) {
+        addRowUpdate(columnName, byteArray);
+    }
+
+    @Override
+    public void setDouble(String columnName, double value) {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setFloat(String columnName, float value) {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setInt(String columnName, int value) {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setLong(String columnName, long value) {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setShort(String columnName, short value) {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setString(String columnName, String value) {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setShortObject(String columnName, Short value)
+    {
+        addRowUpdate(columnName, value);
+    }
+    
+    @Override
+    public void setIntegerObject(String columnName, Integer value)
+    {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setLongObject(String columnName, Long value)
+    {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setFloatObject(String columnName, Float value)
+    {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setDoubleObject(String columnName, Double value)
+    {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setBooleanObject(String columnName, Boolean value)
+    {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setByteObject(String columnName, Byte value)
+    {
+        addRowUpdate(columnName, value);
+    }
+
+    @Override
+    public void setDate(String column, Date value) {
+        addRowUpdate(column, value);
+    }
+
+    
+    public void setNull(String columnName)
+    {
+        addRowUpdate(columnName, null);
+    }
+
+    
+    @Override
+    public void deleteRow() {
+        Object key = (String) rowList.get(currentIndex).get(primaryKeyName);
+        if (rowDeleteSet == null)
+            rowDeleteSet = new HashSet<Object>();
+        rowDeleteSet.add(key);
+    }
+    
+    @Override
+    public Iterator<IResultSet> iterator() {
+        if (resultSetIterator == null)
+            resultSetIterator = new ResultSetIterator(this);
+        return resultSetIterator;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlStorageSource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlStorageSource.java
new file mode 100644
index 0000000..d7e5f95
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlStorageSource.java
@@ -0,0 +1,823 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage.nosql;
+
+import java.lang.Class;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.storage.AbstractStorageSource;
+import net.floodlightcontroller.storage.CompoundPredicate;
+import net.floodlightcontroller.storage.IPredicate;
+import net.floodlightcontroller.storage.IQuery;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.OperatorPredicate;
+import net.floodlightcontroller.storage.RowOrdering;
+import net.floodlightcontroller.storage.StorageException;
+import net.floodlightcontroller.storage.StorageSourceNotification;
+import net.floodlightcontroller.storage.TypeMismatchStorageException;
+
+public abstract class NoSqlStorageSource extends AbstractStorageSource {
+    protected static Logger log = LoggerFactory.getLogger(NoSqlStorageSource.class);
+
+    public enum ColumnIndexMode { NOT_INDEXED, RANGE_INDEXED, EQUALITY_INDEXED };
+    
+    protected static final String DEFAULT_PRIMARY_KEY_NAME = "id";
+    
+    private Map<String,String> tablePrimaryKeyMap = new HashMap<String,String>();
+    private Map<String, Map<String,ColumnIndexMode>> tableIndexedColumnMap =
+        new HashMap<String,Map<String,ColumnIndexMode>>();
+    
+    abstract class NoSqlPredicate {
+
+        public boolean incorporateComparison(String columnName,
+                OperatorPredicate.Operator operator, Comparable<?> value,
+                CompoundPredicate.Operator parentOperator) {
+            return false;
+        }
+        
+        public boolean canExecuteEfficiently() {
+            return false;
+        }
+        
+        public List<Map<String,Object>> execute(String[] columnNames) {
+            assert(false);
+            return null;
+        }
+        
+        abstract public boolean matchesRow(Map<String,Object> row);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    class NoSqlRangePredicate extends NoSqlPredicate {
+        NoSqlStorageSource storageSource;
+        String tableName;
+        String columnName;
+        Comparable<?> startValue;
+        boolean startInclusive;
+        Comparable<?> endValue;
+        boolean endInclusive;
+        
+        NoSqlRangePredicate(NoSqlStorageSource storageSource, String tableName,
+                String columnName, Comparable<?> startValue, boolean startInclusive,
+                Comparable<?> endValue, boolean endInclusive) {
+            this.storageSource = storageSource;
+            this.tableName = tableName;
+            this.columnName = columnName;
+            this.startValue = startValue;
+            this.startInclusive = startInclusive;
+            this.endValue = endValue;
+            this.endInclusive = endInclusive;
+        }
+        
+        public boolean incorporateComparison(String columnName,
+                OperatorPredicate.Operator operator, Comparable<?> value,
+                CompoundPredicate.Operator parentOperator) {
+            
+            assert(operator != null);
+            assert(parentOperator != null);
+            
+            // Must be the same column to incorporate
+            if (!this.columnName.equals(columnName))
+                return false;
+            
+            // The only time we allow a null value is if it's an EQ operator.
+            // In that case we can only incorporate if this predicate is also
+            // a null equality predicate.
+            if (value == null) {
+                return ((operator == OperatorPredicate.Operator.EQ) &&
+                        (startValue == null) && (endValue == null) &&
+                        startInclusive && endInclusive);
+            }
+            
+            // Don't incorporate parameterized values
+            if (value instanceof String) {
+                String s = (String)value;
+                if (s.startsWith("?") && s.endsWith("?")) {
+                    return false;
+                }
+            }
+            
+            if (parentOperator == CompoundPredicate.Operator.AND) {
+                switch (operator) {
+                case EQ:
+                    if (matchesValue(value)) {
+                        startValue = endValue = value;
+                        startInclusive = endInclusive = true;
+                        return true;
+                    }
+                    break;
+                case LT:
+                    if ((endValue == null) || (((Comparable)value).compareTo(endValue) <= 0)) {
+                        endValue = value;
+                        endInclusive = false;
+                        return true;
+                    }
+                    break;
+                case LTE:
+                    if ((endValue == null) || (((Comparable)value).compareTo(endValue) < 0)) {
+                        endValue = value;
+                        endInclusive = true;
+                        return true;
+                    }
+                    break;
+                case GT:
+                    if ((startValue == null) || (((Comparable)value).compareTo(startValue) >= 0)) {
+                        startValue = value;
+                        startInclusive = false;
+                        return true;
+                    }
+                    break;
+                case GTE:
+                    if ((startValue == null) || (((Comparable)value).compareTo(startValue) > 0)) {
+                        startValue = value;
+                        startInclusive = true;
+                        return true;
+                    }
+                    break;
+                }
+            } else {
+                switch (operator) {
+                case EQ:
+                    if (matchesValue(value))
+                        return true;
+                    break;
+                case LT:
+                    if ((endValue == null) || (((Comparable)value).compareTo(endValue) > 0)) {
+                        endValue = value;
+                        endInclusive = false;
+                        return true;
+                    }
+                    break;
+                case LTE:
+                    if ((endValue == null) || (((Comparable)value).compareTo(endValue) >= 0)) {
+                        endValue = value;
+                        endInclusive = true;
+                        return true;
+                    }
+                    break;
+                case GT:
+                    if ((startValue == null) || (((Comparable)value).compareTo(startValue) < 0)) {
+                        startValue = value;
+                        startInclusive = false;
+                        return true;
+                    }
+                    break;
+                case GTE:
+                    if ((startValue == null) || (((Comparable)value).compareTo(startValue) <= 0)) {
+                        startValue = value;
+                        startInclusive = true;
+                        return true;
+                    }
+                    break;
+                }
+            }
+            
+            return false;
+        }
+
+        private boolean isEqualityRange() {
+            return (startValue == endValue) && startInclusive && endInclusive;
+        }
+        
+        public boolean canExecuteEfficiently() {
+            ColumnIndexMode indexMode = storageSource.getColumnIndexMode(tableName, columnName);
+            switch (indexMode) {
+            case NOT_INDEXED:
+                return false;
+            case RANGE_INDEXED:
+                return true;
+            case EQUALITY_INDEXED:
+                return isEqualityRange();
+            }
+            return true;
+        }
+
+        public List<Map<String,Object>> execute(String columnNameList[]) {
+            List<Map<String,Object>> rowList;
+            if (isEqualityRange())
+                rowList = storageSource.executeEqualityQuery(tableName, columnNameList, columnName, startValue);
+            else
+                rowList = storageSource.executeRangeQuery(tableName, columnNameList, columnName,
+                        startValue, startInclusive, endValue, endInclusive);
+                
+            return rowList;
+        }
+        
+        Comparable<?> coerceValue(Comparable<?> value, Class targetClass) {
+            
+            if (value == null)
+                return null;
+            
+            if (value.getClass() == targetClass)
+                return value;
+            
+            // FIXME: For now we convert by first converting the source value to a
+            // string and then converting to the target type. This logic probably needs
+            // another pass to make it more robust/optimized.
+            
+            String s = value.toString();
+            Comparable<?> obj = null;
+            
+            try {
+                if (targetClass == Integer.class) {
+                    obj = new Integer(s);
+                } else if (targetClass == Long.class) {
+                    obj = new Long(s);
+                } else if (targetClass == Short.class) {
+                    obj = new Short(s);
+                } else if (targetClass == Boolean.class) {
+                    obj = new Boolean(s);
+                } else if (targetClass == Float.class) {
+                    obj = new Float(s);
+                } else if (targetClass == Double.class) {
+                    obj = new Double(s);
+                } else if (targetClass == Byte.class) {
+                    obj = new Byte(s);
+                } else if (targetClass == String.class) {
+                    obj = s;
+                } else if (targetClass == Date.class) {
+                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+                    dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+                    try {
+                        obj = dateFormat.parse(s);
+                    }
+                    catch (ParseException exc) {
+                        throw new TypeMismatchStorageException(Date.class.getName(), value.getClass().getName(), "???");
+                    }
+                }
+            }
+            catch (Exception exc) {
+                // Ignore the exception here. In this case obj will not be set, so we'll
+                // throw the StorageException below when we check for a null obj.
+            }
+            
+            if (obj == null)
+                throw new StorageException("Column value could not be coerced to the correct type");
+            
+            return obj;
+        }
+        
+        boolean matchesValue(Comparable<?> value) {
+            boolean isNullEqPredicate = (startValue == null) && (endValue == null) && startInclusive && endInclusive;
+            if (value == null)
+                return isNullEqPredicate;
+
+            if (isNullEqPredicate)
+                return false;
+            
+            int result;
+            Comparable<?> coercedValue;
+            if (startValue != null) {
+                coercedValue = coerceValue(value, startValue.getClass());
+                result = ((Comparable)coercedValue).compareTo(startValue);
+                if ((result < 0) || (!startInclusive && (result == 0)))
+                    return false;
+            }
+            if (endValue != null) {
+                coercedValue = coerceValue(value, endValue.getClass());
+                result = ((Comparable)coercedValue).compareTo(endValue);
+                if ((result > 0) || (!endInclusive && (result == 0)))
+                    return false;
+            }
+            return true;
+        }
+        
+        public boolean matchesRow(Map<String,Object> row) {
+            Comparable value = (Comparable)row.get(columnName);
+            return matchesValue(value);
+        }
+    }
+    
+    class NoSqlOperatorPredicate extends NoSqlPredicate {
+        
+        NoSqlStorageSource storageSource;
+        String columnName;
+        OperatorPredicate.Operator operator;
+        Object value;
+        
+        NoSqlOperatorPredicate(NoSqlStorageSource storageSource, String columnName,
+                OperatorPredicate.Operator operator, Object value) {
+            this.storageSource = storageSource;
+            this.columnName = columnName;
+            this.operator = operator;
+            this.value = value;
+        }
+
+        public boolean incorporateComparison(String columnName,
+                OperatorPredicate.Operator operator, Comparable<?> value,
+                CompoundPredicate.Operator parentOperator) {
+            return false;
+        }
+
+        public boolean canExecuteEfficiently() {
+            return false;
+        }
+
+        public List<Map<String,Object>> execute(String columnNames[]) {
+            throw new StorageException("Unimplemented predicate.");
+        }
+        
+        public boolean matchesRow(Map<String,Object> row) {
+            return false;
+        }
+    }
+    
+    class NoSqlCompoundPredicate extends NoSqlPredicate {
+        
+        NoSqlStorageSource storageSource;
+        CompoundPredicate.Operator operator;
+        boolean negated;
+        List<NoSqlPredicate> predicateList;
+        
+        NoSqlCompoundPredicate(NoSqlStorageSource storageSource, CompoundPredicate.Operator operator,
+                boolean negated, List<NoSqlPredicate> predicateList) {
+            this.storageSource = storageSource;
+            this.operator = operator;
+            this.negated = negated;
+            this.predicateList = predicateList;
+        }
+
+        public boolean incorporateComparison(String columnName,
+                OperatorPredicate.Operator operator, Comparable<?> value,
+                CompoundPredicate.Operator parentOperator) {
+            // It may be possible to incorporate other operator predicate into this one,
+            // but it would need to take into account the negated attribute and I'd need
+            // to think about it some more to make sure it was correct, so for now we just
+            // disallow incorporation
+            //if (parentOperator == this.operator) {
+            //    for (NoSqlPredicate predicate: predicateList) {
+            //        if (predicate.incorporateComparison(columnName, operator, value, parentOperator))
+            //            return true;
+            //    }
+            //}
+            return false;
+        }
+
+        public boolean canExecuteEfficiently() {
+            if (operator == CompoundPredicate.Operator.AND) {
+                for (NoSqlPredicate predicate: predicateList) {
+                    if (predicate.canExecuteEfficiently()) {
+                        return true;
+                    }
+                }
+                return false;
+            } else {
+                for (NoSqlPredicate predicate: predicateList) {
+                    if (!predicate.canExecuteEfficiently()) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        class RowComparator implements Comparator<Map<String,Object>> {
+            private String primaryKeyName;
+            
+            public RowComparator(String primaryKeyName) {
+                this.primaryKeyName = primaryKeyName;
+            }
+            
+            public int compare(Map<String,Object> row1, Map<String,Object> row2) {
+                Comparable key1 = (Comparable)row1.get(primaryKeyName);
+                Comparable key2 = (Comparable)row2.get(primaryKeyName);
+                return key1.compareTo(key2);
+            }
+            
+            public boolean equals(Object obj) {
+                if (!(obj instanceof RowComparator))
+                    return false;
+                RowComparator rc = (RowComparator)obj;
+                if (rc.primaryKeyName == null)
+                    return this.primaryKeyName == null;
+                return rc.primaryKeyName.equals(this.primaryKeyName);
+            }
+        }
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        private List<Map<String,Object>> combineRowLists(String primaryKeyName,
+                List<Map<String,Object>> list1, List<Map<String,Object>> list2,
+                CompoundPredicate.Operator operator) {
+            ArrayList<Map<String,Object>> combinedRowList = new ArrayList<Map<String,Object>>();
+            RowComparator rc = new RowComparator(primaryKeyName);
+            Collections.sort(list1, rc);
+            Collections.sort(list2,rc);
+            
+            Iterator<Map<String,Object>> iterator1 = list1.iterator();
+            Iterator<Map<String,Object>> iterator2 = list2.iterator();
+            boolean update1 = true;
+            boolean update2 = true;
+            Map<String,Object> row1 = null;
+            Map<String,Object> row2 = null;
+            Comparable<?> key1 = null;
+            Comparable<?> key2 = null;
+            
+            while (true) {
+                if (update1) {
+                    if (iterator1.hasNext()) {
+                        row1 = iterator1.next();
+                        key1 = (Comparable<?>)row1.get(primaryKeyName);
+                    } else {
+                        row1 = null;
+                    }
+                }
+                if (update2) {
+                    if (iterator2.hasNext()) {
+                        row2 = iterator1.next();
+                        key2 = (Comparable<?>)row2.get(primaryKeyName);
+                    } else {
+                        row2 = null;
+                    }
+                }
+                if (operator == CompoundPredicate.Operator.AND) {
+                    if ((row1 == null) || (row2 == null))
+                        break;
+                    if (key1.equals(key2))
+                        combinedRowList.add(row1);
+                } else {
+                    if (row1 == null) {
+                        if (row2 == null)
+                            break;
+                        combinedRowList.add(row2);
+                    } else if ((row2 == null) || (((Comparable)key1).compareTo(key2) <= 0)) {
+                        combinedRowList.add(row2);
+                    } else {
+                        combinedRowList.add(row1);
+                    }
+                }
+                
+                update1 = (key2 == null) || (((Comparable)key1).compareTo(key2) <= 0);
+                update2 = (key1 == null) || (((Comparable)key2).compareTo(key1) <= 0);
+            }
+            
+            return combinedRowList;
+        }
+        
+        public List<Map<String,Object>> execute(String columnNames[]) {
+            List<Map<String,Object>> combinedRowList = null;
+            for (NoSqlPredicate predicate: predicateList) {
+                List<Map<String,Object>> rowList = predicate.execute(columnNames);
+                if (combinedRowList != null) {
+                    combinedRowList = combineRowLists("id", combinedRowList, rowList, operator);
+                } else {
+                    combinedRowList = rowList;
+                }
+            }
+            return combinedRowList;
+        }
+
+        public boolean matchesRow(Map<String,Object> row) {
+            if (operator == CompoundPredicate.Operator.AND) {
+                for (NoSqlPredicate predicate : predicateList) {
+                    if (!predicate.matchesRow(row))  {
+                        return false;
+                    }
+                }
+                return true;
+            } else {
+                for (NoSqlPredicate predicate : predicateList) {
+                    if (predicate.matchesRow(row))  {
+                        return true;
+                    }
+                }
+                return false;
+                
+            }
+        }
+    }
+    
+    public NoSqlStorageSource() {
+        super();
+    }
+    
+    @Override
+    public void createTable(String tableName, Set<String> indexedColumns) {
+        super.createTable(tableName, indexedColumns);
+        if (indexedColumns == null) return;
+        for (String columnName : indexedColumns) {
+            setColumnIndexMode(tableName, columnName,
+                               ColumnIndexMode.EQUALITY_INDEXED);
+        }
+    }
+
+    public void setTablePrimaryKeyName(String tableName, String primaryKeyName) {
+        if ((tableName == null) || (primaryKeyName == null))
+            throw new NullPointerException();
+        tablePrimaryKeyMap.put(tableName, primaryKeyName);
+    }
+    
+    protected String getTablePrimaryKeyName(String tableName) {
+        String primaryKeyName = tablePrimaryKeyMap.get(tableName);
+        if (primaryKeyName == null)
+            primaryKeyName = DEFAULT_PRIMARY_KEY_NAME;
+        return primaryKeyName;
+    }
+    
+    protected ColumnIndexMode getColumnIndexMode(String tableName, String columnName) {
+        ColumnIndexMode columnIndexMode = null;
+        Map<String, ColumnIndexMode> indexedColumnMap = tableIndexedColumnMap.get(tableName);
+        if (indexedColumnMap != null)
+            columnIndexMode = indexedColumnMap.get(columnName);
+        if (columnIndexMode == null)
+            return ColumnIndexMode.NOT_INDEXED;
+        return columnIndexMode;
+    }
+    
+    public void setColumnIndexMode(String tableName, String columnName, ColumnIndexMode indexMode) {
+        Map<String, ColumnIndexMode> indexedColumnMap = tableIndexedColumnMap.get(tableName);
+        if (indexedColumnMap == null) {
+            indexedColumnMap = new HashMap<String,ColumnIndexMode>();
+            tableIndexedColumnMap.put(tableName, indexedColumnMap);
+        }
+        indexedColumnMap.put(columnName, indexMode);
+    }
+    
+    Comparable<?> getOperatorPredicateValue(OperatorPredicate predicate, Map<String,Comparable<?>> parameterMap) {
+        Comparable<?> value = predicate.getValue();
+        if (value instanceof String) {
+            String stringValue = (String) value;
+            if ((stringValue.charAt(0) == '?') && (stringValue.charAt(stringValue.length()-1) == '?')) {
+                String parameterName = stringValue.substring(1,stringValue.length()-1);
+                value = parameterMap.get(parameterName);
+            }
+        }
+        return value;
+    }
+    
+    NoSqlPredicate convertPredicate(IPredicate predicate, String tableName, Map<String,Comparable<?>> parameterMap) {
+        if (predicate == null)
+            return null;
+        NoSqlPredicate convertedPredicate = null;
+        if (predicate instanceof CompoundPredicate) {
+            CompoundPredicate compoundPredicate = (CompoundPredicate)predicate;
+            ArrayList<NoSqlPredicate> noSqlPredicateList = new ArrayList<NoSqlPredicate>();
+            for (IPredicate childPredicate: compoundPredicate.getPredicateList()) {
+                boolean incorporated = false;
+                if (childPredicate instanceof OperatorPredicate) {
+                    OperatorPredicate childOperatorPredicate = (OperatorPredicate)childPredicate;
+                    for (NoSqlPredicate childNoSqlPredicate: noSqlPredicateList) {
+                        incorporated = childNoSqlPredicate.incorporateComparison(
+                                childOperatorPredicate.getColumnName(), childOperatorPredicate.getOperator(),
+                                getOperatorPredicateValue(childOperatorPredicate, parameterMap),
+                                compoundPredicate.getOperator());
+                        if (incorporated)
+                            break;
+                    }
+                }
+                if (!incorporated) {
+                    NoSqlPredicate noSqlPredicate = convertPredicate(childPredicate, tableName, parameterMap);
+                    noSqlPredicateList.add(noSqlPredicate);
+                }
+            }
+            convertedPredicate = new NoSqlCompoundPredicate(this, compoundPredicate.getOperator(),
+                    compoundPredicate.isNegated(), noSqlPredicateList);
+        } else if (predicate instanceof OperatorPredicate) {
+            OperatorPredicate operatorPredicate = (OperatorPredicate) predicate;
+            Comparable<?> value = getOperatorPredicateValue(operatorPredicate, parameterMap);
+            switch (operatorPredicate.getOperator()) {
+            case EQ:
+                convertedPredicate = new NoSqlRangePredicate(this, tableName,
+                        operatorPredicate.getColumnName(), value, true, value, true);
+                break;
+            case LT:
+                convertedPredicate = new NoSqlRangePredicate(this, tableName,
+                        operatorPredicate.getColumnName(), null, false, value, false);
+                break;
+            case LTE:
+                convertedPredicate = new NoSqlRangePredicate(this, tableName,
+                        operatorPredicate.getColumnName(), null, false, value, true);
+                break;
+            case GT:
+                convertedPredicate = new NoSqlRangePredicate(this, tableName,
+                        operatorPredicate.getColumnName(), value, false, null, false);
+                break;
+            case GTE:
+                convertedPredicate = new NoSqlRangePredicate(this, tableName,
+                        operatorPredicate.getColumnName(), value, true, null, false);
+                break;
+            default:
+                convertedPredicate = new NoSqlOperatorPredicate(this, operatorPredicate.getColumnName(),
+                        operatorPredicate.getOperator(), value);
+            }
+        } else {
+            throw new StorageException("Unknown predicate type");
+        }
+        
+        return convertedPredicate;
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    class RowComparator implements Comparator<Map<String,Object>> {
+        private RowOrdering rowOrdering;
+        
+        public RowComparator(RowOrdering rowOrdering) {
+            this.rowOrdering = rowOrdering;
+        }
+        
+        public int compare(Map<String,Object> row1, Map<String,Object> row2) {
+            if (rowOrdering == null)
+                return 0;
+            
+            for (RowOrdering.Item item: rowOrdering.getItemList()) {
+                Comparable key1 = (Comparable)row1.get(item.getColumn());
+                Comparable key2 = (Comparable)row2.get(item.getColumn());
+                int result = key1.compareTo(key2);
+                if (result != 0) {
+                    if (item.getDirection() == RowOrdering.Direction.DESCENDING)
+                        result = -result;
+                    return result;
+                }
+            }
+            
+            return 0;
+        }
+        
+        public boolean equals(Object obj) {
+            if (!(obj instanceof RowComparator))
+                return false;
+            RowComparator rc = (RowComparator)obj;
+            if (rc.rowOrdering == null)
+                return this.rowOrdering == null;
+            return rc.rowOrdering.equals(this.rowOrdering);
+        }
+    }
+    
+    private NoSqlResultSet executeParameterizedQuery(String tableName, String[] columnNameList,
+            IPredicate predicate, RowOrdering rowOrdering, Map<String,Comparable<?>> parameterMap) {
+        NoSqlPredicate noSqlPredicate = convertPredicate(predicate, tableName, parameterMap);
+        List<Map<String,Object>> rowList;
+        if ((noSqlPredicate != null) && noSqlPredicate.canExecuteEfficiently()) {
+            rowList = noSqlPredicate.execute(columnNameList);
+        } else {
+            rowList = new ArrayList<Map<String,Object>>();
+            Collection<Map<String,Object>> allRowList = getAllRows(tableName, columnNameList);
+            for (Map<String,Object> row: allRowList) {
+                if ((noSqlPredicate == null) || noSqlPredicate.matchesRow(row)) {
+                    rowList.add(row);
+                }
+            }
+        }
+        if (rowOrdering != null)
+            Collections.sort(rowList, new RowComparator(rowOrdering));
+            
+        return new NoSqlResultSet(this, tableName, rowList);
+    }
+    
+    @Override
+    public IQuery createQuery(String tableName, String[] columnNameList,
+            IPredicate predicate, RowOrdering rowOrdering) {
+        return new NoSqlQuery(tableName, columnNameList, predicate, rowOrdering);
+    }
+
+    @Override
+    public IResultSet executeQueryImpl(IQuery query) {
+        NoSqlQuery noSqlQuery = (NoSqlQuery) query;
+        return executeParameterizedQuery(noSqlQuery.getTableName(),
+                noSqlQuery.getColumnNameList(), noSqlQuery.getPredicate(),
+                noSqlQuery.getRowOrdering(), noSqlQuery.getParameterMap());
+    }
+
+    protected void sendNotification(String tableName, StorageSourceNotification.Action action,
+            List<Map<String,Object>> rows) {
+        Set<Object> rowKeys = new HashSet<Object>();
+        String primaryKeyName = getTablePrimaryKeyName(tableName);
+        for (Map<String,Object> row : rows) {
+            Object rowKey = row.get(primaryKeyName);
+            rowKeys.add(rowKey);
+        }
+        StorageSourceNotification notification =
+            new StorageSourceNotification(tableName, action, rowKeys);
+        notifyListeners(notification);
+    }
+    
+    protected void sendNotification(String tableName,
+            StorageSourceNotification.Action action, Set<Object> rowKeys) {
+        StorageSourceNotification notification =
+            new StorageSourceNotification(tableName, action, rowKeys);
+        notifyListeners(notification);
+    }
+    
+    protected void insertRowsAndNotify(String tableName, List<Map<String,Object>> insertRowList) {
+        insertRows(tableName, insertRowList);
+        sendNotification(tableName, StorageSourceNotification.Action.MODIFY, insertRowList);
+    }
+
+    @Override
+    public void insertRowImpl(String tableName, Map<String, Object> values) {
+        ArrayList<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>();
+        rowList.add(values);
+        insertRowsAndNotify(tableName, rowList);
+    }
+
+    protected void updateRowsAndNotify(String tableName, Set<Object> rowKeys, Map<String,Object> updateRowList) {
+        updateRows(tableName, rowKeys, updateRowList);
+        sendNotification(tableName, StorageSourceNotification.Action.MODIFY, rowKeys);
+    }
+
+    protected void updateRowsAndNotify(String tableName, List<Map<String,Object>> updateRowList) {
+        updateRows(tableName, updateRowList);
+        sendNotification(tableName, StorageSourceNotification.Action.MODIFY, updateRowList);
+    }
+
+    @Override
+    public void updateMatchingRowsImpl(String tableName, IPredicate predicate, Map<String,Object> values) {
+        String primaryKeyName = getTablePrimaryKeyName(tableName);
+        String[] columnNameList = {primaryKeyName};
+        IResultSet resultSet = executeQuery(tableName, columnNameList, predicate, null);
+        Set<Object> rowKeys = new HashSet<Object>();
+        while (resultSet.next()) {
+            String rowKey = resultSet.getString(primaryKeyName);
+            rowKeys.add(rowKey);
+        }
+        updateRowsAndNotify(tableName, rowKeys, values);
+    }
+    
+    @Override
+    public void updateRowImpl(String tableName, Object rowKey, Map<String,Object> values) {
+        Map<String,Object> valuesWithKey = new HashMap<String,Object>(values);
+        String primaryKeyName = getTablePrimaryKeyName(tableName);
+        valuesWithKey.put(primaryKeyName, rowKey);
+        List<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>();
+        rowList.add(valuesWithKey);
+        updateRowsAndNotify(tableName, rowList);
+    }
+
+   @Override
+    public void updateRowImpl(String tableName, Map<String,Object> values) {
+        List<Map<String,Object>> rowKeys = new ArrayList<Map<String,Object>>();
+        rowKeys.add(values);
+        updateRowsAndNotify(tableName, rowKeys);
+    }
+
+   protected void deleteRowsAndNotify(String tableName, Set<Object> rowKeyList) {
+       deleteRows(tableName, rowKeyList);
+       sendNotification(tableName, StorageSourceNotification.Action.DELETE, rowKeyList);
+   }
+
+    @Override
+    public void deleteRowImpl(String tableName, Object key) {
+        HashSet<Object> keys = new HashSet<Object>();
+        keys.add(key);
+        deleteRowsAndNotify(tableName, keys);
+    }
+
+    @Override
+    public IResultSet getRowImpl(String tableName, Object rowKey) {
+        List<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>();
+        Map<String,Object> row = getRow(tableName, null, rowKey);
+        if (row != null)
+            rowList.add(row);
+        NoSqlResultSet resultSet = new NoSqlResultSet(this, tableName, rowList);
+        return resultSet;
+    }
+   
+    // Below are the methods that must be implemented by the subclasses
+    
+    protected abstract Collection<Map<String,Object>> getAllRows(String tableName, String[] columnNameList);
+    
+    protected abstract Map<String,Object> getRow(String tableName, String[] columnNameList, Object rowKey);
+    
+    protected abstract List<Map<String,Object>> executeEqualityQuery(String tableName,
+            String[] columnNameList, String predicateColumnName, Comparable<?> value);
+    
+    protected abstract List<Map<String,Object>> executeRangeQuery(String tableName,
+            String[] columnNameList, String predicateColumnName,
+            Comparable<?> startValue, boolean startInclusive, Comparable<?> endValue, boolean endInclusive);
+    
+    protected abstract void insertRows(String tableName, List<Map<String,Object>> insertRowList);
+    
+    protected abstract void updateRows(String tableName, Set<Object> rowKeys, Map<String,Object> updateColumnMap);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java
new file mode 100644
index 0000000..fcfa96f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java
@@ -0,0 +1,55 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage.web;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.StorageSourceNotification;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class StorageNotifyResource extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(StorageNotifyResource.class);
+    
+    @Post("json")
+    public Map<String,Object> notify(String entity) throws Exception {
+        List<StorageSourceNotification> notifications = null;
+        ObjectMapper mapper = new ObjectMapper();
+        notifications = 
+            mapper.readValue(entity, 
+                    new TypeReference<List<StorageSourceNotification>>(){});
+        
+        IStorageSourceService storageSource = 
+            (IStorageSourceService)getContext().getAttributes().
+                get(IStorageSourceService.class.getCanonicalName());
+        storageSource.notifyListeners(notifications);
+        
+        HashMap<String, Object> model = new HashMap<String,Object>();
+        model.put("output", "OK");
+        return model;
+    }
+    
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageWebRoutable.java
new file mode 100644
index 0000000..681847d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageWebRoutable.java
@@ -0,0 +1,45 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage.web;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+/**
+ * Creates a router to handle the storage web URIs
+ * @author readams
+ *
+ */
+public class StorageWebRoutable implements RestletRoutable {
+
+    @Override
+    public String basePath() {
+        return "/wm/storage";
+    }
+
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/notify/json", StorageNotifyResource.class);
+        return router;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/IThreadPoolService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/IThreadPoolService.java
new file mode 100644
index 0000000..a537a3a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/IThreadPoolService.java
@@ -0,0 +1,15 @@
+package net.floodlightcontroller.threadpool;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IThreadPoolService extends IFloodlightService {
+    /**
+     * Get the master scheduled thread pool executor maintained by the
+     * ThreadPool provider.  This can be used by other modules as a centralized
+     * way to schedule tasks.
+     * @return
+     */
+    public ScheduledExecutorService getScheduledExecutor();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java
new file mode 100644
index 0000000..aa426a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java
@@ -0,0 +1,64 @@
+package net.floodlightcontroller.threadpool;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public class ThreadPool implements IThreadPoolService, IFloodlightModule {
+    protected ScheduledExecutorService executor = null;
+    
+    // IThreadPoolService
+
+    @Override
+    public ScheduledExecutorService getScheduledExecutor() {
+        return executor;
+    }
+    
+    // IFloodlightModule
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IThreadPoolService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                    IFloodlightService>();
+        m.put(IThreadPoolService.class, this);
+        // We are the class that implements the service
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        // No dependencies
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+                                 throws FloodlightModuleException {
+        executor = Executors.newScheduledThreadPool(15);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // no-op
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/Cluster.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/Cluster.java
new file mode 100644
index 0000000..606b079
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/Cluster.java
@@ -0,0 +1,79 @@
+package net.floodlightcontroller.topology;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.routing.Link;
+
+import org.openflow.util.HexString;
+
+public class Cluster {
+    protected long id; // the lowest id of the nodes
+    protected Map<Long, Set<Link>> links; // set of links connected to a node.
+
+    public Cluster() {
+        id = Long.MAX_VALUE;
+        links = new HashMap<Long, Set<Link>>();
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public Map<Long, Set<Link>> getLinks() {
+        return links;
+    }
+
+    public Set<Long> getNodes() {
+        return links.keySet();
+    }
+
+    void add(long n) {
+        if (links.containsKey(n) == false) {
+            links.put(n, new HashSet<Link>());
+            if (n < id) id = n;
+        }
+    }
+
+    void addLink(Link l) {
+        if (links.containsKey(l.getSrc()) == false) {
+            links.put(l.getSrc(), new HashSet<Link>());
+            if (l.getSrc() < id) id = l.getSrc();
+        }
+        links.get(l.getSrc()).add(l);
+
+        if (links.containsKey(l.getDst()) == false) {
+            links.put(l.getDst(), new HashSet<Link>());
+            if (l.getDst() < id) id = l.getDst();
+        }
+        links.get(l.getDst()).add(l);
+     }
+
+    @Override 
+    public int hashCode() {
+        return (int) (id + id >>>32);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+
+        Cluster other = (Cluster) obj;
+        return (this.id == other.id);
+    }
+    
+    public String toString() {
+        return "[Cluster id=" + HexString.toHexString(id) + ", " + links.keySet() + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyListener.java
new file mode 100644
index 0000000..9f06992
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyListener.java
@@ -0,0 +1,8 @@
+package net.floodlightcontroller.topology;
+
+public interface ITopologyListener {
+    /**
+     * Happens when the switch clusters are recomputed
+     */
+    void topologyChanged();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyService.java
new file mode 100644
index 0000000..cc62e82
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyService.java
@@ -0,0 +1,198 @@
+package net.floodlightcontroller.topology;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
+
+public interface ITopologyService extends IFloodlightService  {
+
+    public void addListener(ITopologyListener listener);
+
+    public Date getLastUpdateTime();
+
+    /**
+     * Query to determine if devices must be learned on a given switch port.
+     */
+    public boolean isAttachmentPointPort(long switchid, short port);
+    public boolean isAttachmentPointPort(long switchid, short port,
+                                         boolean tunnelEnabled);
+
+    public long getOpenflowDomainId(long switchId);
+    public long getOpenflowDomainId(long switchId, boolean tunnelEnabled);
+
+    /**
+     * Returns the identifier of the L2 domain of a given switch.
+     * @param switchId The DPID of the switch in long form
+     * @return The DPID of the switch that is the key for the cluster
+     */
+    public long getL2DomainId(long switchId);
+    public long getL2DomainId(long switchId, boolean tunnelEnabled);
+
+    /**
+     * Queries whether two switches are in the same cluster.
+     * @param switch1
+     * @param switch2
+     * @return true if the switches are in the same cluster
+     */
+    public boolean inSameOpenflowDomain(long switch1, long switch2);
+    public boolean inSameOpenflowDomain(long switch1, long switch2, 
+                                        boolean tunnelEnabled);
+
+    /**
+     * Queries whether two switches are in the same island.
+     * Currently, island and cluster are the same. In future,
+     * islands could be different than clusters.
+     * @param switch1
+     * @param switch2
+     * @return True of they are in the same island, false otherwise
+     */
+    public boolean inSameL2Domain(long switch1, long switch2);
+    public boolean inSameL2Domain(long switch1, long switch2, 
+                                  boolean tunnelEnabled);
+
+    public boolean isBroadcastDomainPort(long sw, short port);
+    public boolean isBroadcastDomainPort(long sw, short port, 
+                                         boolean tunnelEnabled);
+
+
+    public boolean isAllowed(long sw, short portId);
+    public boolean isAllowed(long sw, short portId, boolean tunnelEnabled);
+
+    /**
+     * Indicates if an attachment point on the new switch port is consistent
+     * with the attachment point on the old switch port or not.
+     */
+    public boolean isConsistent(long oldSw, short oldPort,
+                                long newSw, short newPort);
+    public boolean isConsistent(long oldSw, short oldPort,
+                                long newSw, short newPort,
+                                boolean tunnelEnabled);
+
+    /**
+     * Indicates if the two switch ports are connected to the same
+     * broadcast domain or not.
+     * @param s1
+     * @param p1
+     * @param s2
+     * @param p2
+     * @return
+     */
+    public boolean isInSameBroadcastDomain(long s1, short p1, 
+                                           long s2, short p2);
+    public boolean isInSameBroadcastDomain(long s1, short p1,
+                                           long s2, short p2,
+                                           boolean tunnelEnabled);
+
+    /**
+     * Gets a list of ports on a given switch that are known to topology.
+     * @param sw The switch DPID in long
+     * @return The set of ports on this switch
+     */
+    public Set<Short> getPortsWithLinks(long sw);
+    public Set<Short> getPortsWithLinks(long sw, boolean tunnelEnabled);
+
+    /** Get broadcast ports on a target switch for a given attachmentpoint
+     * point port.
+     */
+    public Set<Short> getBroadcastPorts(long targetSw, long src, short srcPort);
+
+    public Set<Short> getBroadcastPorts(long targetSw, long src, short srcPort,
+                                        boolean tunnelEnabled);
+
+    /**
+     * 
+     */
+    public boolean isIncomingBroadcastAllowed(long sw, short portId);
+    public boolean isIncomingBroadcastAllowed(long sw, short portId,
+                                              boolean tunnelEnabled);
+
+
+    /** Get the proper outgoing switchport for a given pair of src-dst
+     * switchports.
+     */
+    public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort);
+
+
+    public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort,
+                                               boolean tunnelEnabled);
+
+
+    public NodePortTuple getIncomingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort);
+    public NodePortTuple getIncomingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort,
+                                               boolean tunnelEnabled);
+
+    /**
+     * If the dst is not allowed by the higher-level topology,
+     * this method provides the topologically equivalent broadcast port.
+     * @param src
+     * @param dst
+     * @return the allowed broadcast port
+     */
+    public NodePortTuple 
+    getAllowedOutgoingBroadcastPort(long src,
+                                    short srcPort,
+                                    long dst,
+                                    short dstPort);
+
+    public NodePortTuple 
+    getAllowedOutgoingBroadcastPort(long src,
+                                    short srcPort,
+                                    long dst,
+                                    short dstPort,
+                                    boolean tunnelEnabled);
+
+    /**
+     * If the src broadcast domain port is not allowed for incoming
+     * broadcast, this method provides the topologically equivalent
+     * incoming broadcast-allowed
+     * src port.  
+     * @param src
+     * @param dst
+     * @return the allowed broadcast port
+     */
+    public NodePortTuple
+    getAllowedIncomingBroadcastPort(long src,
+                                    short srcPort);
+
+    public NodePortTuple
+    getAllowedIncomingBroadcastPort(long src,
+                                    short srcPort,
+                                    boolean tunnelEnabled);
+
+
+    /**
+     * Gets the set of ports that belong to a broadcast domain.
+     * @return The set of ports that belong to a broadcast domain.
+     */
+    public Set<NodePortTuple> getBroadcastDomainPorts();
+    public Set<NodePortTuple> getTunnelPorts();
+
+
+    /**
+     * Returns a set of blocked ports.  The set of blocked
+     * ports is the union of all the blocked ports across all
+     * instances.
+     * @return
+     */
+    public Set<NodePortTuple> getBlockedPorts();
+
+    /**
+     * ITopologyListener provides topologyChanged notification, 
+     * but not *what* the changes were.  
+     * This method returns the delta in the linkUpdates between the current and the previous topology instance.
+     * @return
+     */
+    public List<LDUpdate> getLastLinkUpdates();
+
+    /**
+     * Switch methods
+     */
+    public Set<Short> getPorts(long sw);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePair.java
new file mode 100644
index 0000000..ff954a0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePair.java
@@ -0,0 +1,53 @@
+package net.floodlightcontroller.topology;
+
+public class NodePair {
+    private long min;
+    private long max;
+
+    public NodePair(long a, long b) {
+        if (a < b) {
+            min = a; 
+            max = b;
+        } else {
+            min = b;
+            max = a;
+        }
+    }
+
+    public long getNode() {
+        return min;
+    }
+
+    public long getOtherNode() {
+        return max;
+    }
+
+    public String toString() {
+        return "[" + new Long(min) + ", " + new Long(max) + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (int) (max ^ (max >>> 32));
+        result = prime * result + (int) (min ^ (min >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        NodePair other = (NodePair) obj;
+        if (max != other.max)
+            return false;
+        if (min != other.min)
+            return false;
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java
new file mode 100644
index 0000000..4983529
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java
@@ -0,0 +1,90 @@
+package net.floodlightcontroller.topology;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.core.web.serializers.UShortSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+
+/**
+ * A NodePortTuple is similar to a SwitchPortTuple
+ * but it only stores IDs instead of references
+ * to the actual objects.
+ * @author srini
+ */
+public class NodePortTuple {
+    protected long nodeId; // switch DPID
+    protected short portId; // switch port id
+
+    /**
+     * Creates a NodePortTuple
+     * @param nodeId The DPID of the switch
+     * @param portId The port of the switch
+     */
+    public NodePortTuple(long nodeId, short portId) {
+        this.nodeId = nodeId;
+        this.portId = portId;
+    }
+
+    public NodePortTuple(long nodeId, int portId) {
+        this.nodeId = nodeId;
+        this.portId = (short) portId;
+    }
+
+    @JsonProperty("switch")
+    @JsonSerialize(using=DPIDSerializer.class)
+    public long getNodeId() {
+        return nodeId;
+    }
+    public void setNodeId(long nodeId) {
+        this.nodeId = nodeId;
+    }
+    @JsonProperty("port")
+    @JsonSerialize(using=UShortSerializer.class)
+    public short getPortId() {
+        return portId;
+    }
+    public void setPortId(short portId) {
+        this.portId = portId;
+    }
+    
+    public String toString() {
+        return "[id=" + HexString.toHexString(nodeId) + ", port=" + new Short(portId) + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (int) (nodeId ^ (nodeId >>> 32));
+        result = prime * result + portId;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        NodePortTuple other = (NodePortTuple) obj;
+        if (nodeId != other.nodeId)
+            return false;
+        if (portId != other.portId)
+            return false;
+        return true;
+    }
+    
+    /**
+     * API to return a String value formed wtih NodeID and PortID
+     * The portID is a 16-bit field, so mask it as an integer to get full
+     * positive value
+     * @return
+     */
+    public String toKeyString() {
+        return (HexString.toHexString(nodeId)+ "|" + (portId & 0xffff));
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/OrderedNodePair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/OrderedNodePair.java
new file mode 100644
index 0000000..af9e677
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/OrderedNodePair.java
@@ -0,0 +1,49 @@
+package net.floodlightcontroller.topology;
+
+public class OrderedNodePair {
+    private long src;
+    private long dst;
+
+    public OrderedNodePair(long s, long d) {
+        src = s;
+        dst = d;
+    }
+
+    public long getSrc() {
+        return src;
+    }
+
+    public long getDst() {
+        return dst;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 2417;
+        int result = 1;
+        result = prime * result + (int) (dst ^ (dst >>> 32));
+        result = prime * result + (int) (src ^ (src >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        OrderedNodePair other = (OrderedNodePair) obj;
+        if (dst != other.dst)
+            return false;
+        if (src != other.src)
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "OrderedNodePair [src=" + src + ", dst=" + dst + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java
new file mode 100644
index 0000000..85ac6b8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java
@@ -0,0 +1,782 @@
+package net.floodlightcontroller.topology;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.util.ClusterDFS;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.routing.BroadcastTree;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.routing.RouteId;
+import net.floodlightcontroller.util.LRUHashMap;
+
+/**
+ * A representation of a network topology.  Used internally by 
+ * {@link TopologyManager}
+ */
+@LogMessageCategory("Network Topology")
+public class TopologyInstance {
+
+    public static final short LT_SH_LINK = 1;
+    public static final short LT_BD_LINK = 2;
+    public static final short LT_TUNNEL  = 3; 
+
+    public static final int MAX_LINK_WEIGHT = 10000;
+    public static final int MAX_PATH_WEIGHT = Integer.MAX_VALUE - MAX_LINK_WEIGHT - 1;
+    public static final int PATH_CACHE_SIZE = 1000;
+
+    protected static Logger log = LoggerFactory.getLogger(TopologyInstance.class);
+
+    protected Map<Long, Set<Short>> switchPorts; // Set of ports for each switch
+    /** Set of switch ports that are marked as blocked.  A set of blocked
+     * switch ports may be provided at the time of instantiation. In addition,
+     * we may add additional ports to this set.
+     */
+    protected Set<NodePortTuple> blockedPorts;  
+    protected Map<NodePortTuple, Set<Link>> switchPortLinks; // Set of links organized by node port tuple
+    /** Set of links that are blocked. */
+    protected Set<Link> blockedLinks;
+
+    protected Set<Long> switches;
+    protected Set<NodePortTuple> broadcastDomainPorts;
+    protected Set<NodePortTuple> tunnelPorts;
+
+    protected Set<Cluster> clusters;  // set of openflow domains
+    protected Map<Long, Cluster> switchClusterMap; // switch to OF domain map
+
+    // States for routing
+    protected Map<Long, BroadcastTree> destinationRootedTrees;
+    protected Map<Long, Set<NodePortTuple>> clusterBroadcastNodePorts;
+    protected Map<Long, BroadcastTree> clusterBroadcastTrees;
+    protected LRUHashMap<RouteId, Route> pathcache;
+
+    public TopologyInstance() {
+        this.switches = new HashSet<Long>();
+        this.switchPorts = new HashMap<Long, Set<Short>>();
+        this.switchPortLinks = new HashMap<NodePortTuple, Set<Link>>();
+        this.broadcastDomainPorts = new HashSet<NodePortTuple>();
+        this.tunnelPorts = new HashSet<NodePortTuple>();
+        this.blockedPorts = new HashSet<NodePortTuple>();
+        this.blockedLinks = new HashSet<Link>();
+    }
+    
+    public TopologyInstance(Map<Long, Set<Short>> switchPorts,
+                            Map<NodePortTuple, Set<Link>> switchPortLinks)
+    {
+        this.switches = new HashSet<Long>(switchPorts.keySet());
+        this.switchPorts = new HashMap<Long, Set<Short>>(switchPorts);
+        this.switchPortLinks = new HashMap<NodePortTuple, 
+                                           Set<Link>>(switchPortLinks);
+        this.broadcastDomainPorts = new HashSet<NodePortTuple>();
+        this.tunnelPorts = new HashSet<NodePortTuple>();
+        this.blockedPorts = new HashSet<NodePortTuple>();
+        this.blockedLinks = new HashSet<Link>();
+        
+        clusters = new HashSet<Cluster>();
+        switchClusterMap = new HashMap<Long, Cluster>();
+    }
+    public TopologyInstance(Map<Long, Set<Short>> switchPorts,
+                            Set<NodePortTuple> blockedPorts,
+                            Map<NodePortTuple, Set<Link>> switchPortLinks,
+                            Set<NodePortTuple> broadcastDomainPorts,
+                            Set<NodePortTuple> tunnelPorts){
+
+        // copy these structures
+        this.switches = new HashSet<Long>(switchPorts.keySet());
+        this.switchPorts = new HashMap<Long, Set<Short>>();
+        for(long sw: switchPorts.keySet()) {
+            this.switchPorts.put(sw, new HashSet<Short>(switchPorts.get(sw)));
+        }
+
+        this.blockedPorts = new HashSet<NodePortTuple>(blockedPorts);
+        this.switchPortLinks = new HashMap<NodePortTuple, Set<Link>>();
+        for(NodePortTuple npt: switchPortLinks.keySet()) {
+            this.switchPortLinks.put(npt, 
+                                     new HashSet<Link>(switchPortLinks.get(npt)));
+        }
+        this.broadcastDomainPorts = new HashSet<NodePortTuple>(broadcastDomainPorts);
+        this.tunnelPorts = new HashSet<NodePortTuple>(tunnelPorts);
+
+        blockedLinks = new HashSet<Link>();
+        clusters = new HashSet<Cluster>();
+        switchClusterMap = new HashMap<Long, Cluster>();
+        destinationRootedTrees = new HashMap<Long, BroadcastTree>();
+        clusterBroadcastTrees = new HashMap<Long, BroadcastTree>();
+        clusterBroadcastNodePorts = new HashMap<Long, Set<NodePortTuple>>();
+        pathcache = new LRUHashMap<RouteId, Route>(PATH_CACHE_SIZE);
+    }
+
+    public void compute() {
+
+        // Step 1: Compute clusters ignoring broadcast domain links
+        // Create nodes for clusters in the higher level topology
+        // Must ignore blocked links.
+        identifyOpenflowDomains();
+
+        // Step 0: Remove all links connected to blocked ports.
+        // removeLinksOnBlockedPorts();
+
+
+        // Step 1.1: Add links to clusters
+        // Avoid adding blocked links to clusters
+        addLinksToOpenflowDomains();
+
+        // Step 2. Compute shortest path trees in each cluster for 
+        // unicast routing.  The trees are rooted at the destination.
+        // Cost for tunnel links and direct links are the same.
+        calculateShortestPathTreeInClusters();
+
+        // Step 3. Compute broadcast tree in each cluster.
+        // Cost for tunnel links are high to discourage use of 
+        // tunnel links.  The cost is set to the number of nodes
+        // in the cluster + 1, to use as minimum number of 
+        // clusters as possible.
+        calculateBroadcastNodePortsInClusters();
+
+        // Step 4. print topology.
+        // printTopology();
+    }
+
+    public void printTopology() {
+        log.trace("-----------------------------------------------");
+        log.trace("Links: {}",this.switchPortLinks);
+        log.trace("broadcastDomainPorts: {}", broadcastDomainPorts);
+        log.trace("tunnelPorts: {}", tunnelPorts);
+        log.trace("clusters: {}", clusters);
+        log.trace("destinationRootedTrees: {}", destinationRootedTrees);
+        log.trace("clusterBroadcastNodePorts: {}", clusterBroadcastNodePorts);
+        log.trace("-----------------------------------------------");
+    }
+
+    protected void addLinksToOpenflowDomains() {
+        for(long s: switches) {
+            if (switchPorts.get(s) == null) continue;
+            for (short p: switchPorts.get(s)) {
+                NodePortTuple np = new NodePortTuple(s, p);
+                if (switchPortLinks.get(np) == null) continue;
+                if (isBroadcastDomainPort(np)) continue;
+                for(Link l: switchPortLinks.get(np)) {
+                    if (isBlockedLink(l)) continue;
+                    if (isBroadcastDomainLink(l)) continue;
+                    Cluster c1 = switchClusterMap.get(l.getSrc());
+                    Cluster c2 = switchClusterMap.get(l.getDst());
+                    if (c1 ==c2) {
+                        c1.addLink(l);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @author Srinivasan Ramasubramanian
+     *
+     * This function divides the network into clusters. Every cluster is
+     * a strongly connected component. The network may contain unidirectional
+     * links.  The function calls dfsTraverse for performing depth first
+     * search and cluster formation.
+     *
+     * The computation of strongly connected components is based on
+     * Tarjan's algorithm.  For more details, please see the Wikipedia
+     * link below.
+     *
+     * http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+     */
+    @LogMessageDoc(level="ERROR",
+            message="No DFS object for switch {} found.",
+            explanation="The internal state of the topology module is corrupt",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    public void identifyOpenflowDomains() {
+        Map<Long, ClusterDFS> dfsList = new HashMap<Long, ClusterDFS>();
+
+        if (switches == null) return;
+
+        for (Long key: switches) {
+            ClusterDFS cdfs = new ClusterDFS();
+            dfsList.put(key, cdfs);
+        }
+        Set<Long> currSet = new HashSet<Long>();
+
+        for (Long sw: switches) {
+            ClusterDFS cdfs = dfsList.get(sw);
+            if (cdfs == null) {
+                log.error("No DFS object for switch {} found.", sw);
+            }else if (!cdfs.isVisited()) {
+                dfsTraverse(0, 1, sw, dfsList, currSet);
+            }
+        }
+    }
+
+
+    /**
+     * @author Srinivasan Ramasubramanian
+     *
+     * This algorithm computes the depth first search (DFS) traversal of the
+     * switches in the network, computes the lowpoint, and creates clusters
+     * (of strongly connected components).
+     *
+     * The computation of strongly connected components is based on
+     * Tarjan's algorithm.  For more details, please see the Wikipedia
+     * link below.
+     *
+     * http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+     *
+     * The initialization of lowpoint and the check condition for when a
+     * cluster should be formed is modified as we do not remove switches that
+     * are already part of a cluster.
+     *
+     * A return value of -1 indicates that dfsTraverse failed somewhere in the middle
+     * of computation.  This could happen when a switch is removed during the cluster
+     * computation procedure.
+     *
+     * @param parentIndex: DFS index of the parent node
+     * @param currIndex: DFS index to be assigned to a newly visited node
+     * @param currSw: ID of the current switch
+     * @param dfsList: HashMap of DFS data structure for each switch
+     * @param currSet: Set of nodes in the current cluster in formation
+     * @return long: DSF index to be used when a new node is visited
+     */
+
+    private long dfsTraverse (long parentIndex, long currIndex, long currSw,
+                              Map<Long, ClusterDFS> dfsList, Set <Long> currSet) {
+
+        //Get the DFS object corresponding to the current switch
+        ClusterDFS currDFS = dfsList.get(currSw);
+        // Get all the links corresponding to this switch
+
+
+        //Assign the DFS object with right values.
+        currDFS.setVisited(true);
+        currDFS.setDfsIndex(currIndex);
+        currDFS.setParentDFSIndex(parentIndex);
+        currIndex++;
+
+        // Traverse the graph through every outgoing link.
+        if (switchPorts.get(currSw) != null){
+            for(Short p: switchPorts.get(currSw)) {
+                Set<Link> lset = switchPortLinks.get(new NodePortTuple(currSw, p));
+                if (lset == null) continue;
+                for(Link l:lset) {
+                    long dstSw = l.getDst();
+
+                    // ignore incoming links.
+                    if (dstSw == currSw) continue;
+
+                    // ignore if the destination is already added to 
+                    // another cluster
+                    if (switchClusterMap.get(dstSw) != null) continue;
+
+                    // ignore the link if it is blocked.
+                    if (isBlockedLink(l)) continue;
+
+                    // ignore this link if it is in broadcast domain
+                    if (isBroadcastDomainLink(l)) continue;
+
+                    // Get the DFS object corresponding to the dstSw
+                    ClusterDFS dstDFS = dfsList.get(dstSw);
+
+                    if (dstDFS.getDfsIndex() < currDFS.getDfsIndex()) {
+                        // could be a potential lowpoint
+                        if (dstDFS.getDfsIndex() < currDFS.getLowpoint())
+                            currDFS.setLowpoint(dstDFS.getDfsIndex());
+
+                    } else if (!dstDFS.isVisited()) {
+                        // make a DFS visit
+                        currIndex = dfsTraverse(currDFS.getDfsIndex(), currIndex, dstSw,
+                                                dfsList, currSet);
+
+                        if (currIndex < 0) return -1;
+
+                        // update lowpoint after the visit
+                        if (dstDFS.getLowpoint() < currDFS.getLowpoint())
+                            currDFS.setLowpoint(dstDFS.getLowpoint());
+                    }
+                    // else, it is a node already visited with a higher
+                    // dfs index, just ignore.
+                }
+            }
+        }
+
+        // Add current node to currSet.
+        currSet.add(currSw);
+
+        // Cluster computation.
+        // If the node's lowpoint is greater than its parent's DFS index,
+        // we need to form a new cluster with all the switches in the
+        // currSet.
+        if (currDFS.getLowpoint() > currDFS.getParentDFSIndex()) {
+            // The cluster thus far forms a strongly connected component.
+            // create a new switch cluster and the switches in the current
+            // set to the switch cluster.
+            Cluster sc = new Cluster();
+            for(long sw: currSet){
+                sc.add(sw);
+                switchClusterMap.put(sw, sc);
+            }
+            // delete all the nodes in the current set.
+            currSet.clear();
+            // add the newly formed switch clusters to the cluster set.
+            clusters.add(sc);
+        }
+
+        return currIndex;
+    }
+
+    /**
+     *  Go through every link and identify it is a blocked link or not.
+     *  If blocked, remove it from the switchport links and put them in the
+     *  blocked link category.
+     *
+     *  Note that we do not update the tunnel ports and broadcast domain 
+     *  port structures.  We need those to still answer the question if the
+     *  ports are tunnel or broadcast domain ports.
+     *
+     *  If we add additional ports to blocked ports later on, we may simply
+     *  call this method again to remove the links on the newly blocked ports.
+     */
+    protected void removeLinksOnBlockedPorts() {
+        Iterator<NodePortTuple> nptIter;
+        Iterator<Link> linkIter;
+
+        // Iterate through all the links and all the switch ports
+        // and move the links on blocked switch ports to blocked links
+        nptIter = this.switchPortLinks.keySet().iterator();
+        while (nptIter.hasNext()) {
+            NodePortTuple npt = nptIter.next();
+            linkIter = switchPortLinks.get(npt).iterator();
+            while (linkIter.hasNext()) {
+                Link link = linkIter.next();
+                if (isBlockedLink(link)) {
+                    this.blockedLinks.add(link);
+                    linkIter.remove();
+                }
+            }
+            // Note that at this point, the switchport may have
+            // no links in it.  We could delete the switch port, 
+            // but we will leave it as is.
+        }
+    }
+
+    public Set<NodePortTuple> getBlockedPorts() {
+        return this.blockedPorts;
+    }
+
+    protected Set<Link> getBlockedLinks() {
+        return this.blockedLinks;
+    }
+
+    /** Returns true if a link has either one of its switch ports 
+     * blocked.
+     * @param l
+     * @return
+     */
+    protected boolean isBlockedLink(Link l) {
+        NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+        NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+        return (isBlockedPort(n1) || isBlockedPort(n2));
+    }
+
+    protected boolean isBlockedPort(NodePortTuple npt) {
+        return blockedPorts.contains(npt);
+    }
+
+    protected boolean isTunnelPort(NodePortTuple npt) {
+        return tunnelPorts.contains(npt);
+    }
+
+    protected boolean isTunnelLink(Link l) {
+        NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+        NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+        return (isTunnelPort(n1) || isTunnelPort(n2));
+    }
+
+    public boolean isBroadcastDomainLink(Link l) {
+        NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+        NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+        return (isBroadcastDomainPort(n1) || isBroadcastDomainPort(n2));
+    }
+
+    public boolean isBroadcastDomainPort(NodePortTuple npt) {
+        return broadcastDomainPorts.contains(npt);
+    }
+
+    class NodeDist implements Comparable<NodeDist> {
+        private Long node;
+        public Long getNode() {
+            return node;
+        }
+
+        private int dist; 
+        public int getDist() {
+            return dist;
+        }
+
+        public NodeDist(Long node, int dist) {
+            this.node = node;
+            this.dist = dist;
+        }
+
+        public int compareTo(NodeDist o) {
+            if (o.dist == this.dist) {
+                return (int)(o.node - this.node);
+            }
+            return o.dist - this.dist;
+        }
+    }
+
+    protected BroadcastTree dijkstra(Cluster c, Long root, 
+                                     Map<Link, Integer> linkCost,
+                                     boolean isDstRooted) {
+        HashMap<Long, Link> nexthoplinks = new HashMap<Long, Link>();
+        //HashMap<Long, Long> nexthopnodes = new HashMap<Long, Long>();
+        HashMap<Long, Integer> cost = new HashMap<Long, Integer>();
+        int w;
+
+        for (Long node: c.links.keySet()) {
+            nexthoplinks.put(node, null);
+            //nexthopnodes.put(node, null);
+            cost.put(node, MAX_PATH_WEIGHT);
+        }
+
+        HashMap<Long, Boolean> seen = new HashMap<Long, Boolean>();
+        PriorityQueue<NodeDist> nodeq = new PriorityQueue<NodeDist>();
+        nodeq.add(new NodeDist(root, 0));
+        cost.put(root, 0);
+        while (nodeq.peek() != null) {
+            NodeDist n = nodeq.poll();
+            Long cnode = n.getNode();
+            int cdist = n.getDist();
+            if (cdist >= MAX_PATH_WEIGHT) break;
+            if (seen.containsKey(cnode)) continue;
+            seen.put(cnode, true);
+
+            for (Link link: c.links.get(cnode)) {
+                Long neighbor;
+                
+                if (isDstRooted == true) neighbor = link.getSrc();
+                else neighbor = link.getDst();
+                
+                // links directed toward cnode will result in this condition
+                // if (neighbor == cnode) continue;
+                
+                if (linkCost == null || linkCost.get(link)==null) w = 1;
+                else w = linkCost.get(link);
+
+                int ndist = cdist + w; // the weight of the link, always 1 in current version of floodlight.
+                if (ndist < cost.get(neighbor)) {
+                    cost.put(neighbor, ndist);
+                    nexthoplinks.put(neighbor, link);
+                    //nexthopnodes.put(neighbor, cnode);
+                    nodeq.add(new NodeDist(neighbor, ndist));
+                }
+            }
+        }
+
+        BroadcastTree ret = new BroadcastTree(nexthoplinks, cost);
+        return ret;
+    }
+
+    protected void calculateShortestPathTreeInClusters() {
+        pathcache.clear();
+        destinationRootedTrees.clear();
+
+        Map<Link, Integer> linkCost = new HashMap<Link, Integer>();
+        int tunnel_weight = switchPorts.size() + 1;
+
+        for(NodePortTuple npt: tunnelPorts) {
+            if (switchPortLinks.get(npt) == null) continue;
+            for(Link link: switchPortLinks.get(npt)) {
+                if (link == null) continue;
+                linkCost.put(link, tunnel_weight);
+            }
+        }
+
+        for(Cluster c: clusters) {
+            for (Long node : c.links.keySet()) {
+                BroadcastTree tree = dijkstra(c, node, linkCost, true);
+                destinationRootedTrees.put(node, tree);
+            }
+        }
+    }
+
+    protected void calculateBroadcastTreeInClusters() {
+        for(Cluster c: clusters) {
+            // c.id is the smallest node that's in the cluster
+            BroadcastTree tree = destinationRootedTrees.get(c.id);
+            clusterBroadcastTrees.put(c.id, tree);
+        }
+    }
+
+    protected void calculateBroadcastNodePortsInClusters() {
+
+        clusterBroadcastTrees.clear();
+
+        calculateBroadcastTreeInClusters();
+
+        for(Cluster c: clusters) {
+            // c.id is the smallest node that's in the cluster
+            BroadcastTree tree = clusterBroadcastTrees.get(c.id);
+            //log.info("Broadcast Tree {}", tree);
+
+            Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>();
+            Map<Long, Link> links = tree.getLinks();
+            if (links == null) continue;
+            for(long nodeId: links.keySet()) {
+                Link l = links.get(nodeId);
+                if (l == null) continue;
+                NodePortTuple npt1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+                NodePortTuple npt2 = new NodePortTuple(l.getDst(), l.getDstPort());
+                nptSet.add(npt1);
+                nptSet.add(npt2);
+            }
+            clusterBroadcastNodePorts.put(c.id, nptSet);
+        }
+    }
+
+    protected Route buildroute(RouteId id, long srcId, long dstId) {
+        NodePortTuple npt;
+
+        LinkedList<NodePortTuple> switchPorts =
+                new LinkedList<NodePortTuple>();
+
+        if (destinationRootedTrees == null) return null;
+        if (destinationRootedTrees.get(dstId) == null) return null;
+
+        Map<Long, Link> nexthoplinks =
+                destinationRootedTrees.get(dstId).getLinks();
+
+        if (!switches.contains(srcId) || !switches.contains(dstId)) {
+            // This is a switch that is not connected to any other switch
+            // hence there was no update for links (and hence it is not
+            // in the network)
+            log.debug("buildroute: Standalone switch: {}", srcId);
+
+            // The only possible non-null path for this case is
+            // if srcId equals dstId --- and that too is an 'empty' path []
+
+        } else if ((nexthoplinks!=null) && (nexthoplinks.get(srcId)!=null)) {
+            while (srcId != dstId) {
+                Link l = nexthoplinks.get(srcId);
+
+                npt = new NodePortTuple(l.getSrc(), l.getSrcPort());
+                switchPorts.addLast(npt);
+                npt = new NodePortTuple(l.getDst(), l.getDstPort());
+                switchPorts.addLast(npt);
+                srcId = nexthoplinks.get(srcId).getDst();
+            }
+        }
+        // else, no path exists, and path equals null
+
+        Route result = null;
+        if (switchPorts != null && !switchPorts.isEmpty()) 
+            result = new Route(id, switchPorts);
+        if (log.isTraceEnabled()) {
+            log.trace("buildroute: {}", result);
+        }
+        return result;
+    }
+
+    protected int getCost(long srcId, long dstId) {
+        BroadcastTree bt = destinationRootedTrees.get(dstId);
+        if (bt == null) return -1;
+        return (bt.getCost(srcId));
+    }
+
+    /* 
+     * Getter Functions
+     */
+
+    protected Set<Cluster> getClusters() {
+        return clusters;
+    }
+
+    // IRoutingEngineService interfaces
+    protected boolean routeExists(long srcId, long dstId) {
+        BroadcastTree bt = destinationRootedTrees.get(dstId);
+        if (bt == null) return false;
+        Link link = bt.getLinks().get(srcId);
+        if (link == null) return false;
+        return true;
+    }
+
+    protected Route getRoute(long srcId, short srcPort,
+                             long dstId, short dstPort) {
+
+
+        // Return null the route source and desitnation are the
+        // same switchports.
+        if (srcId == dstId && srcPort == dstPort)
+            return null;
+
+        List<NodePortTuple> nptList;
+        NodePortTuple npt;
+        Route r = getRoute(srcId, dstId);
+        if (r == null && srcId != dstId) return null;
+
+        if (r != null) {
+            nptList= new ArrayList<NodePortTuple>(r.getPath());
+        } else {
+            nptList = new ArrayList<NodePortTuple>();
+        }
+        npt = new NodePortTuple(srcId, srcPort);
+        nptList.add(0, npt); // add src port to the front
+        npt = new NodePortTuple(dstId, dstPort);
+        nptList.add(npt); // add dst port to the end
+
+        RouteId id = new RouteId(srcId, dstId);
+        r = new Route(id, nptList);
+        return r;
+    }
+
+    protected Route getRoute(long srcId, long dstId) {
+        RouteId id = new RouteId(srcId, dstId);
+        Route result = null;
+        if (pathcache.containsKey(id)) {
+            result = pathcache.get(id);
+        } else {
+            result = buildroute(id, srcId, dstId);
+            pathcache.put(id, result);
+        }
+        if (log.isTraceEnabled()) {
+            log.trace("getRoute: {} -> {}", id, result);
+        }
+        return result;
+    }
+
+    protected BroadcastTree getBroadcastTreeForCluster(long clusterId){
+        Cluster c = switchClusterMap.get(clusterId);
+        if (c == null) return null;
+        return clusterBroadcastTrees.get(c.id);
+    }
+
+    // 
+    //  ITopologyService interface method helpers.
+    // 
+
+    protected boolean isInternalToOpenflowDomain(long switchid, short port) {
+        return !isAttachmentPointPort(switchid, port);
+    }
+
+    public boolean isAttachmentPointPort(long switchid, short port) {
+        NodePortTuple npt = new NodePortTuple(switchid, port);
+        if (switchPortLinks.containsKey(npt)) return false;
+        return true;
+    }
+
+    protected long getOpenflowDomainId(long switchId) {
+        Cluster c = switchClusterMap.get(switchId);
+        if (c == null) return switchId;
+        return c.getId();
+    }
+
+    protected long getL2DomainId(long switchId) {
+        return getOpenflowDomainId(switchId);
+    }
+
+    protected Set<Long> getSwitchesInOpenflowDomain(long switchId) {
+        Cluster c = switchClusterMap.get(switchId);
+        if (c == null) return null;
+        return (c.getNodes());
+    }
+
+    protected boolean inSameOpenflowDomain(long switch1, long switch2) {
+        Cluster c1 = switchClusterMap.get(switch1);
+        Cluster c2 = switchClusterMap.get(switch2);
+        if (c1 != null && c2 != null)
+            return (c1.getId() == c2.getId());
+        return (switch1 == switch2);
+    }
+
+    public boolean isAllowed(long sw, short portId) {
+        return true;
+    }
+
+    protected boolean
+    isIncomingBroadcastAllowedOnSwitchPort(long sw, short portId) {
+        if (isInternalToOpenflowDomain(sw, portId)) {
+            long clusterId = getOpenflowDomainId(sw);
+            NodePortTuple npt = new NodePortTuple(sw, portId);
+            if (clusterBroadcastNodePorts.get(clusterId).contains(npt))
+                return true;
+            else return false;
+        }
+        return true;
+    }
+
+    public boolean isConsistent(long oldSw, short oldPort, long newSw,
+                                short newPort) {
+        if (isInternalToOpenflowDomain(newSw, newPort)) return true;
+        return (oldSw == newSw && oldPort == newPort);
+    }
+
+    protected Set<NodePortTuple>
+    getBroadcastNodePortsInCluster(long sw) {
+        long clusterId = getOpenflowDomainId(sw);
+        return clusterBroadcastNodePorts.get(clusterId);
+    }
+
+    public boolean inSameBroadcastDomain(long s1, short p1, long s2, short p2) {
+        return false;
+    }
+
+    public boolean inSameL2Domain(long switch1, long switch2) {
+        return inSameOpenflowDomain(switch1, switch2);
+    }
+
+    public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort) {
+        // Use this function to redirect traffic if needed.
+        return new NodePortTuple(dst, dstPort);
+    }
+
+    public NodePortTuple getIncomingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort) {
+     // Use this function to reinject traffic from a different port if needed.
+        return new NodePortTuple(src, srcPort);
+    }
+
+    public Set<Long> getSwitches() {
+        return switches;
+    }
+
+    public Set<Short> getPortsWithLinks(long sw) {
+        return switchPorts.get(sw);
+    }
+
+    public Set<Short> getBroadcastPorts(long targetSw, long src, short srcPort) {
+        Set<Short> result = new HashSet<Short>();
+        long clusterId = getOpenflowDomainId(targetSw);
+        for(NodePortTuple npt: clusterBroadcastNodePorts.get(clusterId)) {
+            if (npt.getNodeId() == targetSw) {
+                result.add(npt.getPortId());
+            }
+        }
+        return result;
+    }
+
+    public NodePortTuple
+            getAllowedOutgoingBroadcastPort(long src, short srcPort, long dst,
+                                            short dstPort) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public NodePortTuple
+    getAllowedIncomingBroadcastPort(long src, short srcPort) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyManager.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyManager.java
new file mode 100644
index 0000000..ba17483
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyManager.java
@@ -0,0 +1,1169 @@
+package net.floodlightcontroller.topology;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.SingletonTask;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.packet.BSN;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.LLDP;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.web.TopologyWebRoutable;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.protocol.OFType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Topology manager is responsible for maintaining the controller's notion
+ * of the network graph, as well as implementing tools for finding routes 
+ * through the topology.
+ */
+@LogMessageCategory("Network Topology")
+public class TopologyManager implements 
+        IFloodlightModule, ITopologyService, 
+        IRoutingService, ILinkDiscoveryListener,
+        IOFMessageListener, IHAListener {
+
+    protected static Logger log = LoggerFactory.getLogger(TopologyManager.class);
+
+    public static final String CONTEXT_TUNNEL_ENABLED = 
+            "com.bigswitch.floodlight.topologymanager.tunnelEnabled";
+
+    /** 
+     * Set of ports for each switch
+     */
+    protected Map<Long, Set<Short>> switchPorts;
+
+    /**
+     * Set of links organized by node port tuple
+     */
+    protected Map<NodePortTuple, Set<Link>> switchPortLinks;
+
+    /**
+     * Set of direct links
+     */
+    protected Map<NodePortTuple, Set<Link>> directLinks;
+
+    /**
+     * set of links that are broadcast domain links.
+     */
+    protected Map<NodePortTuple, Set<Link>> portBroadcastDomainLinks;
+
+    /**
+     * set of tunnel links
+     */
+    protected Map<NodePortTuple, Set<Link>> tunnelLinks; 
+
+    protected ILinkDiscoveryService linkDiscovery;
+    protected IThreadPoolService threadPool;
+    protected IFloodlightProviderService floodlightProvider;
+    protected IRestApiService restApi;
+
+    // Modules that listen to our updates
+    protected ArrayList<ITopologyListener> topologyAware;
+
+    protected BlockingQueue<LDUpdate> ldUpdates;
+    protected List<LDUpdate> appliedUpdates;
+    
+    // These must be accessed using getCurrentInstance(), not directly
+    protected TopologyInstance currentInstance;
+    protected TopologyInstance currentInstanceWithoutTunnels;
+    
+    protected SingletonTask newInstanceTask;
+    private Date lastUpdateTime;
+
+    /**
+     * Flag that indicates if links (direct/tunnel/multihop links) were
+     * updated as part of LDUpdate.
+     */
+    protected boolean linksUpdated;
+    /**
+     * Flag that indicates if direct or tunnel links were updated as
+     * part of LDUpdate.
+     */
+    protected boolean dtLinksUpdated;
+
+    /**
+     * Thread for recomputing topology.  The thread is always running, 
+     * however the function applyUpdates() has a blocking call.
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Error in topology instance task thread",
+            explanation="An unknown error occured in the topology " +
+            		"discovery module.",
+            recommendation=LogMessageDoc.CHECK_CONTROLLER)
+    protected class UpdateTopologyWorker implements Runnable {
+        @Override 
+        public void run() {
+            try {
+                updateTopology();
+            }
+            catch (Exception e) {
+                log.error("Error in topology instance task thread", e);
+            }
+        }
+    }
+
+    public boolean updateTopology() {
+        boolean newInstanceFlag;
+        linksUpdated = false;
+        dtLinksUpdated = false;
+        applyUpdates();
+        newInstanceFlag = createNewInstance();
+        lastUpdateTime = new Date();
+        informListeners();
+        return newInstanceFlag;
+    }
+
+    // **********************
+    // ILinkDiscoveryListener
+    // **********************
+
+    @Override
+    public void linkDiscoveryUpdate(LDUpdate update) {
+        boolean scheduleFlag = false;
+        // if there's no udpates in the queue, then
+        // we need to schedule an update.
+        if (ldUpdates.peek() == null)
+            scheduleFlag = true;
+
+        if (log.isTraceEnabled()) {
+            log.trace("Queuing update: {}", update);
+        }
+        ldUpdates.add(update);
+
+        if (scheduleFlag) {
+            newInstanceTask.reschedule(1, TimeUnit.MICROSECONDS);
+        }
+    }
+    
+    // ****************
+    // ITopologyService
+    // ****************
+
+    //
+    // ITopologyService interface methods
+    //
+    @Override
+    public Date getLastUpdateTime() {
+        return lastUpdateTime;
+    }
+
+    @Override
+    public void addListener(ITopologyListener listener) {
+        topologyAware.add(listener);
+    }
+
+    @Override 
+    public boolean isAttachmentPointPort(long switchid, short port) {
+        return isAttachmentPointPort(switchid, port, true);
+    }
+
+    @Override
+    public boolean isAttachmentPointPort(long switchid, short port, 
+                                         boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+
+        // if the port is not attachment point port according to
+        // topology instance, then return false
+        if (ti.isAttachmentPointPort(switchid, port) == false)
+                return false;
+
+        // Check whether the port is a physical port. We should not learn
+        // attachment points on "special" ports.
+        if ((port & 0xff00) == 0xff00 && port != (short)0xfffe) return false;
+
+        // Make sure that the port is enabled.
+        IOFSwitch sw = floodlightProvider.getSwitches().get(switchid);
+        if (sw == null) return false;
+        return (sw.portEnabled(port));
+    }
+
+    public long getOpenflowDomainId(long switchId) {
+        return getOpenflowDomainId(switchId, true);
+    }
+
+    public long getOpenflowDomainId(long switchId, boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getOpenflowDomainId(switchId);
+    }
+
+    @Override
+    public long getL2DomainId(long switchId) {
+        return getL2DomainId(switchId, true);
+    }
+
+    @Override
+    public long getL2DomainId(long switchId, boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getL2DomainId(switchId);
+    }
+
+    @Override
+    public boolean inSameOpenflowDomain(long switch1, long switch2) {
+        return inSameOpenflowDomain(switch1, switch2, true);
+    }
+
+    @Override
+    public boolean inSameOpenflowDomain(long switch1, long switch2,
+                                        boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.inSameOpenflowDomain(switch1, switch2);
+    }
+
+    @Override
+    public boolean isAllowed(long sw, short portId) {
+        return isAllowed(sw, portId, true);
+    }
+
+    @Override
+    public boolean isAllowed(long sw, short portId, boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.isAllowed(sw, portId);
+    }
+
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+    @Override
+    public boolean isIncomingBroadcastAllowed(long sw, short portId) {
+        return isIncomingBroadcastAllowed(sw, portId, true);
+    }
+
+    public boolean isIncomingBroadcastAllowed(long sw, short portId,
+                                              boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.isIncomingBroadcastAllowedOnSwitchPort(sw, portId);
+    }
+
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+    /** Get all the ports connected to the switch */
+    @Override
+    public Set<Short> getPortsWithLinks(long sw) {
+        return getPortsWithLinks(sw, true);
+    }
+
+    /** Get all the ports connected to the switch */
+    @Override
+    public Set<Short> getPortsWithLinks(long sw, boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getPortsWithLinks(sw);
+    }
+
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+    /** Get all the ports on the target switch (targetSw) on which a 
+     * broadcast packet must be sent from a host whose attachment point
+     * is on switch port (src, srcPort).
+     */
+    public Set<Short> getBroadcastPorts(long targetSw, 
+                                        long src, short srcPort) {
+        return getBroadcastPorts(targetSw, src, srcPort, true);
+    }
+
+    /** Get all the ports on the target switch (targetSw) on which a 
+     * broadcast packet must be sent from a host whose attachment point
+     * is on switch port (src, srcPort).
+     */
+    public Set<Short> getBroadcastPorts(long targetSw, 
+                                        long src, short srcPort,
+                                        boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getBroadcastPorts(targetSw, src, srcPort);
+    }
+
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+    @Override
+    public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort) {
+        // Use this function to redirect traffic if needed.
+        return getOutgoingSwitchPort(src, srcPort, dst, dstPort, true);
+    }
+    
+    @Override
+    public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort,
+                                               boolean tunnelEnabled) {
+        // Use this function to redirect traffic if needed.
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getOutgoingSwitchPort(src, srcPort,
+                                                     dst, dstPort);
+    }
+
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+    @Override
+    public NodePortTuple getIncomingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort) {
+        return getIncomingSwitchPort(src, srcPort, dst, dstPort, true);
+    }
+
+    @Override
+    public NodePortTuple getIncomingSwitchPort(long src, short srcPort,
+                                               long dst, short dstPort,
+                                               boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getIncomingSwitchPort(src, srcPort,
+                                                     dst, dstPort);
+    }
+
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+    /**
+     * Checks if the two switchports belong to the same broadcast domain.
+     */
+    @Override
+    public boolean isInSameBroadcastDomain(long s1, short p1, long s2,
+                                           short p2) {
+        return isInSameBroadcastDomain(s1, p1, s2, p2, true);
+
+    }
+
+    @Override
+    public boolean isInSameBroadcastDomain(long s1, short p1,
+                                           long s2, short p2,
+                                           boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.inSameBroadcastDomain(s1, p1, s2, p2);
+
+    }
+
+
+    /**
+     * Checks if the switchport is a broadcast domain port or not.
+     */
+    @Override
+    public boolean isBroadcastDomainPort(long sw, short port) {
+        return isBroadcastDomainPort(sw, port, true);
+    }
+
+    @Override
+    public boolean isBroadcastDomainPort(long sw, short port,
+                                         boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.isBroadcastDomainPort(new NodePortTuple(sw, port));
+    }
+
+
+    /**
+     * Checks if the new attachment point port is consistent with the
+     * old attachment point port.
+     */
+    @Override
+    public boolean isConsistent(long oldSw, short oldPort,
+                                long newSw, short newPort) {
+        return isConsistent(oldSw, oldPort,
+                                            newSw, newPort, true);
+    }
+
+    @Override
+    public boolean isConsistent(long oldSw, short oldPort,
+                                long newSw, short newPort,
+                                boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.isConsistent(oldSw, oldPort, newSw, newPort);
+    }
+
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+    /**
+     * Checks if the two switches are in the same Layer 2 domain.
+     */
+    @Override
+    public boolean inSameL2Domain(long switch1, long switch2) {
+        return inSameL2Domain(switch1, switch2, true);
+    }
+
+    @Override
+    public boolean inSameL2Domain(long switch1, long switch2,
+                                  boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.inSameL2Domain(switch1, switch2);
+    }
+
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+    @Override
+    public NodePortTuple getAllowedOutgoingBroadcastPort(long src,
+                                                         short srcPort,
+                                                         long dst,
+                                                         short dstPort) {
+        return getAllowedOutgoingBroadcastPort(src, srcPort,
+                                               dst, dstPort, true);
+    }
+
+    @Override
+    public NodePortTuple getAllowedOutgoingBroadcastPort(long src,
+                                                         short srcPort,
+                                                         long dst,
+                                                         short dstPort,
+                                                         boolean tunnelEnabled){
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getAllowedOutgoingBroadcastPort(src, srcPort,
+                                                  dst, dstPort);
+    }
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+    @Override
+    public NodePortTuple 
+    getAllowedIncomingBroadcastPort(long src, short srcPort) {
+        return getAllowedIncomingBroadcastPort(src,srcPort, true);
+    }
+
+    @Override
+    public NodePortTuple 
+    getAllowedIncomingBroadcastPort(long src, short srcPort,
+                                    boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getAllowedIncomingBroadcastPort(src,srcPort);
+    }
+
+    @Override
+    public Set<NodePortTuple> getBroadcastDomainPorts() {
+        return portBroadcastDomainLinks.keySet();
+    }
+
+    @Override
+    public Set<NodePortTuple> getTunnelPorts() {
+        return tunnelLinks.keySet();
+    }
+
+    @Override
+    public Set<NodePortTuple> getBlockedPorts() {
+        Set<NodePortTuple> bp;
+        Set<NodePortTuple> blockedPorts =
+                new HashSet<NodePortTuple>();
+
+        // As we might have two topologies, simply get the union of
+        // both of them and send it.
+        bp = getCurrentInstance(true).getBlockedPorts();
+        if (bp != null)
+            blockedPorts.addAll(bp);
+
+        bp = getCurrentInstance(false).getBlockedPorts();
+        if (bp != null)
+            blockedPorts.addAll(bp);
+
+        return blockedPorts;
+    }
+
+    @Override
+    public List<LDUpdate> getLastLinkUpdates() {
+    	return appliedUpdates;
+    }
+    ////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////
+
+    // ***************
+    // IRoutingService
+    // ***************
+
+    @Override
+    public Route getRoute(long src, long dst) {
+        return getRoute(src, dst, true);
+    }
+
+    @Override
+    public Route getRoute(long src, long dst, boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getRoute(src, dst);
+    }
+
+    @Override
+    public Route getRoute(long src, short srcPort, long dst, short dstPort) {
+        return getRoute(src, srcPort, dst, dstPort, true);
+    }
+
+    @Override
+    public Route getRoute(long src, short srcPort, long dst, short dstPort, 
+                          boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.getRoute(src, srcPort, dst, dstPort);
+    }
+
+    @Override
+    public boolean routeExists(long src, long dst) {
+        return routeExists(src, dst, true);
+    }
+
+    @Override
+    public boolean routeExists(long src, long dst, boolean tunnelEnabled) {
+        TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+        return ti.routeExists(src, dst);
+    }
+
+
+    // ******************
+    // IOFMessageListener
+    // ******************
+
+    @Override
+    public String getName() {
+        return "topology";
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        return "linkdiscovery".equals(name);
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return false;
+    }
+
+    @Override
+    public Command receive(IOFSwitch sw, OFMessage msg,
+                           FloodlightContext cntx) {
+        switch (msg.getType()) {
+            case PACKET_IN:
+                return this.processPacketInMessage(sw, 
+                                                   (OFPacketIn) msg, cntx);
+            default:
+            	break;
+        }
+
+        return Command.CONTINUE;
+    }
+
+    // ***************
+    // IHAListener
+    // ***************
+
+    @Override
+    public void roleChanged(Role oldRole, Role newRole) {
+        switch(newRole) {
+            case MASTER:
+                if (oldRole == Role.SLAVE) {
+                    log.debug("Re-computing topology due " +
+                            "to HA change from SLAVE->MASTER");
+                    newInstanceTask.reschedule(1, TimeUnit.MILLISECONDS);
+                }
+                break;
+            case SLAVE:
+                log.debug("Clearing topology due to " +
+                        "HA change to SLAVE");
+                clearCurrentTopology();
+                break;
+            default:
+            	break;
+        }
+    }
+
+    @Override
+    public void controllerNodeIPsChanged(
+                          Map<String, String> curControllerNodeIPs,
+                          Map<String, String> addedControllerNodeIPs,
+                          Map<String, String> removedControllerNodeIPs) {
+        // no-op
+    }
+
+    // *****************
+    // IFloodlightModule
+    // *****************
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(ITopologyService.class);
+        l.add(IRoutingService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m = 
+            new HashMap<Class<? extends IFloodlightService>,
+                IFloodlightService>();
+        // We are the class that implements the service
+        m.put(ITopologyService.class, this);
+        m.put(IRoutingService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> 
+            getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(ILinkDiscoveryService.class);
+        l.add(IThreadPoolService.class);
+        l.add(IFloodlightProviderService.class);
+        l.add(ICounterStoreService.class);
+        l.add(IRestApiService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        linkDiscovery = context.getServiceImpl(ILinkDiscoveryService.class);
+        threadPool = context.getServiceImpl(IThreadPoolService.class);
+        floodlightProvider = 
+                context.getServiceImpl(IFloodlightProviderService.class);
+        restApi = context.getServiceImpl(IRestApiService.class);
+
+        switchPorts = new HashMap<Long,Set<Short>>();
+        switchPortLinks = new HashMap<NodePortTuple, Set<Link>>();
+        directLinks = new HashMap<NodePortTuple, Set<Link>>();
+        portBroadcastDomainLinks = new HashMap<NodePortTuple, Set<Link>>();
+        tunnelLinks = new HashMap<NodePortTuple, Set<Link>>();
+        topologyAware = new ArrayList<ITopologyListener>();
+        ldUpdates = new LinkedBlockingQueue<LDUpdate>();
+        appliedUpdates = new ArrayList<LDUpdate>();
+        clearCurrentTopology();
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        ScheduledExecutorService ses = threadPool.getScheduledExecutor();
+        newInstanceTask = new SingletonTask(ses, new UpdateTopologyWorker());
+        linkDiscovery.addListener(this);
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+        floodlightProvider.addHAListener(this);
+        addRestletRoutable();
+    }
+
+    protected void addRestletRoutable() {
+        restApi.addRestletRoutable(new TopologyWebRoutable());
+    }
+
+    // ****************
+    // Internal methods
+    // ****************
+    /**
+     * If the packet-in switch port is disabled for all data traffic, then
+     * the packet will be dropped.  Otherwise, the packet will follow the
+     * normal processing chain.
+     * @param sw
+     * @param pi
+     * @param cntx
+     * @return
+     */
+    protected Command dropFilter(long sw, OFPacketIn pi,
+                                             FloodlightContext cntx) {
+        Command result = Command.CONTINUE;
+        short port = pi.getInPort();
+
+        // If the input port is not allowed for data traffic, drop everything.
+        // BDDP packets will not reach this stage.
+        if (isAllowed(sw, port) == false) {
+            if (log.isTraceEnabled()) {
+                log.trace("Ignoring packet because of topology " +
+                        "restriction on switch={}, port={}", sw, port);
+                result = Command.STOP;
+            }
+        }
+
+        // if sufficient information is available, then drop broadcast
+        // packets here as well.
+        return result;
+    }
+
+    /** 
+     * TODO This method must be moved to a layer below forwarding
+     * so that anyone can use it.
+     * @param packetData
+     * @param sw
+     * @param ports
+     * @param cntx
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Failed to clear all flows on switch {switch}",
+            explanation="An I/O error occured while trying send " +
+            		"topology discovery packet",
+            recommendation=LogMessageDoc.CHECK_SWITCH)
+    public void doMultiActionPacketOut(byte[] packetData, IOFSwitch sw, 
+                                       Set<Short> ports,
+                                       FloodlightContext cntx) {
+
+        if (ports == null) return;
+        if (packetData == null || packetData.length <= 0) return;
+
+        OFPacketOut po = 
+                (OFPacketOut) floodlightProvider.getOFMessageFactory().
+                getMessage(OFType.PACKET_OUT);
+
+        List<OFAction> actions = new ArrayList<OFAction>();
+        for(short p: ports) {
+            actions.add(new OFActionOutput(p, (short) 0));
+        }
+
+        // set actions
+        po.setActions(actions);
+        // set action length
+        po.setActionsLength((short) (OFActionOutput.MINIMUM_LENGTH * 
+                ports.size()));
+        // set buffer-id to BUFFER_ID_NONE
+        po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
+        // set in-port to OFPP_NONE
+        po.setInPort(OFPort.OFPP_NONE.getValue());
+
+        // set packet data
+        po.setPacketData(packetData);
+
+        // compute and set packet length.
+        short poLength = (short)(OFPacketOut.MINIMUM_LENGTH + 
+                po.getActionsLength() + 
+                packetData.length);
+
+        po.setLength(poLength);
+
+        try {
+            //counterStore.updatePktOutFMCounterStore(sw, po);
+            if (log.isTraceEnabled()) {
+                log.trace("write broadcast packet on switch-id={} " + 
+                        "interaces={} packet-data={} packet-out={}",
+                        new Object[] {sw.getId(), ports, packetData, po});
+            }
+            sw.write(po, cntx);
+
+        } catch (IOException e) {
+            log.error("Failure writing packet out", e);
+        }
+    }
+
+
+    /**
+     * The BDDP packets are forwarded out of all the ports out of an
+     * openflowdomain.  Get all the switches in the same openflow
+     * domain as the sw (disabling tunnels).  Then get all the 
+     * external switch ports and send these packets out.
+     * @param sw
+     * @param pi
+     * @param cntx
+     */
+    protected void doFloodBDDP(long pinSwitch, OFPacketIn pi, 
+                               FloodlightContext cntx) {
+
+        TopologyInstance ti = getCurrentInstance(false);
+
+        Set<Long> switches = ti.getSwitchesInOpenflowDomain(pinSwitch);
+
+        if (switches == null)
+        {
+            // indicates no links are connected to the switches
+            switches = new HashSet<Long>();
+            switches.add(pinSwitch);
+        }
+
+        for(long sid: switches) {
+            IOFSwitch sw = floodlightProvider.getSwitches().get(sid);
+            if (sw == null) continue;
+            Collection<Short> enabledPorts = sw.getEnabledPortNumbers();
+            if (enabledPorts == null)
+                continue;
+            Set<Short> ports = new HashSet<Short>();
+            ports.addAll(enabledPorts);
+
+            // all the ports known to topology // without tunnels.
+            // out of these, we need to choose only those that are 
+            // broadcast port, otherwise, we should eliminate.
+            Set<Short> portsKnownToTopo = ti.getPortsWithLinks(sid);
+
+            if (portsKnownToTopo != null) {
+                for(short p: portsKnownToTopo) {
+                    NodePortTuple npt = 
+                            new NodePortTuple(sid, p);
+                    if (ti.isBroadcastDomainPort(npt) == false) {
+                        ports.remove(p);
+                    }
+                }
+            }
+
+            // remove the incoming switch port
+            if (pinSwitch == sid) {
+                ports.remove(pi.getInPort());
+            }
+
+            // we have all the switch ports to which we need to broadcast.
+            doMultiActionPacketOut(pi.getPacketData(), sw, ports, cntx);
+        }
+
+    }
+
+    protected Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, 
+                                             FloodlightContext cntx) {
+
+        // get the packet-in switch.
+        Ethernet eth = 
+                IFloodlightProviderService.bcStore.
+                get(cntx,IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+
+        if (eth.getEtherType() == Ethernet.TYPE_BSN) {
+            BSN bsn = (BSN) eth.getPayload();
+            if (bsn == null) return Command.STOP;
+            if (bsn.getPayload() == null) return Command.STOP;
+
+            // It could be a packet other than BSN LLDP, therefore
+            // continue with the regular processing.
+            if (bsn.getPayload() instanceof LLDP == false)
+                return Command.CONTINUE;
+
+            doFloodBDDP(sw.getId(), pi, cntx);
+        } else {
+            return dropFilter(sw.getId(), pi, cntx);
+        }
+        return Command.STOP;
+    }
+
+
+    /**
+     * Updates concerning switch disconnect and port down are not processed.
+     * LinkDiscoveryManager is expected to process those messages and send
+     * multiple link removed messages.  However, all the updates from
+     * LinkDiscoveryManager would be propagated to the listeners of topology.
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Error reading link discovery update.",
+            explanation="Unable to process link discovery update",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    public void applyUpdates() {
+        appliedUpdates.clear();
+        LDUpdate update = null;
+        while (ldUpdates.peek() != null) {
+            try {
+                update = ldUpdates.take();
+            } catch (Exception e) {
+                log.error("Error reading link discovery update.", e);
+            }
+            if (log.isTraceEnabled()) {
+                log.trace("Applying update: {}", update);
+            }
+            if (update.getOperation() == UpdateOperation.LINK_UPDATED) {
+                addOrUpdateLink(update.getSrc(), update.getSrcPort(),
+                                update.getDst(), update.getDstPort(),
+                                update.getType());
+            } else if (update.getOperation() == UpdateOperation.LINK_REMOVED){
+                removeLink(update.getSrc(), update.getSrcPort(), 
+                           update.getDst(), update.getDstPort());
+            }
+            // Add to the list of applied updates.
+            appliedUpdates.add(update);
+        }
+    }
+
+    /**
+     * This function computes a new topology.
+     */
+    /**
+     * This function computes a new topology instance.
+     * It ignores links connected to all broadcast domain ports
+     * and tunnel ports. The method returns if a new instance of
+     * topology was created or not.
+     */
+    protected boolean createNewInstance() {
+        Set<NodePortTuple> blockedPorts = new HashSet<NodePortTuple>();
+
+        if (!linksUpdated) return false;
+
+        Map<NodePortTuple, Set<Link>> openflowLinks;
+        openflowLinks = 
+                new HashMap<NodePortTuple, Set<Link>>(switchPortLinks);
+
+        // Remove all tunnel links.
+        for(NodePortTuple npt: tunnelLinks.keySet()) {
+            if (openflowLinks.get(npt) != null)
+                openflowLinks.remove(npt);
+        }
+
+        // Remove all broadcast domain links.
+        for(NodePortTuple npt: portBroadcastDomainLinks.keySet()) {
+            if (openflowLinks.get(npt) != null)
+                openflowLinks.remove(npt);
+        }
+
+        TopologyInstance nt = new TopologyInstance(switchPorts, 
+                                                   blockedPorts,
+                                                   openflowLinks, 
+                                                   portBroadcastDomainLinks.keySet(), 
+                                                   tunnelLinks.keySet());
+        nt.compute();
+        // We set the instances with and without tunnels to be identical.
+        // If needed, we may compute them differently.
+        currentInstance = nt;
+        currentInstanceWithoutTunnels = nt;
+        return true;
+    }
+
+
+    public void informListeners() {
+        for(int i=0; i<topologyAware.size(); ++i) {
+            ITopologyListener listener = topologyAware.get(i);
+            listener.topologyChanged();
+        }
+    }
+
+    public void addSwitch(long sid) {
+        if (switchPorts.containsKey(sid) == false) {
+            switchPorts.put(sid, new HashSet<Short>());
+        }
+    }
+
+    private void addPortToSwitch(long s, short p) {
+        addSwitch(s);
+        switchPorts.get(s).add(p);
+    }
+
+    public boolean removeSwitchPort(long sw, short port) {
+
+        Set<Link> linksToRemove = new HashSet<Link>();
+        NodePortTuple npt = new NodePortTuple(sw, port);
+        if (switchPortLinks.containsKey(npt) == false) return false;
+
+        linksToRemove.addAll(switchPortLinks.get(npt));
+        for(Link link: linksToRemove) {
+            removeLink(link);
+        }
+        return true;
+    }
+
+    public boolean removeSwitch(long sid) {
+        // Delete all the links in the switch, switch and all 
+        // associated data should be deleted.
+        if (switchPorts.containsKey(sid) == false) return false;
+
+        Set<Link> linksToRemove = new HashSet<Link>();
+        for(Short p: switchPorts.get(sid)) {
+            NodePortTuple n1 = new NodePortTuple(sid, p);
+            linksToRemove.addAll(switchPortLinks.get(n1));
+        }
+
+        if (linksToRemove.isEmpty()) return false;
+
+        for(Link link: linksToRemove) {
+            removeLink(link);
+        }
+        return true;
+    }
+
+    /**
+     * Add the given link to the data structure.  Returns true if a link was
+     * added.
+     * @param s
+     * @param l
+     * @return
+     */
+    private boolean addLinkToStructure(Map<NodePortTuple, 
+                                       Set<Link>> s, Link l) {
+        boolean result1 = false, result2 = false; 
+
+        NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+        NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+
+        if (s.get(n1) == null) {
+            s.put(n1, new HashSet<Link>());
+        }
+        if (s.get(n2) == null) {
+            s.put(n2, new HashSet<Link>());
+        }
+        result1 = s.get(n1).add(l);
+        result2 = s.get(n2).add(l);
+
+        return (result1 || result2);
+    }
+
+    /**
+     * Delete the given link from the data strucure.  Returns true if the
+     * link was deleted.
+     * @param s
+     * @param l
+     * @return
+     */
+    private boolean removeLinkFromStructure(Map<NodePortTuple, 
+                                            Set<Link>> s, Link l) {
+
+        boolean result1 = false, result2 = false;
+        NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+        NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+
+        if (s.get(n1) != null) {
+            result1 = s.get(n1).remove(l);
+            if (s.get(n1).isEmpty()) s.remove(n1);
+        }
+        if (s.get(n2) != null) {
+            result2 = s.get(n2).remove(l);
+            if (s.get(n2).isEmpty()) s.remove(n2);
+        }
+        return result1 || result2;
+    }
+
+    public void addOrUpdateLink(long srcId, short srcPort, long dstId, 
+                                short dstPort, LinkType type) {
+        boolean flag1 = false, flag2 = false;
+
+        Link link = new Link(srcId, srcPort, dstId, dstPort);
+        addPortToSwitch(srcId, srcPort);
+        addPortToSwitch(dstId, dstPort);
+
+        addLinkToStructure(switchPortLinks, link);
+
+        if (type.equals(LinkType.MULTIHOP_LINK)) {
+            addLinkToStructure(portBroadcastDomainLinks, link);
+            flag1 = removeLinkFromStructure(tunnelLinks, link);
+            flag2 = removeLinkFromStructure(directLinks, link);
+            dtLinksUpdated = flag1 || flag2;
+        } else if (type.equals(LinkType.TUNNEL)) {
+            addLinkToStructure(tunnelLinks, link);
+            removeLinkFromStructure(portBroadcastDomainLinks, link);
+            removeLinkFromStructure(directLinks, link);
+            dtLinksUpdated = true;
+        } else if (type.equals(LinkType.DIRECT_LINK)) {
+            addLinkToStructure(directLinks, link);
+            removeLinkFromStructure(tunnelLinks, link);
+            removeLinkFromStructure(portBroadcastDomainLinks, link);
+            dtLinksUpdated = true;
+        }
+        linksUpdated = true;
+    }
+
+    public void removeLink(Link link)  {
+        boolean flag1 = false, flag2 = false;
+
+        flag1 = removeLinkFromStructure(directLinks, link);
+        flag2 = removeLinkFromStructure(tunnelLinks, link);
+
+        linksUpdated = true;
+        dtLinksUpdated = flag1 || flag2;
+
+        removeLinkFromStructure(portBroadcastDomainLinks, link);
+        removeLinkFromStructure(switchPortLinks, link);
+
+        NodePortTuple srcNpt = 
+                new NodePortTuple(link.getSrc(), link.getSrcPort());
+        NodePortTuple dstNpt = 
+                new NodePortTuple(link.getDst(), link.getDstPort());
+
+        // Remove switch ports if there are no links through those switch ports
+        if (switchPortLinks.get(srcNpt) == null) {
+            if (switchPorts.get(srcNpt.getNodeId()) != null)
+                switchPorts.get(srcNpt.getNodeId()).remove(srcNpt.getPortId());
+        }
+        if (switchPortLinks.get(dstNpt) == null) {
+            if (switchPorts.get(dstNpt.getNodeId()) != null)
+                switchPorts.get(dstNpt.getNodeId()).remove(dstNpt.getPortId());
+        }
+
+        // Remove the node if no ports are present
+        if (switchPorts.get(srcNpt.getNodeId())!=null && 
+                switchPorts.get(srcNpt.getNodeId()).isEmpty()) {
+            switchPorts.remove(srcNpt.getNodeId());
+        }
+        if (switchPorts.get(dstNpt.getNodeId())!=null && 
+                switchPorts.get(dstNpt.getNodeId()).isEmpty()) {
+            switchPorts.remove(dstNpt.getNodeId());
+        }
+    }
+
+    public void removeLink(long srcId, short srcPort,
+                           long dstId, short dstPort) {
+        Link link = new Link(srcId, srcPort, dstId, dstPort);
+        removeLink(link);
+    }
+
+    public void clear() {
+        switchPorts.clear();
+        switchPortLinks.clear();
+        portBroadcastDomainLinks.clear();
+        tunnelLinks.clear();
+        directLinks.clear();
+        appliedUpdates.clear();
+    }
+
+    /**
+    * Clears the current topology. Note that this does NOT
+    * send out updates.
+    */
+    public void clearCurrentTopology() {
+        this.clear();
+        linksUpdated = true;
+        dtLinksUpdated = true;
+        createNewInstance();
+        lastUpdateTime = new Date();
+    }
+
+    /**
+     * Getters.  No Setters.
+     */
+    public Map<Long, Set<Short>> getSwitchPorts() {
+        return switchPorts;
+    }
+
+    public Map<NodePortTuple, Set<Link>> getSwitchPortLinks() {
+        return switchPortLinks;
+    }
+
+    public Map<NodePortTuple, Set<Link>> getPortBroadcastDomainLinks() {
+        return portBroadcastDomainLinks;
+    }
+
+    public TopologyInstance getCurrentInstance(boolean tunnelEnabled) {
+        if (tunnelEnabled)
+            return currentInstance;
+        else return this.currentInstanceWithoutTunnels;
+    }
+
+    public TopologyInstance getCurrentInstance() {
+        return this.getCurrentInstance(true);
+    }
+
+    /**
+     *  Switch methods
+     */
+    public Set<Short> getPorts(long sw) {
+        Set<Short> ports = new HashSet<Short>();
+        IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+        if (iofSwitch == null) return null;
+
+        Collection<Short> ofpList = iofSwitch.getEnabledPortNumbers();
+        if (ofpList == null) return null;
+
+        Set<Short> qPorts = linkDiscovery.getQuarantinedPorts(sw);
+        if (qPorts != null)
+            ofpList.removeAll(qPorts);
+
+        ports.addAll(ofpList);
+        return ports;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BlockedPortsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BlockedPortsResource.java
new file mode 100644
index 0000000..dc4ac61
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BlockedPortsResource.java
@@ -0,0 +1,20 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.Set;
+
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class BlockedPortsResource extends ServerResource {
+    @Get("json")
+    public Set<NodePortTuple> retrieve() {
+        ITopologyService topology = 
+                (ITopologyService)getContext().getAttributes().
+                    get(ITopologyService.class.getCanonicalName());
+        
+        return topology.getBlockedPorts();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BroadcastDomainPortsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BroadcastDomainPortsResource.java
new file mode 100644
index 0000000..61b4338
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BroadcastDomainPortsResource.java
@@ -0,0 +1,20 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.Set;
+
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class BroadcastDomainPortsResource extends ServerResource {
+    @Get("json")
+    public Set<NodePortTuple> retrieve() {
+        ITopologyService topology = 
+                (ITopologyService)getContext().getAttributes().
+                    get(ITopologyService.class.getCanonicalName());
+        
+        return topology.getBroadcastDomainPorts();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java
new file mode 100644
index 0000000..aa75321
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java
@@ -0,0 +1,42 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class EnabledPortsResource extends ServerResource {
+    @Get("json")
+    public List<NodePortTuple> retrieve() {
+        List<NodePortTuple> result = new ArrayList<NodePortTuple>();
+
+        IFloodlightProviderService floodlightProvider =
+                (IFloodlightProviderService)getContext().getAttributes().
+                get(IFloodlightProviderService.class.getCanonicalName());
+
+        ITopologyService topology= 
+                (ITopologyService)getContext().getAttributes().
+                get(ITopologyService.class.getCanonicalName());
+
+        if (floodlightProvider == null || topology == null)
+            return result;
+
+        Set<Long> switches = floodlightProvider.getSwitches().keySet();
+        if (switches == null) return result;
+
+        for(long sw: switches) {
+            Set<Short> ports = topology.getPorts(sw);
+            if (ports == null) continue;
+            for(short p: ports) {
+                result.add(new NodePortTuple(sw, p));
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java
new file mode 100644
index 0000000..70e406f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java
@@ -0,0 +1,47 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.List;
+
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.openflow.util.HexString;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RouteResource extends ServerResource {
+
+    protected static Logger log = LoggerFactory.getLogger(RouteResource.class);
+
+    @Get("json")
+    public List<NodePortTuple> retrieve() {
+        IRoutingService routing = 
+                (IRoutingService)getContext().getAttributes().
+                    get(IRoutingService.class.getCanonicalName());
+        
+        String srcDpid = (String) getRequestAttributes().get("src-dpid");
+        String srcPort = (String) getRequestAttributes().get("src-port");
+        String dstDpid = (String) getRequestAttributes().get("dst-dpid");
+        String dstPort = (String) getRequestAttributes().get("dst-port");
+
+        log.debug( srcDpid + "--" + srcPort + "--" + dstDpid + "--" + dstPort);
+
+        long longSrcDpid = HexString.toLong(srcDpid);
+        short shortSrcPort = Short.parseShort(srcPort);
+        long longDstDpid = HexString.toLong(dstDpid);
+        short shortDstPort = Short.parseShort(dstPort);
+        
+        Route result = routing.getRoute(longSrcDpid, shortSrcPort, longDstDpid, shortDstPort);
+        
+        if (result!=null) {
+            return routing.getRoute(longSrcDpid, shortSrcPort, longDstDpid, shortDstPort).getPath();
+        }
+        else {
+            log.debug("ERROR! no route found");
+            return null;
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java
new file mode 100644
index 0000000..f52d27a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java
@@ -0,0 +1,72 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.topology.web;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.topology.ITopologyService;
+
+import org.openflow.util.HexString;
+import org.restlet.data.Form;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+/**
+ * Returns a JSON map of <ClusterId, List<SwitchDpids>>
+ */
+public class SwitchClustersResource extends ServerResource {
+    @Get("json")
+    public Map<String, List<String>> retrieve() {
+        IFloodlightProviderService floodlightProvider = 
+                (IFloodlightProviderService)getContext().getAttributes().
+                    get(IFloodlightProviderService.class.getCanonicalName());
+        ITopologyService topology = 
+                (ITopologyService)getContext().getAttributes().
+                    get(ITopologyService.class.getCanonicalName());
+
+        Form form = getQuery();
+        String queryType = form.getFirstValue("type", true);
+        boolean openflowDomain = true;
+        if (queryType != null && "l2".equals(queryType)) {
+            openflowDomain = false;
+        }
+        
+        Map<String, List<String>> switchClusterMap = new HashMap<String, List<String>>();
+        for (Entry<Long, IOFSwitch> entry : floodlightProvider.getSwitches().entrySet()) {
+            Long clusterDpid = 
+                    (openflowDomain
+                     ? topology.getOpenflowDomainId(entry.getValue().getId())
+                     :topology.getL2DomainId(entry.getValue().getId()));
+            List<String> switchesInCluster = switchClusterMap.get(HexString.toHexString(clusterDpid));
+            if (switchesInCluster != null) {
+                switchesInCluster.add(HexString.toHexString(entry.getKey()));              
+            } else {
+                List<String> l = new ArrayList<String>();
+                l.add(HexString.toHexString(entry.getKey()));
+                switchClusterMap.put(HexString.toHexString(clusterDpid), l);
+            }
+        }
+        return switchClusterMap;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java
new file mode 100644
index 0000000..7989413
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java
@@ -0,0 +1,33 @@
+package net.floodlightcontroller.topology.web;
+
+import org.restlet.Context;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.linkdiscovery.web.LinksResource;
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class TopologyWebRoutable implements RestletRoutable {
+    /**
+     * Create the Restlet router and bind to the proper resources.
+     */
+    @Override
+    public Router getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/links/json", LinksResource.class);
+        router.attach("/tunnellinks/json", TunnelLinksResource.class);
+        router.attach("/switchclusters/json", SwitchClustersResource.class);
+        router.attach("/broadcastdomainports/json", BroadcastDomainPortsResource.class);
+        router.attach("/enabledports/json", EnabledPortsResource.class);
+        router.attach("/blockedports/json", BlockedPortsResource.class);
+        router.attach("/route/{src-dpid}/{src-port}/{dst-dpid}/{dst-port}/json", RouteResource.class);
+        return router;
+    }
+
+    /**
+     * Set the base path for the Topology
+     */
+    @Override
+    public String basePath() {
+        return "/wm/topology";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TunnelLinksResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TunnelLinksResource.java
new file mode 100644
index 0000000..71c3f12
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TunnelLinksResource.java
@@ -0,0 +1,20 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.Set;
+
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class TunnelLinksResource extends ServerResource {
+    @Get("json")
+    public Set<NodePortTuple> retrieve() {
+        ITopologyService topology = 
+                (ITopologyService)getContext().getAttributes().
+                    get(ITopologyService.class.getCanonicalName());
+        
+        return topology.getTunnelPorts();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/ui/web/StaticWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/ui/web/StaticWebRoutable.java
new file mode 100644
index 0000000..c1d5b5f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/ui/web/StaticWebRoutable.java
@@ -0,0 +1,70 @@
+package net.floodlightcontroller.ui.web;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import org.restlet.Client;
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.data.Protocol;
+import org.restlet.resource.Directory;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class StaticWebRoutable implements RestletRoutable, IFloodlightModule {
+
+	private IRestApiService restApi;
+	
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IRestApiService.class);
+        return l;
+    }
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        return null;
+    }
+    
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+                                             throws FloodlightModuleException {
+        restApi = context.getServiceImpl(IRestApiService.class);
+    }
+    
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // Add our REST API
+        restApi.addRestletRoutable(this);
+        
+    }
+
+	@Override
+	public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("", new Directory(context, "clap://classloader/web/"));
+        context.setClientDispatcher(new Client(context, Protocol.CLAP));
+        return router;
+	}
+
+	@Override
+	public String basePath() {
+		return "/ui/";
+	}
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleAction.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleAction.java
new file mode 100644
index 0000000..0d82275
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleAction.java
@@ -0,0 +1,54 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public enum BundleAction {
+    START,
+    STOP,
+    UNINSTALL,
+    REFRESH;
+
+    public static List<BundleAction> getAvailableActions(BundleState state) {
+        List<BundleAction> actions = new ArrayList<BundleAction>();
+        if (Arrays.binarySearch(new BundleState[] {
+                BundleState.ACTIVE, BundleState.STARTING,
+                BundleState.UNINSTALLED }, state) < 0) {
+            actions.add(START);
+        }
+        if (Arrays.binarySearch(new BundleState[] {
+                BundleState.ACTIVE}, state) >= 0) {
+            actions.add(STOP);
+        }
+        if (Arrays.binarySearch(new BundleState[] {
+                BundleState.UNINSTALLED}, state) < 0) {
+            actions.add(UNINSTALL);
+        }
+
+        // Always capable of refresh?
+        actions.add(REFRESH);
+        return actions;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleState.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleState.java
new file mode 100644
index 0000000..f89bc0b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleState.java
@@ -0,0 +1,62 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.util;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public enum BundleState {
+    ACTIVE              (32),
+    INSTALLED           (2),
+    RESOLVED            (4),
+    STARTING            (8),
+    STOPPING            (16),
+    UNINSTALLED         (1);
+
+    protected int value;
+
+    private BundleState(int value) {
+        this.value = value;
+    }
+
+    /**
+     * @return the value
+     */
+    public int getValue() {
+        return value;
+    }
+
+    public static BundleState getState(int value) {
+        switch (value) {
+            case 32:
+                return ACTIVE;
+            case 2:
+                return INSTALLED;
+            case 4:
+                return RESOLVED;
+            case 8:
+                return STARTING;
+            case 16:
+                return STOPPING;
+            case 1:
+                return UNINSTALLED;
+        }
+        return null;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/ClusterDFS.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/ClusterDFS.java
new file mode 100644
index 0000000..3efd5bc
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/ClusterDFS.java
@@ -0,0 +1,47 @@
+package net.floodlightcontroller.util;
+
+public class ClusterDFS {
+    long dfsIndex;
+    long parentDFSIndex;
+    long lowpoint;
+    boolean visited;
+
+    public ClusterDFS() {
+        visited = false;
+        dfsIndex = Long.MAX_VALUE;
+        parentDFSIndex = Long.MAX_VALUE;
+        lowpoint = Long.MAX_VALUE;
+    }
+
+    public long getDfsIndex() {
+        return dfsIndex;
+    }
+
+    public void setDfsIndex(long dfsIndex) {
+        this.dfsIndex = dfsIndex;
+    }
+
+    public long getParentDFSIndex() {
+        return parentDFSIndex;
+    }
+
+    public void setParentDFSIndex(long parentDFSIndex) {
+        this.parentDFSIndex = parentDFSIndex;
+    }
+
+    public long getLowpoint() {
+        return lowpoint;
+    }
+
+    public void setLowpoint(long lowpoint) {
+        this.lowpoint = lowpoint;
+    }
+
+    public boolean isVisited() {
+        return visited;
+    }
+
+    public void setVisited(boolean visited) {
+        this.visited = visited;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistory.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistory.java
new file mode 100644
index 0000000..69031ba
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistory.java
@@ -0,0 +1,179 @@
+/**
+ * 
+ */
+package net.floodlightcontroller.util;
+
+import java.util.ArrayList;
+
+/**
+ * @author subrata
+ *
+ */
+
+public class EventHistory<T> {
+    public static final int EV_HISTORY_DEFAULT_SIZE = 1024;
+
+    public String  description;
+    public int     event_history_size;
+    public int     current_index;
+    public boolean full; // true if all are in use
+    public ArrayList<Event> events;
+
+    public String getDescription() {
+        return description;
+    }
+    public int getEvent_history_size() {
+        return event_history_size;
+    }
+    public int getCurrent_index() {
+        return current_index;
+    }
+    public boolean isFull() {
+        return full;
+    }
+    public ArrayList<Event> getEvents() {
+        return events;
+    }
+
+    public class Event {
+        public EventHistoryBaseInfo base_info;
+        public T info;
+    }
+
+    public enum EvState {
+        FREE,             // no valid event written yet
+        BEING_MODIFIED,   // event is being updated with new value, skip
+        ACTIVE,           // event is active and can be displayed
+    }
+
+    public enum EvAction {
+        ADDED,   // specific entry added
+        REMOVED, // specific entry removed
+        UPDATED, // Entry updated
+        BLOCKED, // Blocked - used for Attachment Points
+        UNBLOCKED,
+        CLEARED,  // All entries are removed
+        PKT_IN,
+        PKT_OUT,
+        SWITCH_CONNECTED,
+        SWITCH_DISCONNECTED,
+        LINK_ADDED,
+        LINK_DELETED,
+        LINK_PORT_STATE_UPDATED,
+        CLUSTER_ID_CHANGED_FOR_CLUSTER,
+        CLUSTER_ID_CHANGED_FOR_A_SWITCH,
+    }
+
+    // Constructor
+    public EventHistory(int maxEvents, String desc) {
+        events = new ArrayList<Event>(maxEvents);
+
+        for (int idx = 0; idx < maxEvents; idx++) {
+            Event evH     = new Event();
+            evH.base_info = new EventHistoryBaseInfo();
+            evH.info      = null;
+            evH.base_info.state = EvState.FREE;
+            evH.base_info.idx   = idx;
+            events.add(idx, evH);
+        }
+
+        description = "Event-History:" + desc;
+        event_history_size   = maxEvents;
+        current_index        = 0;
+        full                 = false;
+    }
+
+    // Constructor for default size
+    public EventHistory(String desc) {
+        this(EV_HISTORY_DEFAULT_SIZE, desc);
+    }
+
+    // Copy constructor - copy latest k items of the event history
+    public EventHistory(EventHistory<T> eventHist, int latestK) {
+
+        if (eventHist == null) {
+            description = "No event found";
+            return;
+        }
+        int curSize = (eventHist.full)?eventHist.event_history_size:
+                                                    eventHist.current_index;
+        int size  = (latestK < curSize)?latestK:curSize;
+        int evIdx = eventHist.current_index;
+        int topSz = (evIdx >= size)?size:evIdx;
+
+        // Need to create a new one since size is different
+        events = new ArrayList<Event>(size);
+
+        // Get the top part
+        int origIdx = evIdx;
+        for (int idx = 0; idx < topSz; idx++) {
+            Event evH         = eventHist.events.get(--origIdx);
+            evH.base_info.idx = idx;
+            events.add(idx, evH);
+        }
+
+        // Get the bottom part
+        origIdx = eventHist.event_history_size;
+        for (int idx = topSz; idx < size; idx++) {
+            Event evH = eventHist.events.get(--origIdx);
+            evH.base_info.idx = idx;
+            events.add(idx, evH);
+        }
+
+        description = eventHist.description;
+        event_history_size   = size;
+        current_index        = 0; // since it is full
+        full                 = true;
+    }
+
+    // Get an index for writing a new event. This method is synchronized for
+    // this event history infra. to be thread-safe. Once the index is obtained
+    // by the caller event at the index is updated without any lock
+    public synchronized int NextIdx() {
+        // curIdx should be in the 0 to evArraySz-1
+        if (current_index == (event_history_size-1)) {
+            current_index = 0;
+            full = true;
+            return (event_history_size-1);
+        } else {
+            current_index++;
+            return (current_index-1);
+        }
+    }
+
+    /**
+     * Add an event to the event history
+     * Eliminate java garbage cration by reusing the same object T
+     * Supplied object t is used to populate the event history array
+     * and the current object at that array location is returned to the
+     * calling process so that the calling process can use that object
+     * for the next event of the same type
+     * @param t
+     * @param op
+     * @return
+     */
+
+    public T put(T t, EvAction action) {
+        int idx = NextIdx();
+        Event evH = events.get(idx);
+        evH.base_info.state = EvState.BEING_MODIFIED;
+        evH.base_info.time_ms = System.currentTimeMillis();
+        evH.base_info.action = action;
+        T temp = evH.info;
+        evH.info = t;
+        evH.base_info.state = EvState.ACTIVE;
+        return temp;
+    }
+
+    /***
+     * Clear the event history, needs to be done under lock
+     */
+    public void clear() {
+        for (int idx = 0; idx < event_history_size; idx++) {
+            Event evH = events.get(idx);
+            evH.base_info.state = EvState.FREE;
+            current_index = 0;
+            full = false;
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java
new file mode 100644
index 0000000..74fc973
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.util;
+
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+@JsonSerialize(using=EventHistoryBaseInfoJSONSerializer.class)
+public class EventHistoryBaseInfo {
+    public int              idx;
+    public long             time_ms; // timestamp in milliseconds
+    public EventHistory.EvState          state;
+    public EventHistory.EvAction         action;
+
+    // Getters
+    public int getIdx() {
+        return idx;
+    }
+    public long getTime_ms() {
+        return time_ms;
+    }
+    public EventHistory.EvState getState() {
+        return state;
+    }
+    public EventHistory.EvAction getAction() {
+        return action;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java
new file mode 100644
index 0000000..6f1d1ff
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java
@@ -0,0 +1,69 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.util;
+
+import java.io.IOException;
+
+import java.sql.Timestamp;
+
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+
+/**
+ * @author subrata
+ *
+ */
+
+public class EventHistoryBaseInfoJSONSerializer extends 
+                                    JsonSerializer<EventHistoryBaseInfo> {
+
+ 
+    /**
+     * Performs the serialization of a EventHistory.BaseInfo object
+     */
+    @Override
+    public void serialize(EventHistoryBaseInfo base_info, JsonGenerator jGen,
+                    SerializerProvider serializer) 
+                    throws IOException, JsonProcessingException {
+        jGen.writeStartObject();
+        jGen.writeNumberField("Idx",    base_info.getIdx());
+        Timestamp ts = new Timestamp(base_info.getTime_ms());
+        String tsStr = ts.toString();
+        while (tsStr.length() < 23) {
+            tsStr = tsStr.concat("0");
+        }
+        jGen.writeStringField("Time", tsStr);
+        jGen.writeStringField("State",  base_info.getState().name());
+        String acStr = base_info.getAction().name().toLowerCase();
+        // Capitalize the first letter
+        acStr = acStr.substring(0,1).toUpperCase().concat(acStr.substring(1));
+        jGen.writeStringField("Action", acStr);
+        jGen.writeEndObject();
+    }
+
+    /**
+     * Tells SimpleModule that we are the serializer for OFMatch
+     */
+    @Override
+    public Class<EventHistoryBaseInfo> handledType() {
+        return EventHistoryBaseInfo.class;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/FilterIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/FilterIterator.java
new file mode 100644
index 0000000..47cd5c9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/FilterIterator.java
@@ -0,0 +1,80 @@
+/**
+*    Copyright 2012, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An iterator that will filter values from an iterator and return only
+ * those values that match the predicate.
+ */
+public abstract class FilterIterator<T> implements Iterator<T> {
+    protected Iterator<T> subIterator;
+    protected T next;
+
+    /**
+     * Construct a filter iterator from the given sub iterator
+     * @param subIterator the sub iterator over which we'll filter
+     */
+    public FilterIterator(Iterator<T> subIterator) {
+        super();
+        this.subIterator = subIterator;
+    }
+
+    /**
+     * Check whether the given value should be returned by the
+     * filter
+     * @param value the value to check
+     * @return true if the value should be included
+     */
+    protected abstract boolean matches(T value);
+    
+    // ***********
+    // Iterator<T>
+    // ***********
+
+    @Override
+    public boolean hasNext() {
+        if (next != null) return true;
+        
+        while (subIterator.hasNext()) {
+            next = subIterator.next();
+            if (matches(next))
+                return true;
+        }
+        next = null;
+        return false;
+    }
+
+    @Override
+    public T next() {
+        if (hasNext()) {
+            T cur = next;
+            next = null;
+            return cur;
+        }
+        throw new NoSuchElementException();
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/IterableIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/IterableIterator.java
new file mode 100644
index 0000000..584de08
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/IterableIterator.java
@@ -0,0 +1,66 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over all values in an iterator of iterators
+ *
+ * @param <T> the type of elements returned by this iterator
+ */
+public class IterableIterator<T> implements Iterator<T> {
+    Iterator<? extends Iterable<T>> subIterator;
+    Iterator<T> current = null;
+    
+    public IterableIterator(Iterator<? extends Iterable<T>> subIterator) {
+        super();
+        this.subIterator = subIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (current == null) {
+            if (subIterator.hasNext()) {
+                current = subIterator.next().iterator();
+            } else {
+                return false;
+            }
+        }
+        while (!current.hasNext() && subIterator.hasNext()) {
+            current = subIterator.next().iterator();
+        }
+        
+        return current.hasNext();
+    }
+
+    @Override
+    public T next() {
+        if (hasNext())
+            return current.next();
+        throw new NoSuchElementException();
+    }
+
+    @Override
+    public void remove() {
+        if (hasNext())
+            current.remove();
+        throw new NoSuchElementException();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/LRUHashMap.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/LRUHashMap.java
new file mode 100644
index 0000000..477e886
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/LRUHashMap.java
@@ -0,0 +1,38 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class LRUHashMap<K, V> extends LinkedHashMap<K, V> {
+    
+    private static final long serialVersionUID = 1L;
+    
+    private final int capacity;
+    public LRUHashMap(int capacity)
+    {
+        super(capacity+1, 0.75f, true);
+        this.capacity = capacity;
+    }
+    
+    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
+       return size() > capacity;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MACAddress.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MACAddress.java
new file mode 100644
index 0000000..4ba9dad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MACAddress.java
@@ -0,0 +1,157 @@
+package net.floodlightcontroller.util;
+
+import java.util.Arrays;
+
+/**
+ * The class representing MAC address.
+ *
+ * @author Sho Shimizu (sho.shimizu@gmail.com)
+ */
+public class MACAddress {
+    public static final int MAC_ADDRESS_LENGTH = 6;
+    private byte[] address = new byte[MAC_ADDRESS_LENGTH];
+
+    public MACAddress(byte[] address) {
+        this.address = Arrays.copyOf(address, MAC_ADDRESS_LENGTH);
+    }
+
+    /**
+     * Returns a MAC address instance representing the value of the specified {@code String}.
+     * @param address the String representation of the MAC Address to be parsed.
+     * @return a MAC Address instance representing the value of the specified {@code String}.
+     * @throws IllegalArgumentException if the string cannot be parsed as a MAC address.
+     */
+    public static MACAddress valueOf(String address) {
+        String[] elements = address.split(":");
+        if (elements.length != MAC_ADDRESS_LENGTH) {
+            throw new IllegalArgumentException(
+                    "Specified MAC Address must contain 12 hex digits" +
+                    " separated pairwise by :'s.");
+        }
+
+        byte[] addressInBytes = new byte[MAC_ADDRESS_LENGTH];
+        for (int i = 0; i < MAC_ADDRESS_LENGTH; i++) {
+            String element = elements[i];
+            addressInBytes[i] = (byte)Integer.parseInt(element, 16);
+        }
+
+        return new MACAddress(addressInBytes);
+    }
+
+    /**
+     * Returns a MAC address instance representing the specified {@code byte} array.
+     * @param address the byte array to be parsed.
+     * @return a MAC address instance representing the specified {@code byte} array.
+     * @throws IllegalArgumentException if the byte array cannot be parsed as a MAC address.
+     */
+    public static MACAddress valueOf(byte[] address) {
+        if (address.length != MAC_ADDRESS_LENGTH) {
+            throw new IllegalArgumentException("the length is not " + MAC_ADDRESS_LENGTH);
+        }
+
+        return new MACAddress(address);
+    }
+
+    /**
+     * Returns a MAC address instance representing the specified {@code long} value.
+     * The lower 48 bits of the long value are used to parse as a MAC address.
+     * @param address the long value to be parsed. The lower 48 bits are used for a MAC address.
+     * @return a MAC address instance representing the specified {@code long} value.
+     * @throws IllegalArgumentException if the long value cannot be parsed as a MAC address.
+     */
+    public static MACAddress valueOf(long address) {
+        byte[] addressInBytes = new byte[] {
+                (byte)((address >> 40) & 0xff),
+                (byte)((address >> 32) & 0xff),
+                (byte)((address >> 24) & 0xff),
+                (byte)((address >> 16) & 0xff),
+                (byte)((address >> 8 ) & 0xff),
+                (byte)((address >> 0) & 0xff)
+        };
+
+        return new MACAddress(addressInBytes);
+    }
+
+    /**
+     * Returns the length of the {@code MACAddress}.
+     * @return the length of the {@code MACAddress}.
+     */
+    public int length() {
+        return address.length;
+    }
+
+    /**
+     * Returns the value of the {@code MACAddress} as a {@code byte} array.
+     * @return the numeric value represented by this object after conversion to type {@code byte} array.
+     */
+    public byte[] toBytes() {
+        return Arrays.copyOf(address, address.length);
+    }
+
+    /**
+     * Returns the value of the {@code MACAddress} as a {@code long}.
+     * @return the numeric value represented by this object after conversion to type {@code long}.
+     */
+    public long toLong() {
+        long mac = 0;
+        for (int i = 0; i < 6; i++) {
+            long t = (address[i] & 0xffL) << ((5 - i) * 8);
+            mac |= t;
+        }
+        return mac;
+    }
+
+    /**
+     * Returns {@code true} if the MAC address is the broadcast address.
+     * @return {@code true} if the MAC address is the broadcast address.
+     */
+    public boolean isBroadcast() {
+        for (byte b : address) {
+            if (b != -1) // checks if equal to 0xff
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns {@code true} if the MAC address is the multicast address.
+     * @return {@code true} if the MAC address is the multicast address.
+     */
+    public boolean isMulticast() {
+        if (isBroadcast()) {
+            return false;
+        }
+        return (address[0] & 0x01) != 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (!(o instanceof MACAddress)) {
+            return false;
+        }
+
+        MACAddress other = (MACAddress)o;
+        return Arrays.equals(this.address, other.address);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(this.address);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        for (byte b: address) {
+            if (builder.length() > 0) {
+                builder.append(":");
+            }
+            builder.append(String.format("%02X", b & 0xFF));
+        }
+        return builder.toString();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MultiIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MultiIterator.java
new file mode 100644
index 0000000..bcbc916
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MultiIterator.java
@@ -0,0 +1,66 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over all values in an iterator of iterators
+ *
+ * @param <T> the type of elements returned by this iterator
+ */
+public class MultiIterator<T> implements Iterator<T> {
+    Iterator<Iterator<T>> subIterator;
+    Iterator<T> current = null;
+    
+    public MultiIterator(Iterator<Iterator<T>> subIterator) {
+        super();
+        this.subIterator = subIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (current == null) {
+            if (subIterator.hasNext()) {
+                current = subIterator.next();
+            } else {
+                return false;
+            }
+        }
+        while (!current.hasNext() && subIterator.hasNext()) {
+            current = subIterator.next();
+        }
+        
+        return current.hasNext();
+    }
+
+    @Override
+    public T next() {
+        if (hasNext())
+            return current.next();
+        throw new NoSuchElementException();
+    }
+
+    @Override
+    public void remove() {
+        if (hasNext())
+            current.remove();
+        throw new NoSuchElementException();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java
new file mode 100644
index 0000000..4dfb60b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright Big Switch Networks 2012
+ */
+
+package net.floodlightcontroller.util;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.Set;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFSwitch;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+
+/**
+ * Dampens OFMessages sent to an OF switch. A message is only written to 
+ * a switch if the same message (as defined by .equals()) has not been written
+ * in the last n milliseconds. Timer granularity is based on TimedCache
+ * @author gregor
+ *
+ */
+public class OFMessageDamper {
+    /**
+     * An entry in the TimedCache. A cache entry consists of the sent message
+     * as well as the switch to which the message was sent. 
+     * 
+     * NOTE: We currently use the full OFMessage object. To save space, we 
+     * could use a cryptographic hash (e.g., SHA-1). However, this would 
+     * obviously be more time-consuming.... 
+     * 
+     * We also store a reference to the actual IOFSwitch object and /not/
+     * the switch DPID. This way we are guarnteed to not dampen messages if
+     * a switch disconnects and then reconnects.
+     * 
+     * @author gregor
+     */
+    protected static class DamperEntry {
+        OFMessage msg;
+        IOFSwitch sw;
+        public DamperEntry(OFMessage msg, IOFSwitch sw) {
+            super();
+            this.msg = msg;
+            this.sw = sw;
+        }
+        /* (non-Javadoc)
+         * @see java.lang.Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((msg == null) ? 0 : msg.hashCode());
+            result = prime * result + ((sw == null) ? 0 : sw.hashCode());
+            return result;
+        }
+        /* (non-Javadoc)
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            if (obj == null) return false;
+            if (getClass() != obj.getClass()) return false;
+            DamperEntry other = (DamperEntry) obj;
+            if (msg == null) {
+                if (other.msg != null) return false;
+            } else if (!msg.equals(other.msg)) return false;
+            if (sw == null) {
+                if (other.sw != null) return false;
+            } else if (!sw.equals(other.sw)) return false;
+            return true;
+        }
+        
+      
+    }
+    TimedCache<DamperEntry> cache;
+    EnumSet<OFType> msgTypesToCache;
+    /**
+     * 
+     * @param capacity the maximum number of messages that should be 
+     * kept
+     * @param typesToDampen The set of OFMessageTypes that should be 
+     * dampened by this instance. Other types will be passed through
+     * @param timeout The dampening timeout. A message will only be
+     * written if the last write for the an equal message more than
+     * timeout ms ago. 
+     */
+    public OFMessageDamper(int capacity, 
+                           Set<OFType> typesToDampen,  
+                           int timeout) {
+        cache = new TimedCache<DamperEntry>(capacity, timeout);
+        msgTypesToCache = EnumSet.copyOf(typesToDampen);
+    }        
+    
+    /**
+     * write the messag to the switch according to our dampening settings
+     * @param sw
+     * @param msg
+     * @param cntx
+     * @return true if the message was written to the switch, false if
+     * the message was dampened. 
+     * @throws IOException
+     */
+    public boolean write(IOFSwitch sw, OFMessage msg, FloodlightContext cntx)
+                    throws IOException {
+        return write(sw, msg, cntx, false);
+    }
+    
+    /**
+     * write the messag to the switch according to our dampening settings
+     * @param sw
+     * @param msg
+     * @param cntx
+     * @param flush true to flush the packet immidiately
+     * @return true if the message was written to the switch, false if
+     * the message was dampened. 
+     * @throws IOException
+     */
+    public boolean write(IOFSwitch sw, OFMessage msg,
+                        FloodlightContext cntx, boolean flush) 
+            throws IOException {
+        if (! msgTypesToCache.contains(msg.getType())) {
+            sw.write(msg, cntx);
+            if (flush) {
+                sw.flush();
+            }
+            return true;
+        }
+        
+        DamperEntry entry = new DamperEntry(msg, sw);
+        if (cache.update(entry)) {
+            // entry exists in cache. Dampening.
+            return false; 
+        } else {
+            sw.write(msg, cntx);
+            if (flush) {
+                sw.flush();
+            }
+            return true;
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/TimedCache.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/TimedCache.java
new file mode 100644
index 0000000..7341df7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/TimedCache.java
@@ -0,0 +1,79 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.util;
+
+import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * The key is any object/hash-code
+ * The value is time-stamp in milliseconds
+ * The time interval denotes the interval for which the entry should remain in the hashmap.
+ * If an entry is present in the Linkedhashmap, it does not mean that it's valid (recently seen)
+ * 
+ * @param <K> Type of the values in this cache
+ */
+public class TimedCache<K> {    
+    private final long timeoutInterval;    //specified in milliseconds.
+	private ConcurrentMap<K, Long> cache;
+    
+    /**
+     * 
+     * @param capacity the maximum number of entries in the cache before the 
+     * oldest entry is evicted. 
+     * @param timeToLive specified in milliseconds
+     */
+	public TimedCache(int capacity, int timeToLive) {
+        cache = new ConcurrentLinkedHashMap.Builder<K, Long>()
+        	    .maximumWeightedCapacity(capacity)
+            .build();
+        this.timeoutInterval = timeToLive;
+    }
+    
+    public long getTimeoutInterval() {
+        return this.timeoutInterval;
+    }
+    
+    /**
+     * Always try to update the cache and set the last-seen value for this key.
+     * 
+     * Return true, if a valid existing field was updated, else return false.
+     * (note: if multiple threads update simultaneously, one of them will succeed,
+     *  other wills return false)
+     * 
+     * @param key
+     * @return boolean
+     */
+    public boolean update(K key)
+    {
+        Long curr = new Long(System.currentTimeMillis());
+        Long prev = cache.putIfAbsent(key, curr);
+        
+        if (prev == null) {
+        		return false;
+        }
+
+        if (curr - prev > this.timeoutInterval) {
+            if (cache.replace(key, prev, curr)) {
+            		return false;
+            }
+        }
+        
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java
new file mode 100644
index 0000000..6021e3d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java
@@ -0,0 +1,95 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.io.IOException;
+
+import net.floodlightcontroller.util.MACAddress;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.restlet.data.Status;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Put;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HostResource extends org.restlet.resource.ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(HostResource.class);
+    
+    public class HostDefinition {
+        String port = null; // Logical port name
+        String guid = null; // Network ID
+        String mac = null; // MAC Address
+        String attachment = null; // Attachment name
+    }
+    
+    protected void jsonToHostDefinition(String json, HostDefinition host) throws IOException {
+        MappingJsonFactory f = new MappingJsonFactory();
+        JsonParser jp;
+        
+        try {
+            jp = f.createJsonParser(json);
+        } catch (JsonParseException e) {
+            throw new IOException(e);
+        }
+        
+        jp.nextToken();
+        if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
+            throw new IOException("Expected START_OBJECT");
+        }
+        
+        while (jp.nextToken() != JsonToken.END_OBJECT) {
+            if (jp.getCurrentToken() != JsonToken.FIELD_NAME) {
+                throw new IOException("Expected FIELD_NAME");
+            }
+            
+            String n = jp.getCurrentName();
+            jp.nextToken();
+            if (jp.getText().equals("")) 
+                continue;
+            else if (n.equals("attachment")) {
+                while (jp.nextToken() != JsonToken.END_OBJECT) {
+                    String field = jp.getCurrentName();
+                    if (field.equals("id")) {
+                        host.attachment = jp.getText();
+                    } else if (field.equals("mac")) {
+                        host.mac = jp.getText();
+                    }
+                }
+            }
+        }
+        
+        jp.close();
+    }
+    
+    @Put
+    public String addHost(String postData) {
+        IVirtualNetworkService vns =
+                (IVirtualNetworkService)getContext().getAttributes().
+                    get(IVirtualNetworkService.class.getCanonicalName());
+        HostDefinition host = new HostDefinition();
+        host.port = (String) getRequestAttributes().get("port");
+        host.guid = (String) getRequestAttributes().get("network");
+        try {
+            jsonToHostDefinition(postData, host);
+        } catch (IOException e) {
+            log.error("Could not parse JSON {}", e.getMessage());
+        }
+        vns.addHost(MACAddress.valueOf(host.mac), host.guid, host.port);
+        setStatus(Status.SUCCESS_OK);
+        return "{\"status\":\"ok\"}";
+    }
+    
+    
+    @Delete
+    public String deleteHost() {
+        String port = (String) getRequestAttributes().get("port");
+        IVirtualNetworkService vns =
+                (IVirtualNetworkService)getContext().getAttributes().
+                    get(IVirtualNetworkService.class.getCanonicalName());
+        vns.deleteHost(null, port);
+        setStatus(Status.SUCCESS_OK);
+        return "{\"status\":\"ok\"}";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java
new file mode 100644
index 0000000..4304a33
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java
@@ -0,0 +1,46 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.util.Collection;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.util.MACAddress;
+
+public interface IVirtualNetworkService extends IFloodlightService {
+    /**
+     * Creates a new virtual network. This can also be called
+     * to modify a virtual network. To update a network you specify the GUID
+     * and the fields you want to update.
+     * @param network The network name. Must be unique.
+     * @param guid The ID of the network. Must be unique.
+     * @param gateway The IP address of the network gateway, null if none.
+     */
+    public void createNetwork(String guid, String network, Integer gateway);
+    
+    /**
+     * Deletes a virtual network.
+     * @param guid The ID (not name) of virtual network to delete.
+     */
+    public void deleteNetwork(String guid);
+    
+    /**
+     * Adds a host to a virtual network. If a mapping already exists the
+     * new one will override the old mapping.
+     * @param mac The MAC address of the host to add.
+     * @param network The network to add the host to.
+     * @param port The logical port name to attach the host to. Must be unique.
+     */
+    public void addHost(MACAddress mac, String network, String port); 
+    
+    /**
+     * Deletes a host from a virtual network. Either the MAC or Port must
+     * be specified.
+     * @param mac The MAC address to delete.
+     * @param port The logical port the host is attached to.
+     */
+    public void deleteHost(MACAddress mac, String port);
+    
+    /**
+     * Return list of all virtual networks.
+     * @return Collection <VirtualNetwork>
+     */
+    public Collection <VirtualNetwork> listNetworks();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java
new file mode 100644
index 0000000..2efe52a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java
@@ -0,0 +1,133 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import net.floodlightcontroller.packet.IPv4;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.restlet.data.Status;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.restlet.resource.Put;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NetworkResource extends ServerResource {
+    protected static Logger log = LoggerFactory.getLogger(NetworkResource.class);
+    
+    public class NetworkDefinition {
+        public String name = null;
+        public String guid = null;
+        public String gateway = null;
+    }
+    
+    protected void jsonToNetworkDefinition(String json, NetworkDefinition network) throws IOException {
+        MappingJsonFactory f = new MappingJsonFactory();
+        JsonParser jp;
+        
+        try {
+            jp = f.createJsonParser(json);
+        } catch (JsonParseException e) {
+            throw new IOException(e);
+        }
+        
+        jp.nextToken();
+        if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
+            throw new IOException("Expected START_OBJECT");
+        }
+        
+        while (jp.nextToken() != JsonToken.END_OBJECT) {
+            if (jp.getCurrentToken() != JsonToken.FIELD_NAME) {
+                throw new IOException("Expected FIELD_NAME");
+            }
+            
+            String n = jp.getCurrentName();
+            jp.nextToken();
+            if (jp.getText().equals("")) 
+                continue;
+            else if (n.equals("network")) {
+                while (jp.nextToken() != JsonToken.END_OBJECT) {
+                    String field = jp.getCurrentName();
+                    if (field.equals("name")) {
+                        network.name = jp.getText();
+                    } else if (field.equals("gateway")) {
+                    	String gw = jp.getText();
+                    	if ((gw != null) && (!gw.equals("null")))
+                    		network.gateway = gw;
+                    } else if (field.equals("id")) {
+                    	network.guid = jp.getText();
+                    } else {
+                        log.warn("Unrecognized field {} in " +
+                        		"parsing network definition", 
+                        		jp.getText());
+                    }
+                }
+            }
+        }
+        
+        jp.close();
+    }
+    
+    @Get("json")
+    public Collection <VirtualNetwork> retrieve() {
+        IVirtualNetworkService vns =
+                (IVirtualNetworkService)getContext().getAttributes().
+                    get(IVirtualNetworkService.class.getCanonicalName());
+        
+        return vns.listNetworks();               
+    }
+    
+    @Put
+    @Post
+    public String createNetwork(String postData) {        
+        NetworkDefinition network = new NetworkDefinition();
+        try {
+            jsonToNetworkDefinition(postData, network);
+        } catch (IOException e) {
+            log.error("Could not parse JSON {}", e.getMessage());
+        }
+        
+        // We try to get the ID from the URI only if it's not
+        // in the POST data 
+        if (network.guid == null) {
+	        String guid = (String) getRequestAttributes().get("network");
+	        if ((guid != null) && (!guid.equals("null")))
+	        	network.guid = guid;
+        }
+        
+        IVirtualNetworkService vns =
+                (IVirtualNetworkService)getContext().getAttributes().
+                    get(IVirtualNetworkService.class.getCanonicalName());
+        
+        Integer gw = null;
+        if (network.gateway != null) {
+            try {
+                gw = IPv4.toIPv4Address(network.gateway);
+            } catch (IllegalArgumentException e) {
+                log.warn("Could not parse gateway {} as IP for network {}, setting as null",
+                         network.gateway, network.name);
+                network.gateway = null;
+            }
+        }
+        vns.createNetwork(network.guid, network.name, gw);
+        setStatus(Status.SUCCESS_OK);
+        return "{\"status\":\"ok\"}";
+    }
+    
+    @Delete
+    public String deleteNetwork() {
+        IVirtualNetworkService vns =
+                (IVirtualNetworkService)getContext().getAttributes().
+                    get(IVirtualNetworkService.class.getCanonicalName());
+        String guid = (String) getRequestAttributes().get("network");
+        vns.deleteNetwork(guid);
+        setStatus(Status.SUCCESS_OK);
+        return "{\"status\":\"ok\"}";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NoOp.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NoOp.java
new file mode 100644
index 0000000..a184a95
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NoOp.java
@@ -0,0 +1,21 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.restlet.resource.Put;
+import org.restlet.resource.ServerResource;
+
+public class NoOp extends ServerResource {
+	/**
+	 * Does nothing and returns 200 OK with a status message
+	 * @return status: ok
+	 */
+	@Get
+	@Put
+	@Post
+	public String noOp(String postdata) {
+		setStatus(Status.SUCCESS_OK);
+        return "{\"status\":\"ok\"}";
+	}
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java
new file mode 100644
index 0000000..f5dfb21
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java
@@ -0,0 +1,88 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import net.floodlightcontroller.util.MACAddress;
+
+/**
+ * Data structure for storing and outputing information of a virtual network created
+ * by VirtualNetworkFilter
+ * 
+ * @author KC Wang
+ */
+
+@JsonSerialize(using=VirtualNetworkSerializer.class)
+public class VirtualNetwork{
+    protected String name; // network name
+    protected String guid; // network id
+    protected String gateway; // network gateway
+    protected Collection<MACAddress> hosts; // array of hosts explicitly added to this network
+
+    /**
+     * Constructor requires network name and id
+     * @param name: network name
+     * @param guid: network id 
+     */
+    public VirtualNetwork(String name, String guid) {
+        this.name = name;
+        this.guid = guid;
+        this.gateway = null;
+        this.hosts = new ArrayList<MACAddress>();
+        return;        
+    }
+
+    /**
+     * Sets network name
+     * @param gateway: IP address as String
+     */
+    public void setName(String name){
+        this.name = name;
+        return;                
+    }
+    
+    /**
+     * Sets network gateway IP address
+     * @param gateway: IP address as String
+     */
+    public void setGateway(String gateway){
+        this.gateway = gateway;
+        return;                
+    }
+    
+    /**
+     * Adds a host to this network record
+     * @param host: MAC address as MACAddress
+     */
+    public void addHost(MACAddress host){
+        this.hosts.add(host);
+        return;        
+    }
+    
+    /**
+     * Removes a host from this network record
+     * @param host: MAC address as MACAddress
+     * @return boolean: true: removed, false: host not found
+     */
+    public boolean removeHost(MACAddress host){
+        Iterator<MACAddress> iter = this.hosts.iterator();
+        while(iter.hasNext()){
+            MACAddress element = iter.next();
+            if(element.equals(host) ){
+                //assuming MAC address for host is unique
+                iter.remove();
+                return true;
+            }                
+        }
+        return false;
+    }
+    
+    /**
+     * Removes all hosts from this network record
+     */
+    public void clearHosts(){
+        this.hosts.clear();
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java
new file mode 100644
index 0000000..012dfb6
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java
@@ -0,0 +1,521 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.AppCookie;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.packet.DHCP;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.routing.ForwardingBase;
+import net.floodlightcontroller.util.MACAddress;
+
+/**
+ * A simple Layer 2 (MAC based) network virtualization module. This module allows
+ * you to create simple L2 networks (host + gateway) and will drop traffic if
+ * they are not on the same virtual network.
+ * 
+ * LIMITATIONS
+ * - This module does not allow overlapping of IPs or MACs
+ * - You can only have 1 gateway per virtual network (can be shared)
+ * - There is filtering of multicast/broadcast traffic
+ * - All DHCP traffic will be allowed, regardless of unicast/broadcast
+ * 
+ * @author alexreimers
+ */
+public class VirtualNetworkFilter 
+    implements IFloodlightModule, IVirtualNetworkService, IOFMessageListener, IDeviceListener {
+    protected static Logger log = LoggerFactory.getLogger(VirtualNetworkFilter.class);
+    
+    private final short APP_ID = 20;
+    
+    // Our dependencies
+    IFloodlightProviderService floodlightProvider;
+    IRestApiService restApi;
+    IDeviceService deviceService;
+    
+    // Our internal state
+    protected Map<String, VirtualNetwork> vNetsByGuid; // List of all created virtual networks 
+    protected Map<String, String> nameToGuid; // Logical name -> Network ID
+    protected Map<String, Integer> guidToGateway; // Network ID -> Gateway IP
+    protected Map<Integer, Set<String>> gatewayToGuid; // Gateway IP -> Network ID
+    protected Map<MACAddress, Integer> macToGateway; // Gateway MAC -> Gateway IP
+    protected Map<MACAddress, String> macToGuid; // Host MAC -> Network ID
+    protected Map<String, MACAddress> portToMac; // Host MAC -> logical port name
+    
+    /**
+     * Adds a gateway to a virtual network.
+     * @param guid The ID (not name) of the network.
+     * @param ip The IP addresses of the gateway.
+     */
+    protected void addGateway(String guid, Integer ip) {
+        if (ip.intValue() != 0) {
+        	if (log.isDebugEnabled())
+        		log.debug("Adding {} as gateway for GUID {}",
+        				IPv4.fromIPv4Address(ip), guid);
+        	
+            guidToGateway.put(guid, ip);
+            if (vNetsByGuid.get(guid) != null)
+                vNetsByGuid.get(guid).setGateway(IPv4.fromIPv4Address(ip));
+            if (gatewayToGuid.containsKey(ip)) {
+                Set<String> gSet = gatewayToGuid.get(ip);
+                gSet.add(guid);
+            } else {
+                Set<String> gSet = Collections.synchronizedSet(new HashSet<String>());
+                gSet.add(guid);
+                gatewayToGuid.put(ip, gSet);
+            }
+        }
+    }
+    
+    /**
+     * Deletes a gateway for a virtual network.
+     * @param guid The ID (not name) of the network to delete
+     * the gateway for.
+     */
+    protected void deleteGateway(String guid) {
+        Integer gwIp = guidToGateway.remove(guid);
+        if (gwIp == null) return;
+        Set<String> gSet = gatewayToGuid.get(gwIp);
+        gSet.remove(guid);
+        if(vNetsByGuid.get(guid)!=null)
+            vNetsByGuid.get(guid).setGateway(null);
+    }
+    
+    // IVirtualNetworkService
+    
+    @Override
+    public void createNetwork(String guid, String network, Integer gateway) {
+        if (log.isDebugEnabled()) {
+            String gw = null;
+            try {
+                gw = IPv4.fromIPv4Address(gateway);
+            } catch (Exception e) {
+                // fail silently
+            }
+            log.debug("Creating network {} with ID {} and gateway {}", 
+                      new Object[] {network, guid, gw});
+        }
+        
+        if (!nameToGuid.isEmpty()) {
+            // We have to iterate all the networks to handle name/gateway changes
+            for (Entry<String, String> entry : nameToGuid.entrySet()) {
+                if (entry.getValue().equals(guid)) {
+                    nameToGuid.remove(entry.getKey());
+                    break;
+                }
+            }
+        }
+        nameToGuid.put(network, guid);
+        if (vNetsByGuid.containsKey(guid))
+            vNetsByGuid.get(guid).setName(network); //network already exists, just updating name
+        else
+            vNetsByGuid.put(guid, new VirtualNetwork(network, guid)); //new network
+        
+        // If they don't specify a new gateway the old one will be preserved
+        if ((gateway != null) && (gateway != 0)) {
+            addGateway(guid, gateway);
+            if(vNetsByGuid.get(guid)!=null)
+                vNetsByGuid.get(guid).setGateway(IPv4.fromIPv4Address(gateway));
+        }
+    }
+
+    @Override
+    public void deleteNetwork(String guid) {
+        String name = null;
+        if (nameToGuid.isEmpty()) {
+            log.warn("Could not delete network with ID {}, network doesn't exist",
+                     guid);
+            return;
+        }
+        for (Entry<String, String> entry : nameToGuid.entrySet()) {
+            if (entry.getValue().equals(guid)) {
+                name = entry.getKey();
+                break;
+            }
+            log.warn("Could not delete network with ID {}, network doesn't exist",
+                     guid);
+        }
+        
+        if (log.isDebugEnabled()) 
+            log.debug("Deleting network with name {} ID {}", name, guid);
+        
+        nameToGuid.remove(name);
+        deleteGateway(guid);
+        if(vNetsByGuid.get(guid)!=null){
+            vNetsByGuid.get(guid).clearHosts();
+            vNetsByGuid.remove(guid);
+        }
+        Collection<MACAddress> deleteList = new ArrayList<MACAddress>();
+        for (MACAddress host : macToGuid.keySet()) {
+            if (macToGuid.get(host).equals(guid)) {
+                deleteList.add(host);
+            }
+        }
+        for (MACAddress mac : deleteList) {
+            if (log.isDebugEnabled()) {
+                log.debug("Removing host {} from network {}", 
+                          HexString.toHexString(mac.toBytes()), guid);
+            }
+            macToGuid.remove(mac);
+            for (Entry<String, MACAddress> entry : portToMac.entrySet()) {
+                if (entry.getValue().equals(mac)) {
+                    portToMac.remove(entry.getKey());
+                    break;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void addHost(MACAddress mac, String guid, String port) {
+        if (guid != null) {
+            if (log.isDebugEnabled()) {
+                log.debug("Adding {} to network ID {} on port {}",
+                          new Object[] {mac, guid, port});
+            }
+            // We ignore old mappings
+            macToGuid.put(mac, guid);
+            portToMac.put(port, mac);
+            if(vNetsByGuid.get(guid)!=null)
+                vNetsByGuid.get(guid).addHost(new MACAddress(mac.toBytes()));
+        } else {
+            log.warn("Could not add MAC {} to network ID {} on port {}, the network does not exist",
+                     new Object[] {mac, guid, port});
+        }
+    }
+
+    @Override
+    public void deleteHost(MACAddress mac, String port) {
+        if (log.isDebugEnabled()) {
+            log.debug("Removing host {} from port {}", mac, port);
+        }
+        if (mac == null && port == null) return;
+        if (port != null) {
+            MACAddress host = portToMac.remove(port);
+            if(vNetsByGuid.get(macToGuid.get(host)) != null)
+                vNetsByGuid.get(macToGuid.get(host)).removeHost(host);
+            macToGuid.remove(host);
+        } else if (mac != null) {
+            if (!portToMac.isEmpty()) {
+                for (Entry<String, MACAddress> entry : portToMac.entrySet()) {
+                    if (entry.getValue().equals(mac)) {
+                        if(vNetsByGuid.get(macToGuid.get(entry.getValue())) != null)
+                            vNetsByGuid.get(macToGuid.get(entry.getValue())).removeHost(entry.getValue());
+                        portToMac.remove(entry.getKey());
+                        macToGuid.remove(entry.getValue());
+                        return;
+                    }
+                }
+            }
+        }
+    }
+    
+    // IFloodlightModule
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IVirtualNetworkService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                    IFloodlightService>();
+        m.put(IVirtualNetworkService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        l.add(IRestApiService.class);
+        l.add(IDeviceService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)  
+                                 throws FloodlightModuleException {
+        floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
+        restApi = context.getServiceImpl(IRestApiService.class);
+        deviceService = context.getServiceImpl(IDeviceService.class);
+        
+        vNetsByGuid = new ConcurrentHashMap<String, VirtualNetwork>();
+        nameToGuid = new ConcurrentHashMap<String, String>();
+        guidToGateway = new ConcurrentHashMap<String, Integer>();
+        gatewayToGuid = new ConcurrentHashMap<Integer, Set<String>>();
+        macToGuid = new ConcurrentHashMap<MACAddress, String>();
+        portToMac = new ConcurrentHashMap<String, MACAddress>();
+        macToGateway = new ConcurrentHashMap<MACAddress, Integer>();
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+        restApi.addRestletRoutable(new VirtualNetworkWebRoutable());
+        deviceService.addListener(this);
+    }
+
+    // IOFMessageListener
+    
+    @Override
+    public String getName() {
+        return "virtualizer";
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        // Link discovery should go before us so we don't block LLDPs
+        return (type.equals(OFType.PACKET_IN) && 
+        		(name.equals("linkdiscovery") || (name.equals("devicemanager"))));
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        // We need to go before forwarding
+        return (type.equals(OFType.PACKET_IN) && name.equals("forwarding"));
+    }
+
+    @Override
+    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+        switch (msg.getType()) {
+            case PACKET_IN:
+                return processPacketIn(sw, (OFPacketIn)msg, cntx);
+            default:
+            	break;
+        }
+        log.warn("Received unexpected message {}", msg);
+        return Command.CONTINUE;
+    }
+    
+    /**
+     * Checks whether the frame is destined to or from a gateway.
+     * @param frame The ethernet frame to check.
+     * @return True if it is to/from a gateway, false otherwise.
+     */
+    protected boolean isDefaultGateway(Ethernet frame) {
+    	if (macToGateway.containsKey(frame.getSourceMAC()))
+    		return true;
+    	
+    	Integer gwIp = macToGateway.get(frame.getDestinationMAC());
+    	if (gwIp != null) {
+    		MACAddress host = frame.getSourceMAC();
+    		String srcNet = macToGuid.get(host);
+    		if (srcNet != null) {
+	    		Integer gwIpSrcNet = guidToGateway.get(srcNet);
+	    		if ((gwIpSrcNet != null) && (gwIp.equals(gwIpSrcNet)))
+	    			return true;
+    		}
+    	}
+
+    	return false;
+    }
+    
+    /**
+     * Checks to see if two MAC Addresses are on the same network.
+     * @param m1 The first MAC.
+     * @param m2 The second MAC.
+     * @return True if they are on the same virtual network,
+     * 		   false otherwise.
+     */
+    protected boolean oneSameNetwork(MACAddress m1, MACAddress m2) {
+        String net1 = macToGuid.get(m1);
+        String net2 = macToGuid.get(m2);
+        if (net1 == null) return false;
+        if (net2 == null) return false;
+        return net1.equals(net2);
+    }
+    
+    /**
+     * Checks to see if an Ethernet frame is a DHCP packet.
+     * @param frame The Ethernet frame.
+     * @return True if it is a DHCP frame, false otherwise.
+     */
+    protected boolean isDhcpPacket(Ethernet frame) {
+        IPacket payload = frame.getPayload(); // IP
+        if (payload == null) return false;
+        IPacket p2 = payload.getPayload(); // TCP or UDP
+        if (p2 == null) return false;
+        IPacket p3 = p2.getPayload(); // Application
+        if ((p3 != null) && (p3 instanceof DHCP)) return true;
+        return false;
+    }
+    
+    /**
+     * Processes an OFPacketIn message and decides if the OFPacketIn should be dropped
+     * or the processing should continue.
+     * @param sw The switch the PacketIn came from.
+     * @param msg The OFPacketIn message from the switch.
+     * @param cntx The FloodlightContext for this message.
+     * @return Command.CONTINUE if processing should be continued, Command.STOP otherwise.
+     */
+    protected Command processPacketIn(IOFSwitch sw, OFPacketIn msg, FloodlightContext cntx) {
+        Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, 
+                                              IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+        Command ret = Command.STOP;
+        String srcNetwork = macToGuid.get(eth.getSourceMAC());
+        // If the host is on an unknown network we deny it.
+        // We make exceptions for ARP and DHCP.
+        if (eth.isBroadcast() || eth.isMulticast() || isDefaultGateway(eth) || isDhcpPacket(eth)) {
+        	ret = Command.CONTINUE;
+        } else if (srcNetwork == null) {
+            log.trace("Blocking traffic from host {} because it is not attached to any network.",
+                      HexString.toHexString(eth.getSourceMACAddress()));
+            ret = Command.STOP;
+        } else if (oneSameNetwork(eth.getSourceMAC(), eth.getDestinationMAC())) {
+            // if they are on the same network continue
+            ret = Command.CONTINUE;
+        }
+        
+        if (log.isTraceEnabled())
+        	log.trace("Results for flow between {} and {} is {}",
+        			new Object[] {eth.getSourceMAC(), eth.getDestinationMAC(), ret});
+        /*
+         * TODO - figure out how to still detect gateways while using
+         * drop mods 
+        if (ret == Command.STOP) {
+            if (!(eth.getPayload() instanceof ARP))
+            	doDropFlow(sw, msg, cntx);
+        }
+        */
+        return ret;
+    }
+    
+    /**
+     * Writes a FlowMod to a switch that inserts a drop flow.
+     * @param sw The switch to write the FlowMod to.
+     * @param pi The corresponding OFPacketIn. Used to create the OFMatch structure.
+     * @param cntx The FloodlightContext that gets passed to the switch.
+     */
+    protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
+        if (log.isTraceEnabled()) {
+            log.trace("doDropFlow pi={} srcSwitch={}",
+                    new Object[] { pi, sw });
+        }
+
+        if (sw == null) {
+            log.warn("Switch is null, not installing drop flowmod for PacketIn {}", pi);
+            return;
+        }
+
+        // Create flow-mod based on packet-in and src-switch
+        OFFlowMod fm = 
+            (OFFlowMod) floodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD);
+        OFMatch match = new OFMatch();
+        match.loadFromPacket(pi.getPacketData(), pi.getInPort());
+        List<OFAction> actions = new ArrayList<OFAction>(); // no actions = drop
+        long cookie = AppCookie.makeCookie(APP_ID, 0);
+        fm.setCookie(cookie)
+        .setIdleTimeout(ForwardingBase.FLOWMOD_DEFAULT_IDLE_TIMEOUT)
+        .setHardTimeout(ForwardingBase.FLOWMOD_DEFAULT_HARD_TIMEOUT)
+        .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+        .setMatch(match)
+        .setActions(actions)
+        .setLengthU(OFFlowMod.MINIMUM_LENGTH);
+        fm.setFlags(OFFlowMod.OFPFF_SEND_FLOW_REM);
+        try {
+            if (log.isTraceEnabled()) {
+                log.trace("write drop flow-mod srcSwitch={} match={} " + 
+                          "pi={} flow-mod={}",
+                          new Object[] {sw, match, pi, fm});
+            }
+            sw.write(fm, cntx);
+        } catch (IOException e) {
+            log.error("Failure writing drop flow mod", e);
+        }
+        return;
+    }
+
+    // IDeviceListener
+    
+	@Override
+	public void deviceAdded(IDevice device) {
+		if (device.getIPv4Addresses() == null) return;
+		for (Integer i : device.getIPv4Addresses()) {
+			if (gatewayToGuid.containsKey(i)) {
+				MACAddress mac = MACAddress.valueOf(device.getMACAddress());
+				if (log.isDebugEnabled())
+					log.debug("Adding MAC {} with IP {} a a gateway",
+							HexString.toHexString(mac.toBytes()),
+							IPv4.fromIPv4Address(i));
+				macToGateway.put(mac, i);
+			}
+		}
+	}
+
+	@Override
+	public void deviceRemoved(IDevice device) {
+		// if device is a gateway remove
+		MACAddress mac = MACAddress.valueOf(device.getMACAddress());
+		if (macToGateway.containsKey(mac)) {
+			if (log.isDebugEnabled())
+				log.debug("Removing MAC {} as a gateway",
+						HexString.toHexString(mac.toBytes()));
+			macToGateway.remove(mac);
+		}
+	}
+
+	@Override
+	public void deviceIPV4AddrChanged(IDevice device) {
+		// add or remove entry as gateway
+		deviceAdded(device);
+	}
+
+	@Override
+	public void deviceMoved(IDevice device) {
+		// ignore
+	}
+	
+	@Override
+	public void deviceVlanChanged(IDevice device) {
+		// ignore
+	}
+
+    @Override
+    public Collection <VirtualNetwork> listNetworks() {
+        return vNetsByGuid.values();
+        
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java
new file mode 100644
index 0000000..6902f6c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java
@@ -0,0 +1,38 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import net.floodlightcontroller.util.MACAddress;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+/**
+ * Serialize a VirtualNetwork object
+ * @author KC Wang
+ */
+public class VirtualNetworkSerializer extends JsonSerializer<VirtualNetwork> {
+
+    @Override
+    public void serialize(VirtualNetwork vNet, JsonGenerator jGen,
+            SerializerProvider serializer) throws IOException,
+            JsonProcessingException {
+        jGen.writeStartObject();
+        
+        jGen.writeStringField("name", vNet.name);
+        jGen.writeStringField("guid", vNet.guid);
+        jGen.writeStringField("gateway", vNet.gateway);
+
+        jGen.writeArrayFieldStart("mac");
+        Iterator<MACAddress> hit = vNet.hosts.iterator();
+        while (hit.hasNext())
+            jGen.writeString(hit.next().toString());
+        jGen.writeEndArray();
+        
+        jGen.writeEndObject();
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkWebRoutable.java
new file mode 100644
index 0000000..61769ec
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkWebRoutable.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class VirtualNetworkWebRoutable implements RestletRoutable {
+
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/tenants/{tenant}/networks", NetworkResource.class); // GET
+        router.attach("/tenants/{tenant}/networks/{network}", NetworkResource.class); // PUT, DELETE
+        router.attach("/tenants/{tenant}/networks", NetworkResource.class); // POST
+        router.attach("/tenants/{tenant}/networks/{network}/ports/{port}/attachment", HostResource.class);
+        router.attachDefault(NoOp.class);
+        return router;
+    }
+
+    @Override
+    public String basePath() {
+        return "/quantum/v1.0";
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/Instantiable.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/Instantiable.java
new file mode 100644
index 0000000..1358ba7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/Instantiable.java
@@ -0,0 +1,31 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface Instantiable<E> {
+
+    /**
+     * Create a new instance of a given subclass.
+     * @return the new instance.
+     */
+    public E instantiate();
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierReply.java
new file mode 100644
index 0000000..a79a15f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierReply.java
@@ -0,0 +1,32 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.openflow.util.U16;
+
+/**
+ * Represents an OFPT_BARRIER_REPLY message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFBarrierReply extends OFMessage {
+    public OFBarrierReply() {
+        super();
+        this.type = OFType.BARRIER_REPLY;
+        this.length = U16.t(OFMessage.MINIMUM_LENGTH);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierRequest.java
new file mode 100644
index 0000000..9992186
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierRequest.java
@@ -0,0 +1,32 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.openflow.util.U16;
+
+/**
+ * Represents an OFPT_BARRIER_REQUEST message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFBarrierRequest extends OFMessage {
+    public OFBarrierRequest() {
+        super();
+        this.type = OFType.BARRIER_REQUEST;
+        this.length = U16.t(OFMessage.MINIMUM_LENGTH);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoReply.java
new file mode 100644
index 0000000..3e282a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoReply.java
@@ -0,0 +1,36 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_echo_reply message
+ * 
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ */
+
+public class OFEchoReply extends OFEchoRequest {
+    public static int MINIMUM_LENGTH = 8;
+
+    public OFEchoReply() {
+        super();
+        this.type = OFType.ECHO_REPLY;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoRequest.java
new file mode 100644
index 0000000..295a397
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoRequest.java
@@ -0,0 +1,101 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+
+import java.util.Arrays;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_echo_request message
+ * 
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ */
+
+public class OFEchoRequest extends OFMessage {
+    public static int MINIMUM_LENGTH = 8;
+    byte[] payload;
+
+    public OFEchoRequest() {
+        super();
+        this.type = OFType.ECHO_REQUEST;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer bb) {
+        super.readFrom(bb);
+        int datalen = this.getLengthU() - MINIMUM_LENGTH;
+        if (datalen > 0) {
+            this.payload = new byte[datalen];
+            bb.readBytes(payload);
+        }
+    }
+
+    /**
+     * @return the payload
+     */
+    public byte[] getPayload() {
+        return payload;
+    }
+
+    /**
+     * @param payload
+     *            the payload to set
+     */
+    public void setPayload(byte[] payload) {
+        this.payload = payload;
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer bb) {
+        super.writeTo(bb);
+        if (payload != null)
+            bb.writeBytes(payload);
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = super.hashCode();
+        result = prime * result + Arrays.hashCode(payload);
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        OFEchoRequest other = (OFEchoRequest) obj;
+        if (!Arrays.equals(payload, other.payload))
+            return false;
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFError.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFError.java
new file mode 100644
index 0000000..515a79c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFError.java
@@ -0,0 +1,319 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.factory.MessageParseException;
+import org.openflow.protocol.factory.OFMessageFactory;
+import org.openflow.protocol.factory.OFMessageFactoryAware;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_error_msg
+ * 
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ */
+public class OFError extends OFMessage implements OFMessageFactoryAware {
+    public static int MINIMUM_LENGTH = 12;
+
+    public enum OFErrorType {
+        // OFPET_VENDOR_ERROR is an extension that was added in Open vSwitch and isn't
+        // in the OF 1.0 spec, but it was easier to add it here instead of adding
+        // generic support for extensible vendor-defined error messages. 
+        // It uses the random value 0xb0c2 to avoid conflicts with other possible new
+        // error types. Support for vendor-defined extended errors has been standardized
+        // in the OF 1.2 spec, so this workaround is only needed for 1.0.
+        OFPET_HELLO_FAILED, OFPET_BAD_REQUEST, OFPET_BAD_ACTION, OFPET_FLOW_MOD_FAILED, OFPET_PORT_MOD_FAILED, OFPET_QUEUE_OP_FAILED, OFPET_VENDOR_ERROR((short)0xb0c2);
+        
+        protected short value;
+        
+        private OFErrorType() {
+            this.value = (short) this.ordinal();
+        }
+        
+        private OFErrorType(short value) {
+            this.value = value;
+        }
+        
+        public short getValue() {
+            return value;
+        }
+    }
+
+    public enum OFHelloFailedCode {
+        OFPHFC_INCOMPATIBLE, OFPHFC_EPERM
+    }
+
+    public enum OFBadRequestCode {
+        OFPBRC_BAD_VERSION, OFPBRC_BAD_TYPE, OFPBRC_BAD_STAT, OFPBRC_BAD_VENDOR, OFPBRC_BAD_SUBTYPE, OFPBRC_EPERM, OFPBRC_BAD_LEN, OFPBRC_BUFFER_EMPTY, OFPBRC_BUFFER_UNKNOWN
+    }
+
+    public enum OFBadActionCode {
+        OFPBAC_BAD_TYPE, OFPBAC_BAD_LEN, OFPBAC_BAD_VENDOR, OFPBAC_BAD_VENDOR_TYPE, OFPBAC_BAD_OUT_PORT, OFPBAC_BAD_ARGUMENT, OFPBAC_EPERM, OFPBAC_TOO_MANY, OFPBAC_BAD_QUEUE
+    }
+
+    public enum OFFlowModFailedCode {
+        OFPFMFC_ALL_TABLES_FULL, OFPFMFC_OVERLAP, OFPFMFC_EPERM, OFPFMFC_BAD_EMERG_TIMEOUT, OFPFMFC_BAD_COMMAND, OFPFMFC_UNSUPPORTED
+    }
+
+    public enum OFPortModFailedCode {
+        OFPPMFC_BAD_PORT, OFPPMFC_BAD_HW_ADDR
+    }
+
+    public enum OFQueueOpFailedCode {
+        OFPQOFC_BAD_PORT, OFPQOFC_BAD_QUEUE, OFPQOFC_EPERM
+    }
+
+    protected short errorType;
+    protected short errorCode;
+    protected int vendor;
+    protected int vendorErrorType;
+    protected short vendorErrorCode;
+    protected OFMessageFactory factory;
+    protected byte[] error;
+    protected boolean errorIsAscii;
+
+    public OFError() {
+        super();
+        this.type = OFType.ERROR;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    /**
+     * @return the errorType
+     */
+    public short getErrorType() {
+        return errorType;
+    }
+
+    /**
+     * @param errorType
+     *            the errorType to set
+     */
+    public void setErrorType(short errorType) {
+        this.errorType = errorType;
+    }
+
+    public void setErrorType(OFErrorType type) {
+        this.errorType = type.getValue();
+    }
+
+    /**
+     * @return true if the error is an extended vendor error
+     */
+    public boolean isVendorError() {
+        return errorType == OFErrorType.OFPET_VENDOR_ERROR.getValue();
+    }
+    
+    /**
+     * @return the errorCode
+     */
+    public short getErrorCode() {
+        return errorCode;
+    }
+
+    /**
+     * @param errorCode
+     *            the errorCode to set
+     */
+    public void setErrorCode(OFHelloFailedCode code) {
+        this.errorCode = (short) code.ordinal();
+    }
+
+    public void setErrorCode(short errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    public void setErrorCode(OFBadRequestCode code) {
+        this.errorCode = (short) code.ordinal();
+    }
+
+    public void setErrorCode(OFBadActionCode code) {
+        this.errorCode = (short) code.ordinal();
+    }
+
+    public void setErrorCode(OFFlowModFailedCode code) {
+        this.errorCode = (short) code.ordinal();
+    }
+
+    public void setErrorCode(OFPortModFailedCode code) {
+        this.errorCode = (short) code.ordinal();
+    }
+
+    public void setErrorCode(OFQueueOpFailedCode code) {
+        this.errorCode = (short) code.ordinal();
+    }
+
+    public int getVendorErrorType() {
+        return vendorErrorType;
+    }
+    
+    public void setVendorErrorType(int vendorErrorType) {
+        this.vendorErrorType = vendorErrorType;
+    }
+    
+    public short getVendorErrorCode() {
+        return vendorErrorCode;
+    }
+    
+    public void setVendorErrorCode(short vendorErrorCode) {
+        this.vendorErrorCode = vendorErrorCode;
+    }
+    
+    public OFMessage getOffendingMsg() throws MessageParseException {
+        // should only have one message embedded; if more than one, just
+        // grab first
+        if (this.error == null)
+            return null;
+        ChannelBuffer errorMsg = ChannelBuffers.wrappedBuffer(this.error);
+        if (factory == null)
+            throw new RuntimeException("MessageFactory not set");
+
+        List<OFMessage> msglist = this.factory.parseMessage(errorMsg);
+        if (msglist == null)
+                return null;
+        return msglist.get(0);
+    }
+
+    /**
+     * Write this offending message into the payload of the Error message
+     * 
+     * @param offendingMsg
+     */
+
+    public void setOffendingMsg(OFMessage offendingMsg) {
+        if (offendingMsg == null) {
+            super.setLengthU(MINIMUM_LENGTH);
+        } else {
+            this.error = new byte[offendingMsg.getLengthU()];
+            ChannelBuffer data = ChannelBuffers.wrappedBuffer(this.error);
+            data.writerIndex(0);
+            offendingMsg.writeTo(data);
+            super.setLengthU(MINIMUM_LENGTH + offendingMsg.getLengthU());
+        }
+    }
+
+    public OFMessageFactory getFactory() {
+        return factory;
+    }
+
+    @Override
+    public void setMessageFactory(OFMessageFactory factory) {
+        this.factory = factory;
+    }
+
+    /**
+     * @return the error
+     */
+    public byte[] getError() {
+        return error;
+    }
+
+    /**
+     * @param error
+     *            the error to set
+     */
+    public void setError(byte[] error) {
+        this.error = error;
+    }
+
+    /**
+     * @return the errorIsAscii
+     */
+    public boolean isErrorIsAscii() {
+        return errorIsAscii;
+    }
+
+    /**
+     * @param errorIsAscii
+     *            the errorIsAscii to set
+     */
+    public void setErrorIsAscii(boolean errorIsAscii) {
+        this.errorIsAscii = errorIsAscii;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.errorType = data.readShort();
+        this.errorCode = data.readShort();
+        int dataLength = this.getLengthU() - MINIMUM_LENGTH;
+        if (dataLength > 0) {
+            this.error = new byte[dataLength];
+            data.readBytes(this.error);
+            if (this.errorType == OFErrorType.OFPET_HELLO_FAILED.getValue())
+                this.errorIsAscii = true;
+        }
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeShort(errorType);
+        data.writeShort(errorCode);
+        if (error != null)
+            data.writeBytes(error);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = super.hashCode();
+        result = prime * result + Arrays.hashCode(error);
+        result = prime * result + errorCode;
+        result = prime * result + (errorIsAscii ? 1231 : 1237);
+        result = prime * result + errorType;
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        OFError other = (OFError) obj;
+        if (!Arrays.equals(error, other.error))
+            return false;
+        if (errorCode != other.errorCode)
+            return false;
+        if (errorIsAscii != other.errorIsAscii)
+            return false;
+        if (errorType != other.errorType)
+            return false;
+        return true;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesReply.java
new file mode 100644
index 0000000..d3af574
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesReply.java
@@ -0,0 +1,258 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.serializers.OFFeaturesReplyJSONSerializer;
+import org.openflow.util.U16;
+
+
+/**
+ * Represents a features reply message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+@JsonSerialize(using=OFFeaturesReplyJSONSerializer.class)
+public class OFFeaturesReply extends OFMessage {
+    public static int MINIMUM_LENGTH = 32;
+
+    /**
+     * Corresponds to bits on the capabilities field
+     */
+    public enum OFCapabilities {
+        OFPC_FLOW_STATS     (1 << 0),
+        OFPC_TABLE_STATS    (1 << 1),
+        OFPC_PORT_STATS     (1 << 2),
+        OFPC_STP            (1 << 3),
+        OFPC_RESERVED       (1 << 4),
+        OFPC_IP_REASM       (1 << 5),
+        OFPC_QUEUE_STATS    (1 << 6),
+        OFPC_ARP_MATCH_IP   (1 << 7);
+
+        protected int value;
+
+        private OFCapabilities(int value) {
+            this.value = value;
+        }
+
+        /**
+         * @return the value
+         */
+        public int getValue() {
+            return value;
+        }
+    }
+
+    protected long datapathId;
+    protected int buffers;
+    protected byte tables;
+    protected int capabilities;
+    protected int actions;
+    protected List<OFPhysicalPort> ports;
+
+    public OFFeaturesReply() {
+        super();
+        this.type = OFType.FEATURES_REPLY;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    /**
+     * @return the datapathId
+     */
+    public long getDatapathId() {
+        return datapathId;
+    }
+
+    /**
+     * @param datapathId the datapathId to set
+     */
+    public void setDatapathId(long datapathId) {
+        this.datapathId = datapathId;
+    }
+
+    /**
+     * @return the buffers
+     */
+    public int getBuffers() {
+        return buffers;
+    }
+
+    /**
+     * @param buffers the buffers to set
+     */
+    public void setBuffers(int buffers) {
+        this.buffers = buffers;
+    }
+
+    /**
+     * @return the tables
+     */
+    public byte getTables() {
+        return tables;
+    }
+
+    /**
+     * @param tables the tables to set
+     */
+    public void setTables(byte tables) {
+        this.tables = tables;
+    }
+
+    /**
+     * @return the capabilities
+     */
+    public int getCapabilities() {
+        return capabilities;
+    }
+
+    /**
+     * @param capabilities the capabilities to set
+     */
+    public void setCapabilities(int capabilities) {
+        this.capabilities = capabilities;
+    }
+
+    /**
+     * @return the actions
+     */
+    public int getActions() {
+        return actions;
+    }
+
+    /**
+     * @param actions the actions to set
+     */
+    public void setActions(int actions) {
+        this.actions = actions;
+    }
+
+    /**
+     * @return the ports
+     */
+    public List<OFPhysicalPort> getPorts() {
+        return ports;
+    }
+
+    /**
+     * @param ports the ports to set
+     */
+    public void setPorts(List<OFPhysicalPort> ports) {
+        this.ports = ports;
+        if (ports == null) {
+            this.setLengthU(MINIMUM_LENGTH);
+        } else {
+            this.setLengthU(MINIMUM_LENGTH + ports.size()
+                    * OFPhysicalPort.MINIMUM_LENGTH);
+        }
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.datapathId = data.readLong();
+        this.buffers = data.readInt();
+        this.tables = data.readByte();
+        data.readerIndex(data.readerIndex() + 3); // pad
+        this.capabilities = data.readInt();
+        this.actions = data.readInt();
+        if (this.ports == null) {
+            this.ports = new ArrayList<OFPhysicalPort>();
+        } else {
+            this.ports.clear();
+        }
+        int portCount = (super.getLengthU() - 32)
+                / OFPhysicalPort.MINIMUM_LENGTH;
+        OFPhysicalPort port;
+        for (int i = 0; i < portCount; ++i) {
+            port = new OFPhysicalPort();
+            port.readFrom(data);
+            this.ports.add(port);
+        }
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeLong(this.datapathId);
+        data.writeInt(this.buffers);
+        data.writeByte(this.tables);
+        data.writeShort((short) 0); // pad
+        data.writeByte((byte) 0); // pad
+        data.writeInt(this.capabilities);
+        data.writeInt(this.actions);
+        if (this.ports != null)
+            for (OFPhysicalPort port : this.ports) {
+                port.writeTo(data);
+            }
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 139;
+        int result = super.hashCode();
+        result = prime * result + actions;
+        result = prime * result + buffers;
+        result = prime * result + capabilities;
+        result = prime * result + (int) (datapathId ^ (datapathId >>> 32));
+        result = prime * result + ((ports == null) ? 0 : ports.hashCode());
+        result = prime * result + tables;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFFeaturesReply)) {
+            return false;
+        }
+        OFFeaturesReply other = (OFFeaturesReply) obj;
+        if (actions != other.actions) {
+            return false;
+        }
+        if (buffers != other.buffers) {
+            return false;
+        }
+        if (capabilities != other.capabilities) {
+            return false;
+        }
+        if (datapathId != other.datapathId) {
+            return false;
+        }
+        if (ports == null) {
+            if (other.ports != null) {
+                return false;
+            }
+        } else if (!ports.equals(other.ports)) {
+            return false;
+        }
+        if (tables != other.tables) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesRequest.java
new file mode 100644
index 0000000..0a89e4f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesRequest.java
@@ -0,0 +1,36 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.openflow.util.U16;
+
+
+/**
+ * Represents a features request message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class OFFeaturesRequest extends OFMessage {
+    public static int MINIMUM_LENGTH = 8;
+
+    public OFFeaturesRequest() {
+        super();
+        this.type = OFType.FEATURES_REQUEST;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowMod.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowMod.java
new file mode 100644
index 0000000..51b5de3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowMod.java
@@ -0,0 +1,388 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.factory.OFActionFactory;
+import org.openflow.protocol.factory.OFActionFactoryAware;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_flow_mod message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class OFFlowMod extends OFMessage implements OFActionFactoryAware, Cloneable {
+    public static int MINIMUM_LENGTH = 72;
+
+    public static final short OFPFC_ADD = 0;                /* New flow. */
+    public static final short OFPFC_MODIFY = 1;             /* Modify all matching flows. */
+    public static final short OFPFC_MODIFY_STRICT = 2;      /* Modify entry strictly matching wildcards */
+    public static final short OFPFC_DELETE=3;               /* Delete all matching flows. */
+    public static final short OFPFC_DELETE_STRICT =4;       /* Strictly match wildcards and priority. */
+
+    // Open Flow Flow Mod Flags. Use "or" operation to set multiple flags
+    public static final short OFPFF_SEND_FLOW_REM = 0x1; // 1 << 0
+    public static final short OFPFF_CHECK_OVERLAP = 0x2; // 1 << 1
+    public static final short OFPFF_EMERG         = 0x4; // 1 << 2
+
+    protected OFActionFactory actionFactory;
+    protected OFMatch match;
+    protected long cookie;
+    protected short command;
+    protected short idleTimeout;
+    protected short hardTimeout;
+    protected short priority;
+    protected int bufferId;
+    protected short outPort;
+    protected short flags;
+    protected List<OFAction> actions;
+
+    public OFFlowMod() {
+        super();
+        this.type = OFType.FLOW_MOD;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    /**
+     * Get buffer_id
+     * @return
+     */
+    public int getBufferId() {
+        return this.bufferId;
+    }
+
+    /**
+     * Set buffer_id
+     * @param bufferId
+     */
+    public OFFlowMod setBufferId(int bufferId) {
+        this.bufferId = bufferId;
+        return this;
+    }
+
+    /**
+     * Get cookie
+     * @return
+     */
+    public long getCookie() {
+        return this.cookie;
+    }
+
+    /**
+     * Set cookie
+     * @param cookie
+     */
+    public OFFlowMod setCookie(long cookie) {
+        this.cookie = cookie;
+        return this;
+    }
+
+    /**
+     * Get command
+     * @return
+     */
+    public short getCommand() {
+        return this.command;
+    }
+
+    /**
+     * Set command
+     * @param command
+     */
+    public OFFlowMod setCommand(short command) {
+        this.command = command;
+        return this;
+    }
+
+    /**
+     * Get flags
+     * @return
+     */
+    public short getFlags() {
+        return this.flags;
+    }
+
+    /**
+     * Set flags
+     * @param flags
+     */
+    public OFFlowMod setFlags(short flags) {
+        this.flags = flags;
+        return this;
+    }
+
+    /**
+     * Get hard_timeout
+     * @return
+     */
+    public short getHardTimeout() {
+        return this.hardTimeout;
+    }
+
+    /**
+     * Set hard_timeout
+     * @param hardTimeout
+     */
+    public OFFlowMod setHardTimeout(short hardTimeout) {
+        this.hardTimeout = hardTimeout;
+        return this;
+    }
+
+    /**
+     * Get idle_timeout
+     * @return
+     */
+    public short getIdleTimeout() {
+        return this.idleTimeout;
+    }
+
+    /**
+     * Set idle_timeout
+     * @param idleTimeout
+     */
+    public OFFlowMod setIdleTimeout(short idleTimeout) {
+        this.idleTimeout = idleTimeout;
+        return this;
+    }
+
+    /**
+     * Gets a copy of the OFMatch object for this FlowMod, changes to this
+     * object do not modify the FlowMod
+     * @return
+     */
+    public OFMatch getMatch() {
+        return this.match;
+    }
+
+    /**
+     * Set match
+     * @param match
+     */
+    public OFFlowMod setMatch(OFMatch match) {
+        this.match = match;
+        return this;
+    }
+
+    /**
+     * Get out_port
+     * @return
+     */
+    public short getOutPort() {
+        return this.outPort;
+    }
+
+    /**
+     * Set out_port
+     * @param outPort
+     */
+    public OFFlowMod setOutPort(short outPort) {
+        this.outPort = outPort;
+        return this;
+    }
+
+    /**
+     * Set out_port
+     * @param port
+     */
+    public OFFlowMod setOutPort(OFPort port) {
+        this.outPort = port.getValue();
+        return this;
+    }
+
+    /**
+     * Get priority
+     * @return
+     */
+    public short getPriority() {
+        return this.priority;
+    }
+
+    /**
+     * Set priority
+     * @param priority
+     */
+    public OFFlowMod setPriority(short priority) {
+        this.priority = priority;
+        return this;
+    }
+
+    /**
+     * Returns read-only copies of the actions contained in this Flow Mod
+     * @return a list of ordered OFAction objects
+     */
+    public List<OFAction> getActions() {
+        return this.actions;
+    }
+
+    /**
+     * Sets the list of actions this Flow Mod contains
+     * @param actions a list of ordered OFAction objects
+     */
+    public OFFlowMod setActions(List<OFAction> actions) {
+        this.actions = actions;
+        return this;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        if (this.match == null)
+            this.match = new OFMatch();
+        this.match.readFrom(data);
+        this.cookie = data.readLong();
+        this.command = data.readShort();
+        this.idleTimeout = data.readShort();
+        this.hardTimeout = data.readShort();
+        this.priority = data.readShort();
+        this.bufferId = data.readInt();
+        this.outPort = data.readShort();
+        this.flags = data.readShort();
+        if (this.actionFactory == null)
+            throw new RuntimeException("OFActionFactory not set");
+        this.actions = this.actionFactory.parseActions(data, getLengthU() -
+                MINIMUM_LENGTH);
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        this.match.writeTo(data);
+        data.writeLong(cookie);
+        data.writeShort(command);
+        data.writeShort(idleTimeout);
+        data.writeShort(hardTimeout);
+        data.writeShort(priority);
+        data.writeInt(bufferId);
+        data.writeShort(outPort);
+        data.writeShort(flags);
+        if (actions != null) {
+            for (OFAction action : actions) {
+                action.writeTo(data);
+            }
+        }
+    }
+
+    @Override
+    public void setActionFactory(OFActionFactory actionFactory) {
+        this.actionFactory = actionFactory;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 227;
+        int result = super.hashCode();
+        result = prime * result + ((actions == null) ? 0 : actions.hashCode());
+        result = prime * result + bufferId;
+        result = prime * result + command;
+        result = prime * result + (int) (cookie ^ (cookie >>> 32));
+        result = prime * result + flags;
+        result = prime * result + hardTimeout;
+        result = prime * result + idleTimeout;
+        result = prime * result + ((match == null) ? 0 : match.hashCode());
+        result = prime * result + outPort;
+        result = prime * result + priority;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFFlowMod)) {
+            return false;
+        }
+        OFFlowMod other = (OFFlowMod) obj;
+        if (actions == null) {
+            if (other.actions != null) {
+                return false;
+            }
+        } else if (!actions.equals(other.actions)) {
+            return false;
+        }
+        if (bufferId != other.bufferId) {
+            return false;
+        }
+        if (command != other.command) {
+            return false;
+        }
+        if (cookie != other.cookie) {
+            return false;
+        }
+        if (flags != other.flags) {
+            return false;
+        }
+        if (hardTimeout != other.hardTimeout) {
+            return false;
+        }
+        if (idleTimeout != other.idleTimeout) {
+            return false;
+        }
+        if (match == null) {
+            if (other.match != null) {
+                return false;
+            }
+        } else if (!match.equals(other.match)) {
+            return false;
+        }
+        if (outPort != other.outPort) {
+            return false;
+        }
+        if (priority != other.priority) {
+            return false;
+        }
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public OFFlowMod clone() throws CloneNotSupportedException {
+        OFMatch neoMatch = match.clone();
+        OFFlowMod flowMod= (OFFlowMod) super.clone();
+        flowMod.setMatch(neoMatch);
+        List<OFAction> neoActions = new LinkedList<OFAction>();
+        for(OFAction action: this.actions)
+            neoActions.add((OFAction) action.clone());
+        flowMod.setActions(neoActions);
+        return flowMod;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "OFFlowMod [actionFactory=" + actionFactory + ", actions="
+                + actions + ", bufferId=" + bufferId + ", command=" + command
+                + ", cookie=" + cookie + ", flags=" + flags + ", hardTimeout="
+                + hardTimeout + ", idleTimeout=" + idleTimeout + ", match="
+                + match + ", outPort=" + outPort + ", priority=" + priority
+                + ", length=" + length + ", type=" + type + ", version="
+                + version + ", xid=" + xid + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowRemoved.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowRemoved.java
new file mode 100644
index 0000000..4f4e0c7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowRemoved.java
@@ -0,0 +1,290 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_flow_removed message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class OFFlowRemoved extends OFMessage {
+    public static int MINIMUM_LENGTH = 88;
+
+    public enum OFFlowRemovedReason {
+        OFPRR_IDLE_TIMEOUT,
+        OFPRR_HARD_TIMEOUT,
+        OFPRR_DELETE
+    }
+
+    protected OFMatch match;
+    protected long cookie;
+    protected short priority;
+    protected OFFlowRemovedReason reason;
+    protected int durationSeconds;
+    protected int durationNanoseconds;
+    protected short idleTimeout;
+    protected long packetCount;
+    protected long byteCount;
+    
+    public OFFlowRemoved() {
+        super();
+        this.type = OFType.FLOW_REMOVED;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    /**
+     * Get cookie
+     * @return
+     */
+    public long getCookie() {
+        return this.cookie;
+    }
+
+    /**
+     * Set cookie
+     * @param cookie
+     */
+    public void setCookie(long cookie) {
+        this.cookie = cookie;
+    }
+
+    /**
+     * Get idle_timeout
+     * @return
+     */
+    public short getIdleTimeout() {
+        return this.idleTimeout;
+    }
+
+    /**
+     * Set idle_timeout
+     * @param idleTimeout
+     */
+    public void setIdleTimeout(short idleTimeout) {
+        this.idleTimeout = idleTimeout;
+    }
+
+    /**
+     * Gets a copy of the OFMatch object for this FlowMod, changes to this
+     * object do not modify the FlowMod
+     * @return
+     */
+    public OFMatch getMatch() {
+        return this.match;
+    }
+
+    /**
+     * Set match
+     * @param match
+     */
+    public void setMatch(OFMatch match) {
+        this.match = match;
+    }
+
+    /**
+     * Get priority
+     * @return
+     */
+    public short getPriority() {
+        return this.priority;
+    }
+
+    /**
+     * Set priority
+     * @param priority
+     */
+    public void setPriority(short priority) {
+        this.priority = priority;
+    }
+
+    /**
+     * @return the reason
+     */
+    public OFFlowRemovedReason getReason() {
+        return reason;
+    }
+
+    /**
+     * @param reason the reason to set
+     */
+    public void setReason(OFFlowRemovedReason reason) {
+        this.reason = reason;
+    }
+
+    /**
+     * @return the durationSeconds
+     */
+    public int getDurationSeconds() {
+        return durationSeconds;
+    }
+
+    /**
+     * @param durationSeconds the durationSeconds to set
+     */
+    public void setDurationSeconds(int durationSeconds) {
+        this.durationSeconds = durationSeconds;
+    }
+
+    /**
+     * @return the durationNanoseconds
+     */
+    public int getDurationNanoseconds() {
+        return durationNanoseconds;
+    }
+
+    /**
+     * @param durationNanoseconds the durationNanoseconds to set
+     */
+    public void setDurationNanoseconds(int durationNanoseconds) {
+        this.durationNanoseconds = durationNanoseconds;
+    }
+
+    /**
+     * @return the packetCount
+     */
+    public long getPacketCount() {
+        return packetCount;
+    }
+
+    /**
+     * @param packetCount the packetCount to set
+     */
+    public void setPacketCount(long packetCount) {
+        this.packetCount = packetCount;
+    }
+
+    /**
+     * @return the byteCount
+     */
+    public long getByteCount() {
+        return byteCount;
+    }
+
+    /**
+     * @param byteCount the byteCount to set
+     */
+    public void setByteCount(long byteCount) {
+        this.byteCount = byteCount;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        if (this.match == null)
+            this.match = new OFMatch();
+        this.match.readFrom(data);
+        this.cookie = data.readLong();
+        this.priority = data.readShort();
+        this.reason = OFFlowRemovedReason.values()[(0xff & data.readByte())];
+        data.readByte(); // pad
+        this.durationSeconds = data.readInt();
+        this.durationNanoseconds = data.readInt();
+        this.idleTimeout = data.readShort();
+        data.readByte(); // pad
+        data.readByte(); // pad
+        this.packetCount = data.readLong();
+        this.byteCount = data.readLong();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        this.match.writeTo(data);
+        data.writeLong(cookie);
+        data.writeShort(priority);
+        data.writeByte((byte) this.reason.ordinal());
+        data.writeByte((byte) 0);
+        data.writeInt(this.durationSeconds);
+        data.writeInt(this.durationNanoseconds);
+        data.writeShort(idleTimeout);
+        data.writeByte((byte) 0); // pad
+        data.writeByte((byte) 0); // pad
+        data.writeLong(this.packetCount);
+        data.writeLong(this.byteCount);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 271;
+        int result = super.hashCode();
+        result = prime * result + (int) (byteCount ^ (byteCount >>> 32));
+        result = prime * result + (int) (cookie ^ (cookie >>> 32));
+        result = prime * result + durationNanoseconds;
+        result = prime * result + durationSeconds;
+        result = prime * result + idleTimeout;
+        result = prime * result + ((match == null) ? 0 : match.hashCode());
+        result = prime * result + (int) (packetCount ^ (packetCount >>> 32));
+        result = prime * result + priority;
+        result = prime * result + ((reason == null) ? 0 : reason.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFFlowRemoved)) {
+            return false;
+        }
+        OFFlowRemoved other = (OFFlowRemoved) obj;
+        if (byteCount != other.byteCount) {
+            return false;
+        }
+        if (cookie != other.cookie) {
+            return false;
+        }
+        if (durationNanoseconds != other.durationNanoseconds) {
+            return false;
+        }
+        if (durationSeconds != other.durationSeconds) {
+            return false;
+        }
+        if (idleTimeout != other.idleTimeout) {
+            return false;
+        }
+        if (match == null) {
+            if (other.match != null) {
+                return false;
+            }
+        } else if (!match.equals(other.match)) {
+            return false;
+        }
+        if (packetCount != other.packetCount) {
+            return false;
+        }
+        if (priority != other.priority) {
+            return false;
+        }
+        if (reason == null) {
+            if (other.reason != null) {
+                return false;
+            }
+        } else if (!reason.equals(other.reason)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigReply.java
new file mode 100644
index 0000000..257867a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigReply.java
@@ -0,0 +1,29 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+/**
+ * Represents an OFPT_GET_CONFIG_REPLY type message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFGetConfigReply extends OFSwitchConfig {
+    public OFGetConfigReply() {
+        super();
+        this.type = OFType.GET_CONFIG_REPLY;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigRequest.java
new file mode 100644
index 0000000..85c7499
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigRequest.java
@@ -0,0 +1,32 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.openflow.util.U16;
+
+/**
+ * Represents an OFPT_GET_CONFIG_REQUEST type message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFGetConfigRequest extends OFMessage {
+    public OFGetConfigRequest() {
+        super();
+        this.type = OFType.GET_CONFIG_REQUEST;
+        this.length = U16.t(OFMessage.MINIMUM_LENGTH);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFHello.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFHello.java
new file mode 100644
index 0000000..e702ca4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFHello.java
@@ -0,0 +1,39 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.openflow.util.U16;
+
+
+/**
+ * Represents an ofp_hello message
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Feb 8, 2010
+ */
+public class OFHello extends OFMessage {
+    public static int MINIMUM_LENGTH = 8;
+
+    /**
+     * Construct a ofp_hello message
+     */
+    public OFHello() {
+        super();
+        this.type = OFType.HELLO;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatch.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatch.java
new file mode 100644
index 0000000..0336d7d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatch.java
@@ -0,0 +1,945 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.serializers.OFMatchJSONSerializer;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.openflow.util.U8;
+
+/**
+ * Represents an ofp_match structure
+ * 
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ * 
+ */
+@JsonSerialize(using=OFMatchJSONSerializer.class)
+public class OFMatch implements Cloneable, Serializable {
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+    public static int MINIMUM_LENGTH = 40;
+    final public static int OFPFW_ALL = ((1 << 22) - 1);
+
+    final public static int OFPFW_IN_PORT = 1 << 0; /* Switch input port. */
+    final public static int OFPFW_DL_VLAN = 1 << 1; /* VLAN id. */
+    final public static int OFPFW_DL_SRC = 1 << 2; /* Ethernet source address. */
+    final public static int OFPFW_DL_DST = 1 << 3; /*
+                                                    * Ethernet destination
+                                                    * address.
+                                                    */
+    final public static int OFPFW_DL_TYPE = 1 << 4; /* Ethernet frame type. */
+    final public static int OFPFW_NW_PROTO = 1 << 5; /* IP protocol. */
+    final public static int OFPFW_TP_SRC = 1 << 6; /* TCP/UDP source port. */
+    final public static int OFPFW_TP_DST = 1 << 7; /* TCP/UDP destination port. */
+
+    /*
+     * IP source address wildcard bit count. 0 is exact match, 1 ignores the
+     * LSB, 2 ignores the 2 least-significant bits, ..., 32 and higher wildcard
+     * the entire field. This is the *opposite* of the usual convention where
+     * e.g. /24 indicates that 8 bits (not 24 bits) are wildcarded.
+     */
+    final public static int OFPFW_NW_SRC_SHIFT = 8;
+    final public static int OFPFW_NW_SRC_BITS = 6;
+    final public static int OFPFW_NW_SRC_MASK = ((1 << OFPFW_NW_SRC_BITS) - 1) << OFPFW_NW_SRC_SHIFT;
+    final public static int OFPFW_NW_SRC_ALL = 32 << OFPFW_NW_SRC_SHIFT;
+
+    /* IP destination address wildcard bit count. Same format as source. */
+    final public static int OFPFW_NW_DST_SHIFT = 14;
+    final public static int OFPFW_NW_DST_BITS = 6;
+    final public static int OFPFW_NW_DST_MASK = ((1 << OFPFW_NW_DST_BITS) - 1) << OFPFW_NW_DST_SHIFT;
+    final public static int OFPFW_NW_DST_ALL = 32 << OFPFW_NW_DST_SHIFT;
+
+    final public static int OFPFW_DL_VLAN_PCP = 1 << 20; /* VLAN priority. */
+    final public static int OFPFW_NW_TOS = 1 << 21; /*
+                                                     * IP ToS (DSCP field, 6
+                                                     * bits).
+                                                     */
+
+    /* List of Strings for marshalling and unmarshalling to human readable forms */
+    final public static String STR_IN_PORT = "in_port";
+    final public static String STR_DL_DST = "dl_dst";
+    final public static String STR_DL_SRC = "dl_src";
+    final public static String STR_DL_TYPE = "dl_type";
+    final public static String STR_DL_VLAN = "dl_vlan";
+    final public static String STR_DL_VLAN_PCP = "dl_vlan_pcp";
+    final public static String STR_NW_DST = "nw_dst";
+    final public static String STR_NW_SRC = "nw_src";
+    final public static String STR_NW_PROTO = "nw_proto";
+    final public static String STR_NW_TOS = "nw_tos";
+    final public static String STR_TP_DST = "tp_dst";
+    final public static String STR_TP_SRC = "tp_src";
+
+    protected int wildcards;
+    protected short inputPort;
+    protected byte[] dataLayerSource;
+    protected byte[] dataLayerDestination;
+    protected short dataLayerVirtualLan;
+    protected byte dataLayerVirtualLanPriorityCodePoint;
+    protected short dataLayerType;
+    protected byte networkTypeOfService;
+    protected byte networkProtocol;
+    protected int networkSource;
+    protected int networkDestination;
+    protected short transportSource;
+    protected short transportDestination;
+
+    /**
+     * By default, create a OFMatch that matches everything
+     * 
+     * (mostly because it's the least amount of work to make a valid OFMatch)
+     */
+    public OFMatch() {
+        this.wildcards = OFPFW_ALL;
+        this.dataLayerDestination = new byte[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+        this.dataLayerSource = new byte[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+        this.dataLayerVirtualLan = -1;
+        this.dataLayerVirtualLanPriorityCodePoint = 0;
+        this.dataLayerType = 0;
+        this.inputPort = 0;
+        this.networkProtocol = 0;
+        this.networkTypeOfService = 0;
+        this.networkSource = 0;
+        this.networkDestination = 0;
+        this.transportDestination = 0;
+        this.transportSource = 0;
+    }
+
+    /**
+     * Get dl_dst
+     * 
+     * @return an arrays of bytes
+     */
+    public byte[] getDataLayerDestination() {
+        return this.dataLayerDestination;
+    }
+
+    /**
+     * Set dl_dst
+     * 
+     * @param dataLayerDestination
+     */
+    public OFMatch setDataLayerDestination(byte[] dataLayerDestination) {
+        this.dataLayerDestination = dataLayerDestination;
+        return this;
+    }
+
+    /**
+     * Set dl_dst, but first translate to byte[] using HexString
+     * 
+     * @param mac
+     *            A colon separated string of 6 pairs of octets, e..g.,
+     *            "00:17:42:EF:CD:8D"
+     */
+    public OFMatch setDataLayerDestination(String mac) {
+        byte bytes[] = HexString.fromHexString(mac);
+        if (bytes.length != 6)
+            throw new IllegalArgumentException(
+                    "expected string with 6 octets, got '" + mac + "'");
+        this.dataLayerDestination = bytes;
+        return this;
+    }
+
+    /**
+     * Get dl_src
+     * 
+     * @return an array of bytes
+     */
+    public byte[] getDataLayerSource() {
+        return this.dataLayerSource;
+    }
+
+    /**
+     * Set dl_src
+     * 
+     * @param dataLayerSource
+     */
+    public OFMatch setDataLayerSource(byte[] dataLayerSource) {
+        this.dataLayerSource = dataLayerSource;
+        return this;
+    }
+
+    /**
+     * Set dl_src, but first translate to byte[] using HexString
+     * 
+     * @param mac
+     *            A colon separated string of 6 pairs of octets, e..g.,
+     *            "00:17:42:EF:CD:8D"
+     */
+    public OFMatch setDataLayerSource(String mac) {
+        byte bytes[] = HexString.fromHexString(mac);
+        if (bytes.length != 6)
+            throw new IllegalArgumentException(
+                    "expected string with 6 octets, got '" + mac + "'");
+        this.dataLayerSource = bytes;
+        return this;
+    }
+
+    /**
+     * Get dl_type
+     * 
+     * @return ether_type
+     */
+    public short getDataLayerType() {
+        return this.dataLayerType;
+    }
+
+    /**
+     * Set dl_type
+     * 
+     * @param dataLayerType
+     */
+    public OFMatch setDataLayerType(short dataLayerType) {
+        this.dataLayerType = dataLayerType;
+        return this;
+    }
+
+    /**
+     * Get dl_vlan
+     * 
+     * @return vlan tag; VLAN_NONE == no tag
+     */
+    public short getDataLayerVirtualLan() {
+        return this.dataLayerVirtualLan;
+    }
+
+    /**
+     * Set dl_vlan
+     * 
+     * @param dataLayerVirtualLan
+     */
+    public OFMatch setDataLayerVirtualLan(short dataLayerVirtualLan) {
+        this.dataLayerVirtualLan = dataLayerVirtualLan;
+        return this;
+    }
+
+    /**
+     * Get dl_vlan_pcp
+     * 
+     * @return
+     */
+    public byte getDataLayerVirtualLanPriorityCodePoint() {
+        return this.dataLayerVirtualLanPriorityCodePoint;
+    }
+
+    /**
+     * Set dl_vlan_pcp
+     * 
+     * @param pcp
+     */
+    public OFMatch setDataLayerVirtualLanPriorityCodePoint(byte pcp) {
+        this.dataLayerVirtualLanPriorityCodePoint = pcp;
+        return this;
+    }
+
+    /**
+     * Get in_port
+     * 
+     * @return
+     */
+    public short getInputPort() {
+        return this.inputPort;
+    }
+
+    /**
+     * Set in_port
+     * 
+     * @param inputPort
+     */
+    public OFMatch setInputPort(short inputPort) {
+        this.inputPort = inputPort;
+        return this;
+    }
+
+    /**
+     * Get nw_dst
+     * 
+     * @return
+     */
+    public int getNetworkDestination() {
+        return this.networkDestination;
+    }
+
+    /**
+     * Set nw_dst
+     * 
+     * @param networkDestination
+     */
+    public OFMatch setNetworkDestination(int networkDestination) {
+        this.networkDestination = networkDestination;
+        return this;
+    }
+
+    /**
+     * Parse this match's wildcard fields and return the number of significant
+     * bits in the IP destination field.
+     * 
+     * NOTE: this returns the number of bits that are fixed, i.e., like CIDR,
+     * not the number of bits that are free like OpenFlow encodes.
+     * 
+     * @return a number between 0 (matches all IPs) and 63 ( 32>= implies exact
+     *         match)
+     */
+    public int getNetworkDestinationMaskLen() {
+        return Math
+                .max(32 - ((wildcards & OFPFW_NW_DST_MASK) >> OFPFW_NW_DST_SHIFT),
+                        0);
+    }
+
+    /**
+     * Parse this match's wildcard fields and return the number of significant
+     * bits in the IP destination field.
+     * 
+     * NOTE: this returns the number of bits that are fixed, i.e., like CIDR,
+     * not the number of bits that are free like OpenFlow encodes.
+     * 
+     * @return a number between 0 (matches all IPs) and 32 (exact match)
+     */
+    public int getNetworkSourceMaskLen() {
+        return Math
+                .max(32 - ((wildcards & OFPFW_NW_SRC_MASK) >> OFPFW_NW_SRC_SHIFT),
+                        0);
+    }
+
+    /**
+     * Get nw_proto
+     * 
+     * @return
+     */
+    public byte getNetworkProtocol() {
+        return this.networkProtocol;
+    }
+
+    /**
+     * Set nw_proto
+     * 
+     * @param networkProtocol
+     */
+    public OFMatch setNetworkProtocol(byte networkProtocol) {
+        this.networkProtocol = networkProtocol;
+        return this;
+    }
+
+    /**
+     * Get nw_src
+     * 
+     * @return
+     */
+    public int getNetworkSource() {
+        return this.networkSource;
+    }
+
+    /**
+     * Set nw_src
+     * 
+     * @param networkSource
+     */
+    public OFMatch setNetworkSource(int networkSource) {
+        this.networkSource = networkSource;
+        return this;
+    }
+
+    /**
+     * Get nw_tos
+     * OFMatch stores the ToS bits as top 6-bits, so right shift by 2 bits
+     * before returning the value
+     * 
+     * @return : 6-bit DSCP value (0-63)
+     */
+    public byte getNetworkTypeOfService() {
+        return (byte) ((this.networkTypeOfService >> 2) & 0x3f);
+    }
+
+    /**
+     * Set nw_tos
+     * OFMatch stores the ToS bits as top 6-bits, so left shift by 2 bits
+     * before storing the value
+     * 
+     * @param networkTypeOfService : 6-bit DSCP value (0-63)
+     */
+    public OFMatch setNetworkTypeOfService(byte networkTypeOfService) {
+        this.networkTypeOfService = (byte)(networkTypeOfService << 2);
+        return this;
+    }
+    
+
+    /**
+     * Get tp_dst
+     * 
+     * @return
+     */
+    public short getTransportDestination() {
+        return this.transportDestination;
+    }
+
+    /**
+     * Set tp_dst
+     * 
+     * @param transportDestination
+     */
+    public OFMatch setTransportDestination(short transportDestination) {
+        this.transportDestination = transportDestination;
+        return this;
+    }
+
+    /**
+     * Get tp_src
+     * 
+     * @return
+     */
+    public short getTransportSource() {
+        return this.transportSource;
+    }
+
+    /**
+     * Set tp_src
+     * 
+     * @param transportSource
+     */
+    public OFMatch setTransportSource(short transportSource) {
+        this.transportSource = transportSource;
+        return this;
+    }
+
+    /**
+     * Get wildcards
+     * 
+     * @return
+     */
+    public int getWildcards() {
+        return this.wildcards;
+    }
+
+    /**
+     * Set wildcards
+     * 
+     * @param wildcards
+     */
+    public OFMatch setWildcards(int wildcards) {
+        this.wildcards = wildcards;
+        return this;
+    }
+
+    /**
+     * Initializes this OFMatch structure with the corresponding data from the
+     * specified packet.
+     * 
+     * Must specify the input port, to ensure that this.in_port is set
+     * correctly.
+     * 
+     * Specify OFPort.NONE or OFPort.ANY if input port not applicable or
+     * available
+     * 
+     * @param packetData
+     *            The packet's data
+     * @param inputPort
+     *            the port the packet arrived on
+     */
+    public OFMatch loadFromPacket(byte[] packetData, short inputPort) {
+        short scratch;
+        int transportOffset = 34;
+        ByteBuffer packetDataBB = ByteBuffer.wrap(packetData);
+        int limit = packetDataBB.limit();
+
+        this.wildcards = 0; // all fields have explicit entries
+
+        this.inputPort = inputPort;
+
+        if (inputPort == OFPort.OFPP_ALL.getValue())
+            this.wildcards |= OFPFW_IN_PORT;
+
+        assert (limit >= 14);
+        // dl dst
+        this.dataLayerDestination = new byte[6];
+        packetDataBB.get(this.dataLayerDestination);
+        // dl src
+        this.dataLayerSource = new byte[6];
+        packetDataBB.get(this.dataLayerSource);
+        // dl type
+        this.dataLayerType = packetDataBB.getShort();
+
+        if (getDataLayerType() != (short) 0x8100) { // need cast to avoid signed
+            // bug
+            setDataLayerVirtualLan((short) 0xffff);
+            setDataLayerVirtualLanPriorityCodePoint((byte) 0);
+        } else {
+            // has vlan tag
+            scratch = packetDataBB.getShort();
+            setDataLayerVirtualLan((short) (0xfff & scratch));
+            setDataLayerVirtualLanPriorityCodePoint((byte) ((0xe000 & scratch) >> 13));
+            this.dataLayerType = packetDataBB.getShort();
+        }
+
+        switch (getDataLayerType()) {
+        case 0x0800:
+            // ipv4
+            // check packet length
+            scratch = packetDataBB.get();
+            scratch = (short) (0xf & scratch);
+            transportOffset = (packetDataBB.position() - 1) + (scratch * 4);
+            // nw tos (dscp)
+            scratch = packetDataBB.get();
+            setNetworkTypeOfService((byte) ((0xfc & scratch) >> 2));
+            // nw protocol
+            packetDataBB.position(packetDataBB.position() + 7);
+            this.networkProtocol = packetDataBB.get();
+            // nw src
+            packetDataBB.position(packetDataBB.position() + 2);
+            this.networkSource = packetDataBB.getInt();
+            // nw dst
+            this.networkDestination = packetDataBB.getInt();
+            packetDataBB.position(transportOffset);
+            break;
+        case 0x0806:
+            // arp
+            int arpPos = packetDataBB.position();
+            // opcode
+            scratch = packetDataBB.getShort(arpPos + 6);
+            setNetworkProtocol((byte) (0xff & scratch));
+
+            scratch = packetDataBB.getShort(arpPos + 2);
+            // if ipv4 and addr len is 4
+            if (scratch == 0x800 && packetDataBB.get(arpPos + 5) == 4) {
+                // nw src
+                this.networkSource = packetDataBB.getInt(arpPos + 14);
+                // nw dst
+                this.networkDestination = packetDataBB.getInt(arpPos + 24);
+            } else {
+                setNetworkSource(0);
+                setNetworkDestination(0);
+            }
+            break;
+        default:
+            setNetworkTypeOfService((byte) 0);
+            setNetworkProtocol((byte) 0);
+            setNetworkSource(0);
+            setNetworkDestination(0);
+            break;
+        }
+
+        switch (getNetworkProtocol()) {
+        case 0x01:
+            // icmp
+            // type
+            this.transportSource = U8.f(packetDataBB.get());
+            // code
+            this.transportDestination = U8.f(packetDataBB.get());
+            break;
+        case 0x06:
+            // tcp
+            // tcp src
+            this.transportSource = packetDataBB.getShort();
+            // tcp dest
+            this.transportDestination = packetDataBB.getShort();
+            break;
+        case 0x11:
+            // udp
+            // udp src
+            this.transportSource = packetDataBB.getShort();
+            // udp dest
+            this.transportDestination = packetDataBB.getShort();
+            break;
+        default:
+            setTransportDestination((short) 0);
+            setTransportSource((short) 0);
+            break;
+        }
+        return this;
+    }
+
+    /**
+     * Read this message off the wire from the specified ByteBuffer
+     * 
+     * @param data
+     */
+    public void readFrom(ChannelBuffer data) {
+        this.wildcards = data.readInt();
+        this.inputPort = data.readShort();
+        this.dataLayerSource = new byte[6];
+        data.readBytes(this.dataLayerSource);
+        this.dataLayerDestination = new byte[6];
+        data.readBytes(this.dataLayerDestination);
+        this.dataLayerVirtualLan = data.readShort();
+        this.dataLayerVirtualLanPriorityCodePoint = data.readByte();
+        data.readByte(); // pad
+        this.dataLayerType = data.readShort();
+        this.networkTypeOfService = data.readByte();
+        this.networkProtocol = data.readByte();
+        data.readByte(); // pad
+        data.readByte(); // pad
+        this.networkSource = data.readInt();
+        this.networkDestination = data.readInt();
+        this.transportSource = data.readShort();
+        this.transportDestination = data.readShort();
+    }
+
+    /**
+     * Write this message's binary format to the specified ByteBuffer
+     * 
+     * @param data
+     */
+    public void writeTo(ChannelBuffer data) {
+        data.writeInt(wildcards);
+        data.writeShort(inputPort);
+        data.writeBytes(this.dataLayerSource);
+        data.writeBytes(this.dataLayerDestination);
+        data.writeShort(dataLayerVirtualLan);
+        data.writeByte(dataLayerVirtualLanPriorityCodePoint);
+        data.writeByte((byte) 0x0); // pad
+        data.writeShort(dataLayerType);
+        data.writeByte(networkTypeOfService);
+        data.writeByte(networkProtocol);
+        data.writeByte((byte) 0x0); // pad
+        data.writeByte((byte) 0x0); // pad
+        data.writeInt(networkSource);
+        data.writeInt(networkDestination);
+        data.writeShort(transportSource);
+        data.writeShort(transportDestination);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 131;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(dataLayerDestination);
+        result = prime * result + Arrays.hashCode(dataLayerSource);
+        result = prime * result + dataLayerType;
+        result = prime * result + dataLayerVirtualLan;
+        result = prime * result + dataLayerVirtualLanPriorityCodePoint;
+        result = prime * result + inputPort;
+        result = prime * result + networkDestination;
+        result = prime * result + networkProtocol;
+        result = prime * result + networkSource;
+        result = prime * result + networkTypeOfService;
+        result = prime * result + transportDestination;
+        result = prime * result + transportSource;
+        result = prime * result + wildcards;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFMatch)) {
+            return false;
+        }
+        OFMatch other = (OFMatch) obj;
+        if (!Arrays.equals(dataLayerDestination, other.dataLayerDestination)) {
+            return false;
+        }
+        if (!Arrays.equals(dataLayerSource, other.dataLayerSource)) {
+            return false;
+        }
+        if (dataLayerType != other.dataLayerType) {
+            return false;
+        }
+        if (dataLayerVirtualLan != other.dataLayerVirtualLan) {
+            return false;
+        }
+        if (dataLayerVirtualLanPriorityCodePoint != other.dataLayerVirtualLanPriorityCodePoint) {
+            return false;
+        }
+        if (inputPort != other.inputPort) {
+            return false;
+        }
+        if (networkDestination != other.networkDestination) {
+            return false;
+        }
+        if (networkProtocol != other.networkProtocol) {
+            return false;
+        }
+        if (networkSource != other.networkSource) {
+            return false;
+        }
+        if (networkTypeOfService != other.networkTypeOfService) {
+            return false;
+        }
+        if (transportDestination != other.transportDestination) {
+            return false;
+        }
+        if (transportSource != other.transportSource) {
+            return false;
+        }
+        if ((wildcards & OFMatch.OFPFW_ALL) != (other.wildcards & OFPFW_ALL)) { // only
+            // consider
+            // allocated
+            // part
+            // of
+            // wildcards
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Implement clonable interface
+     */
+    @Override
+    public OFMatch clone() {
+        try {
+            OFMatch ret = (OFMatch) super.clone();
+            ret.dataLayerDestination = this.dataLayerDestination.clone();
+            ret.dataLayerSource = this.dataLayerSource.clone();
+            return ret;
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Output a dpctl-styled string, i.e., only list the elements that are not
+     * wildcarded
+     * 
+     * A match-everything OFMatch outputs "OFMatch[]"
+     * 
+     * @return 
+     *         "OFMatch[dl_src:00:20:01:11:22:33,nw_src:192.168.0.0/24,tp_dst:80]"
+     */
+    @Override
+    public String toString() {
+        String str = "";
+
+        // l1
+        if ((wildcards & OFPFW_IN_PORT) == 0)
+            str += "," + STR_IN_PORT + "=" + U16.f(this.inputPort);
+
+        // l2
+        if ((wildcards & OFPFW_DL_DST) == 0)
+            str += "," + STR_DL_DST + "="
+                    + HexString.toHexString(this.dataLayerDestination);
+        if ((wildcards & OFPFW_DL_SRC) == 0)
+            str += "," + STR_DL_SRC + "="
+                    + HexString.toHexString(this.dataLayerSource);
+        if ((wildcards & OFPFW_DL_TYPE) == 0)
+            str += "," + STR_DL_TYPE + "=0x"
+                    + Integer.toHexString(U16.f(this.dataLayerType));
+        if ((wildcards & OFPFW_DL_VLAN) == 0)
+            str += "," + STR_DL_VLAN + "=0x"
+                    + Integer.toHexString(U16.f(this.dataLayerVirtualLan));
+        if ((wildcards & OFPFW_DL_VLAN_PCP) == 0)
+            str += ","
+                    + STR_DL_VLAN_PCP
+                    + "="
+                    + Integer.toHexString(U8
+                            .f(this.dataLayerVirtualLanPriorityCodePoint));
+
+        // l3
+        if (getNetworkDestinationMaskLen() > 0)
+            str += ","
+                    + STR_NW_DST
+                    + "="
+                    + cidrToString(networkDestination,
+                            getNetworkDestinationMaskLen());
+        if (getNetworkSourceMaskLen() > 0)
+            str += "," + STR_NW_SRC + "="
+                    + cidrToString(networkSource, getNetworkSourceMaskLen());
+        if ((wildcards & OFPFW_NW_PROTO) == 0)
+            str += "," + STR_NW_PROTO + "=" + this.networkProtocol;
+        if ((wildcards & OFPFW_NW_TOS) == 0)
+            str += "," + STR_NW_TOS + "=" + this.getNetworkTypeOfService();
+
+        // l4
+        if ((wildcards & OFPFW_TP_DST) == 0)
+            str += "," + STR_TP_DST + "=" + this.transportDestination;
+        if ((wildcards & OFPFW_TP_SRC) == 0)
+            str += "," + STR_TP_SRC + "=" + this.transportSource;
+        if ((str.length() > 0) && (str.charAt(0) == ','))
+            str = str.substring(1); // trim the leading ","
+        // done
+        return "OFMatch[" + str + "]";
+    }
+
+    private String cidrToString(int ip, int prefix) {
+        String str;
+        if (prefix >= 32) {
+            str = ipToString(ip);
+        } else {
+            // use the negation of mask to fake endian magic
+            int mask = ~((1 << (32 - prefix)) - 1);
+            str = ipToString(ip & mask) + "/" + prefix;
+        }
+
+        return str;
+    }
+
+    /**
+     * Set this OFMatch's parameters based on a comma-separated key=value pair
+     * dpctl-style string, e.g., from the output of OFMatch.toString() <br>
+     * <p>
+     * Supported keys/values include <br>
+     * <p>
+     * <TABLE border=1>
+     * <TR>
+     * <TD>KEY(s)
+     * <TD>VALUE
+     * </TR>
+     * <TR>
+     * <TD>"in_port","input_port"
+     * <TD>integer
+     * </TR>
+     * <TR>
+     * <TD>"dl_src","eth_src", "dl_dst","eth_dst"
+     * <TD>hex-string
+     * </TR>
+     * <TR>
+     * <TD>"dl_type", "dl_vlan", "dl_vlan_pcp"
+     * <TD>integer
+     * </TR>
+     * <TR>
+     * <TD>"nw_src", "nw_dst", "ip_src", "ip_dst"
+     * <TD>CIDR-style netmask
+     * </TR>
+     * <TR>
+     * <TD>"tp_src","tp_dst"
+     * <TD>integer (max 64k)
+     * </TR>
+     * </TABLE>
+     * <p>
+     * The CIDR-style netmasks assume 32 netmask if none given, so:
+     * "128.8.128.118/32" is the same as "128.8.128.118"
+     * 
+     * @param match
+     *            a key=value comma separated string, e.g.
+     *            "in_port=5,ip_dst=192.168.0.0/16,tp_src=80"
+     * @throws IllegalArgumentException
+     *             on unexpected key or value
+     */
+
+    public void fromString(String match) throws IllegalArgumentException {
+        if (match.equals("") || match.equalsIgnoreCase("any")
+                || match.equalsIgnoreCase("all") || match.equals("[]"))
+            match = "OFMatch[]";
+        String[] tokens = match.split("[\\[,\\]]");
+        String[] values;
+        int initArg = 0;
+        if (tokens[0].equals("OFMatch"))
+            initArg = 1;
+        this.wildcards = OFPFW_ALL;
+        int i;
+        for (i = initArg; i < tokens.length; i++) {
+            values = tokens[i].split("=");
+            if (values.length != 2)
+                throw new IllegalArgumentException("Token " + tokens[i]
+                        + " does not have form 'key=value' parsing " + match);
+            values[0] = values[0].toLowerCase(); // try to make this case insens
+            if (values[0].equals(STR_IN_PORT) || values[0].equals("input_port")) {
+                this.inputPort = U16.t(Integer.valueOf(values[1]));
+                this.wildcards &= ~OFPFW_IN_PORT;
+            } else if (values[0].equals(STR_DL_DST) || values[0].equals("eth_dst")) {
+                this.dataLayerDestination = HexString.fromHexString(values[1]);
+                this.wildcards &= ~OFPFW_DL_DST;
+            } else if (values[0].equals(STR_DL_SRC) || values[0].equals("eth_src")) {
+                this.dataLayerSource = HexString.fromHexString(values[1]);
+                this.wildcards &= ~OFPFW_DL_SRC;
+            } else if (values[0].equals(STR_DL_TYPE) || values[0].equals("eth_type")) {
+                if (values[1].startsWith("0x"))
+                    this.dataLayerType = U16.t(Integer.valueOf(
+                            values[1].replaceFirst("0x", ""), 16));
+                else
+                    this.dataLayerType = U16.t(Integer.valueOf(values[1]));
+                this.wildcards &= ~OFPFW_DL_TYPE;
+            } else if (values[0].equals(STR_DL_VLAN)) {
+            	if (values[1].startsWith("0x"))
+            		this.dataLayerVirtualLan = U16.t(Integer.valueOf(
+            				values[1].replaceFirst("0x", ""),16));
+            	else
+            		this.dataLayerVirtualLan = U16.t(Integer.valueOf(values[1]));
+                this.wildcards &= ~OFPFW_DL_VLAN;
+            } else if (values[0].equals(STR_DL_VLAN_PCP)) {
+                this.dataLayerVirtualLanPriorityCodePoint = U8.t(Short
+                        .valueOf(values[1]));
+                this.wildcards &= ~OFPFW_DL_VLAN_PCP;
+            } else if (values[0].equals(STR_NW_DST) || values[0].equals("ip_dst")) {
+                setFromCIDR(values[1], STR_NW_DST);
+            } else if (values[0].equals(STR_NW_SRC) || values[0].equals("ip_src")) {
+                setFromCIDR(values[1], STR_NW_SRC);
+            } else if (values[0].equals(STR_NW_PROTO)) {
+                this.networkProtocol = U8.t(Short.valueOf(values[1]));
+                this.wildcards &= ~OFPFW_NW_PROTO;
+            } else if (values[0].equals(STR_NW_TOS)) {
+                this.setNetworkTypeOfService(U8.t(Short.valueOf(values[1])));
+                this.wildcards &= ~OFPFW_NW_TOS;
+            } else if (values[0].equals(STR_TP_DST)) {
+                this.transportDestination = U16.t(Integer.valueOf(values[1]));
+                this.wildcards &= ~OFPFW_TP_DST;
+            } else if (values[0].equals(STR_TP_SRC)) {
+                this.transportSource = U16.t(Integer.valueOf(values[1]));
+                this.wildcards &= ~OFPFW_TP_SRC;
+            } else {
+                throw new IllegalArgumentException("unknown token " + tokens[i]
+                        + " parsing " + match);
+            }
+        }
+    }
+
+    /**
+     * Set the networkSource or networkDestionation address and their wildcards
+     * from the CIDR string
+     * 
+     * @param cidr
+     *            "192.168.0.0/16" or "172.16.1.5"
+     * @param which
+     *            one of STR_NW_DST or STR_NW_SRC
+     * @throws IllegalArgumentException
+     */
+    private void setFromCIDR(String cidr, String which)
+            throws IllegalArgumentException {
+        String values[] = cidr.split("/");
+        String[] ip_str = values[0].split("\\.");
+        int ip = 0;
+        ip += Integer.valueOf(ip_str[0]) << 24;
+        ip += Integer.valueOf(ip_str[1]) << 16;
+        ip += Integer.valueOf(ip_str[2]) << 8;
+        ip += Integer.valueOf(ip_str[3]);
+        int prefix = 32; // all bits are fixed, by default
+
+        if (values.length >= 2)
+            prefix = Integer.valueOf(values[1]);
+        int mask = 32 - prefix;
+        if (which.equals(STR_NW_DST)) {
+            this.networkDestination = ip;
+            this.wildcards = (wildcards & ~OFPFW_NW_DST_MASK)
+                    | (mask << OFPFW_NW_DST_SHIFT);
+        } else if (which.equals(STR_NW_SRC)) {
+            this.networkSource = ip;
+            this.wildcards = (wildcards & ~OFPFW_NW_SRC_MASK)
+                    | (mask << OFPFW_NW_SRC_SHIFT);
+        }
+    }
+
+    protected static String ipToString(int ip) {
+        return Integer.toString(U8.f((byte) ((ip & 0xff000000) >> 24))) + "."
+                + Integer.toString((ip & 0x00ff0000) >> 16) + "."
+                + Integer.toString((ip & 0x0000ff00) >> 8) + "."
+                + Integer.toString(ip & 0x000000ff);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchBeanInfo.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchBeanInfo.java
new file mode 100644
index 0000000..16a813f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchBeanInfo.java
@@ -0,0 +1,106 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Extra info for how to treat OFMatch as a JavaBean
+ * 
+ * For some (inane!) reason, using chained setters in OFMatch breaks a lot of the JavaBean defaults.
+ * 
+ * We don't really use OFMatch as a java bean, but there are a lot of nice XML utils that work for
+ * free if OFMatch follows the java bean paradigm.
+ * 
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ *
+ */
+
+public class OFMatchBeanInfo extends SimpleBeanInfo {
+
+    @Override
+    public PropertyDescriptor[] getPropertyDescriptors() {
+        List<PropertyDescriptor> descs = new LinkedList<PropertyDescriptor>();
+        Field[] fields = OFMatch.class.getDeclaredFields();
+        String name;
+        for (int i=0; i< fields.length; i++) {
+            int mod = fields[i].getModifiers();
+            if(Modifier.isFinal(mod) ||     // don't expose static or final fields 
+                    Modifier.isStatic(mod))
+                continue;
+            
+            name = fields[i].getName();
+            Class<?> type = fields[i].getType();
+            
+            try {
+                descs.add(new PropertyDescriptor(name, 
+                        name2getter(OFMatch.class, name), 
+                        name2setter(OFMatch.class, name, type)));
+            } catch (IntrospectionException e) {
+                e.printStackTrace();
+                throw new RuntimeException(e);
+            }
+        }
+        
+        return descs.toArray(new PropertyDescriptor[0]);
+    }
+
+
+    private Method name2setter(Class<OFMatch> c, String name, Class<?> type) {
+        String mName = "set" + toLeadingCaps(name);
+        Method m = null;
+        try {
+            m = c.getMethod(mName, new Class[]{ type});
+        } catch (SecurityException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+        return m;
+    }
+
+    private Method name2getter(Class<OFMatch> c, String name) {
+        String mName= "get" + toLeadingCaps(name);
+        Method m = null;
+        try {
+            m = c.getMethod(mName, new Class[]{});
+        } catch (SecurityException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+        return m;
+    }
+    
+    private String toLeadingCaps(String s) {
+        char[] array = s.toCharArray();
+        array[0] = Character.toUpperCase(array[0]);
+        return String.valueOf(array, 0, array.length);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchWithSwDpid.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchWithSwDpid.java
new file mode 100644
index 0000000..052a652
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchWithSwDpid.java
@@ -0,0 +1,39 @@
+package org.openflow.protocol;
+
+import org.openflow.util.HexString;
+
+public class OFMatchWithSwDpid {
+    protected OFMatch ofMatch;
+    protected long  switchDataPathId;
+
+    public OFMatchWithSwDpid() {
+    	this.ofMatch = new OFMatch();
+    	this.switchDataPathId = 0;
+    }
+    
+    public OFMatchWithSwDpid(OFMatch ofm, long swDpid) {
+    	this.ofMatch = ofm.clone();
+    	this.switchDataPathId = swDpid;
+    }
+    public OFMatch getOfMatch() {
+		return ofMatch;
+	}
+
+	public void setOfMatch(OFMatch ofMatch) {
+		this.ofMatch = ofMatch.clone();
+	}
+
+	public long getSwitchDataPathId() {
+        return this.switchDataPathId;
+    }
+
+    public OFMatchWithSwDpid setSwitchDataPathId(long dpid) {
+        this.switchDataPathId = dpid;
+        return this;
+    }
+    
+    @Override
+    public String toString() {
+        return "OFMatchWithSwDpid [" + HexString.toHexString(switchDataPathId) + ofMatch + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessage.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessage.java
new file mode 100644
index 0000000..7ea69a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessage.java
@@ -0,0 +1,334 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.packet.Ethernet;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.openflow.util.U32;
+import org.openflow.util.U8;
+
+/**
+ * The base class for all OpenFlow protocol messages. This class contains the
+ * equivalent of the ofp_header which is present in all OpenFlow messages.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Feb 3, 2010
+ * @author Rob Sherwood (rob.sherwood@stanford.edu) - Feb 3, 2010
+ */
+public class OFMessage {
+    public static byte OFP_VERSION = 0x01;
+    public static int MINIMUM_LENGTH = 8;
+
+    protected byte version;
+    protected OFType type;
+    protected short length;
+    protected int xid;
+    
+    private ConcurrentHashMap<String, Object> storage;
+    
+    public OFMessage() {
+        storage = null;
+        this.version = OFP_VERSION;
+    }
+    
+    protected synchronized ConcurrentHashMap<String, Object> getMessageStore() {
+        if (storage == null) {
+            storage = new ConcurrentHashMap<String, Object>();;
+        }
+        return storage;
+    }
+
+    /**
+     * Get the length of this message
+     *
+     * @return
+     */
+    public short getLength() {
+        return length;
+    }
+
+    /**
+     * Get the length of this message, unsigned
+     *
+     * @return
+     */
+    public int getLengthU() {
+        return U16.f(length);
+    }
+
+    /**
+     * Set the length of this message
+     *
+     * @param length
+     */
+    public OFMessage setLength(short length) {
+        this.length = length;
+        return this;
+    }
+
+    /**
+     * Set the length of this message, unsigned
+     *
+     * @param length
+     */
+    public OFMessage setLengthU(int length) {
+        this.length = U16.t(length);
+        return this;
+    }
+
+    /**
+     * Get the type of this message
+     *
+     * @return
+     */
+    public OFType getType() {
+        return type;
+    }
+
+    /**
+     * Set the type of this message
+     *
+     * @param type
+     */
+    public void setType(OFType type) {
+        this.type = type;
+    }
+
+    /**
+     * Get the OpenFlow version of this message
+     *
+     * @return
+     */
+    public byte getVersion() {
+        return version;
+    }
+
+    /**
+     * Set the OpenFlow version of this message
+     *
+     * @param version
+     */
+    public void setVersion(byte version) {
+        this.version = version;
+    }
+
+    /**
+     * Get the transaction id of this message
+     *
+     * @return
+     */
+    public int getXid() {
+        return xid;
+    }
+
+    /**
+     * Set the transaction id of this message
+     *
+     * @param xid
+     */
+    public void setXid(int xid) {
+        this.xid = xid;
+    }
+
+    /**
+     * Read this message off the wire from the specified ByteBuffer
+     * @param data
+     */
+    public void readFrom(ChannelBuffer data) {
+        this.version = data.readByte();
+        this.type = OFType.valueOf(data.readByte());
+        this.length = data.readShort();
+        this.xid = data.readInt();
+    }
+
+    /**
+     * Write this message's binary format to the specified ByteBuffer
+     * @param data
+     */
+    public void writeTo(ChannelBuffer data) {
+        data.writeByte(version);
+        data.writeByte(type.getTypeValue());
+        data.writeShort(length);
+        data.writeInt(xid);
+    }
+
+    /**
+     * Returns a summary of the message
+     * @return "ofmsg=v=$version;t=$type:l=$len:xid=$xid"
+     */
+    public String toString() {
+        return "ofmsg" +
+            ":v=" + U8.f(this.getVersion()) +
+            ";t=" + this.getType() +
+            ";l=" + this.getLengthU() +
+            ";x=" + U32.f(this.getXid());
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 97;
+        int result = 1;
+        result = prime * result + length;
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        result = prime * result + version;
+        result = prime * result + xid;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFMessage)) {
+            return false;
+        }
+        OFMessage other = (OFMessage) obj;
+        if (length != other.length) {
+            return false;
+        }
+        if (type == null) {
+            if (other.type != null) {
+                return false;
+            }
+        } else if (!type.equals(other.type)) {
+            return false;
+        }
+        if (version != other.version) {
+            return false;
+        }
+        if (xid != other.xid) {
+            return false;
+        }
+        return true;
+    }
+    
+    public static String getDataAsString(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+
+        Ethernet eth;
+        StringBuffer sb =  new StringBuffer("");
+
+        DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
+        Date date = new Date();
+
+        sb.append(dateFormat.format(date));
+        sb.append("      ");
+
+        switch (msg.getType()) {
+            case PACKET_IN:
+                OFPacketIn pktIn = (OFPacketIn) msg;
+                sb.append("packet_in          [ ");
+                sb.append(sw.getStringId());
+                sb.append(" -> Controller");
+                sb.append(" ]");
+
+                sb.append("\ntotal length: ");
+                sb.append(pktIn.getTotalLength());
+                sb.append("\nin_port: ");
+                sb.append(pktIn.getInPort());
+                sb.append("\ndata_length: ");
+                sb.append(pktIn.getTotalLength() - OFPacketIn.MINIMUM_LENGTH);
+                sb.append("\nbuffer: ");
+                sb.append(pktIn.getBufferId());
+
+                // If the conext is not set by floodlight, then ignore.
+                if (cntx != null) {
+                // packet type  icmp, arp, etc.
+                    eth = IFloodlightProviderService.bcStore.get(cntx,
+                            IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+                    if (eth != null)
+                           sb.append(eth.toString());
+                }
+                break;
+
+            case PACKET_OUT:
+                OFPacketOut pktOut = (OFPacketOut) msg;
+                sb.append("packet_out         [ ");
+                sb.append("Controller -> ");
+                sb.append(HexString.toHexString(sw.getId()));
+                sb.append(" ]");
+
+                sb.append("\nin_port: ");
+                sb.append(pktOut.getInPort());
+                sb.append("\nactions_len: ");
+                sb.append(pktOut.getActionsLength());
+                if (pktOut.getActions() != null) {
+                    sb.append("\nactions: ");
+                    sb.append(pktOut.getActions().toString());
+                }
+                break;
+
+            case FLOW_MOD:
+                OFFlowMod fm = (OFFlowMod) msg;
+                sb.append("flow_mod           [ ");
+                sb.append("Controller -> ");
+                sb.append(HexString.toHexString(sw.getId()));
+                sb.append(" ]");
+
+                // If the conext is not set by floodlight, then ignore.
+                if (cntx != null) {
+                    eth = IFloodlightProviderService.bcStore.get(cntx,
+                        IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+                    if (eth != null)
+                        sb.append(eth.toString());
+                }
+
+                sb.append("\nADD: cookie: ");
+                sb.append(fm.getCookie());
+                sb.append(" idle: ");
+                sb.append(fm.getIdleTimeout());
+                sb.append(" hard: ");
+                sb.append(fm.getHardTimeout());
+                sb.append(" pri: ");
+                sb.append(fm.getPriority());
+                sb.append(" buf: ");
+                sb.append(fm.getBufferId());
+                sb.append(" flg: ");
+                sb.append(fm.getFlags());
+                if (fm.getActions() != null) {
+                    sb.append("\nactions: ");
+                    sb.append(fm.getActions().toString());
+                }
+                break;
+
+            default:
+                sb.append("[Unknown Packet]");
+        }
+
+        sb.append("\n\n");
+        return sb.toString();
+
+    }
+
+    public static byte[] getData(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+        return OFMessage.getDataAsString(sw, msg, cntx).getBytes();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessageContextStore.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessageContextStore.java
new file mode 100644
index 0000000..b60aa1c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessageContextStore.java
@@ -0,0 +1,39 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.openflow.protocol.OFMessage;
+
+public class OFMessageContextStore<V> {
+    protected OFMessage msg;
+    String namespace;
+    
+    public OFMessageContextStore(OFMessage msg, String namespace) {
+        this.msg = msg;
+        this.namespace = namespace;
+    }
+    
+    @SuppressWarnings("unchecked")
+    public V get(String key) {
+        return (V)msg.getMessageStore().get(namespace + "|" + key);
+    }
+    
+    public void put(String key, V value) {
+        msg.getMessageStore().put(namespace + "|" + key, value);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketIn.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketIn.java
new file mode 100644
index 0000000..c37c918
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketIn.java
@@ -0,0 +1,211 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.Arrays;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+import org.openflow.util.U32;
+import org.openflow.util.U8;
+
+/**
+ * Represents an ofp_packet_in
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Feb 8, 2010
+ */
+public class OFPacketIn extends OFMessage {
+    public static short MINIMUM_LENGTH = 18;
+
+    public enum OFPacketInReason {
+        NO_MATCH, ACTION
+    }
+
+    protected int bufferId;
+    protected short totalLength;
+    protected short inPort;
+    protected OFPacketInReason reason;
+    protected byte[] packetData;
+
+    public OFPacketIn() {
+        super();
+        this.type = OFType.PACKET_IN;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    /**
+     * Get buffer_id
+     * @return
+     */
+    public int getBufferId() {
+        return this.bufferId;
+    }
+
+    /**
+     * Set buffer_id
+     * @param bufferId
+     */
+    public OFPacketIn setBufferId(int bufferId) {
+        this.bufferId = bufferId;
+        return this;
+    }
+
+    /**
+     * Returns the packet data
+     * @return
+     */
+    public byte[] getPacketData() {
+        return this.packetData;
+    }
+
+    /**
+     * Sets the packet data, and updates the length of this message
+     * @param packetData
+     */
+    public OFPacketIn setPacketData(byte[] packetData) {
+        this.packetData = packetData;
+        this.length = U16.t(OFPacketIn.MINIMUM_LENGTH + packetData.length);
+        return this;
+    }
+
+    /**
+     * Get in_port
+     * @return
+     */
+    public short getInPort() {
+        return this.inPort;
+    }
+
+    /**
+     * Set in_port
+     * @param inPort
+     */
+    public OFPacketIn setInPort(short inPort) {
+        this.inPort = inPort;
+        return this;
+    }
+
+    /**
+     * Get reason
+     * @return
+     */
+    public OFPacketInReason getReason() {
+        return this.reason;
+    }
+
+    /**
+     * Set reason
+     * @param reason
+     */
+    public OFPacketIn setReason(OFPacketInReason reason) {
+        this.reason = reason;
+        return this;
+    }
+
+    /**
+     * Get total_len
+     * @return
+     */
+    public short getTotalLength() {
+        return this.totalLength;
+    }
+
+    /**
+     * Set total_len
+     * @param totalLength
+     */
+    public OFPacketIn setTotalLength(short totalLength) {
+        this.totalLength = totalLength;
+        return this;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.bufferId = data.readInt();
+        this.totalLength = data.readShort();
+        this.inPort = data.readShort();
+        this.reason = OFPacketInReason.values()[U8.f(data.readByte())];
+        data.readByte(); // pad
+        this.packetData = new byte[getLengthU() - MINIMUM_LENGTH];
+        data.readBytes(this.packetData);
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeInt(bufferId);
+        data.writeShort(totalLength);
+        data.writeShort(inPort);
+        data.writeByte((byte) reason.ordinal());
+        data.writeByte((byte) 0x0); // pad
+        data.writeBytes(this.packetData);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 283;
+        int result = super.hashCode();
+        result = prime * result + bufferId;
+        result = prime * result + inPort;
+        result = prime * result + Arrays.hashCode(packetData);
+        result = prime * result + ((reason == null) ? 0 : reason.hashCode());
+        result = prime * result + totalLength;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFPacketIn)) {
+            return false;
+        }
+        OFPacketIn other = (OFPacketIn) obj;
+        if (bufferId != other.bufferId) {
+            return false;
+        }
+        if (inPort != other.inPort) {
+            return false;
+        }
+        if (!Arrays.equals(packetData, other.packetData)) {
+            return false;
+        }
+        if (reason == null) {
+            if (other.reason != null) {
+                return false;
+            }
+        } else if (!reason.equals(other.reason)) {
+            return false;
+        }
+        if (totalLength != other.totalLength) {
+            return false;
+        }
+        return true;
+    }
+
+    public String toString() {
+        String myStr = super.toString();
+        return "packetIn" +
+            ":bufferId=" + U32.f(this.bufferId) + myStr;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketOut.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketOut.java
new file mode 100644
index 0000000..2b45e8a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketOut.java
@@ -0,0 +1,240 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.factory.OFActionFactory;
+import org.openflow.protocol.factory.OFActionFactoryAware;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_packet_out message
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 12, 2010
+ */
+public class OFPacketOut extends OFMessage implements OFActionFactoryAware {
+    public static int MINIMUM_LENGTH = 16;
+    public static int BUFFER_ID_NONE = 0xffffffff;
+
+    protected OFActionFactory actionFactory;
+    protected int bufferId;
+    protected short inPort;
+    protected short actionsLength;
+    protected List<OFAction> actions;
+    protected byte[] packetData;
+
+    public OFPacketOut() {
+        super();
+        this.type = OFType.PACKET_OUT;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    /**
+     * Get buffer_id
+     * @return
+     */
+    public int getBufferId() {
+        return this.bufferId;
+    }
+
+    /**
+     * Set buffer_id
+     * @param bufferId
+     */
+    public OFPacketOut setBufferId(int bufferId) {
+        this.bufferId = bufferId;
+        return this;
+    }
+
+    /**
+     * Returns the packet data
+     * @return
+     */
+    public byte[] getPacketData() {
+        return this.packetData;
+    }
+
+    /**
+     * Sets the packet data
+     * @param packetData
+     */
+    public OFPacketOut setPacketData(byte[] packetData) {
+        this.packetData = packetData;
+        return this;
+    }
+
+    /**
+     * Get in_port
+     * @return
+     */
+    public short getInPort() {
+        return this.inPort;
+    }
+
+    /**
+     * Set in_port
+     * @param inPort
+     */
+    public OFPacketOut setInPort(short inPort) {
+        this.inPort = inPort;
+        return this;
+    }
+
+    /**
+     * Set in_port. Convenience method using OFPort enum.
+     * @param inPort
+     */
+    public OFPacketOut setInPort(OFPort inPort) {
+        this.inPort = inPort.getValue();
+        return this;
+    }
+
+    /**
+     * Get actions_len
+     * @return
+     */
+    public short getActionsLength() {
+        return this.actionsLength;
+    }
+
+    /**
+     * Get actions_len, unsigned
+     * @return
+     */
+    public int getActionsLengthU() {
+        return U16.f(this.actionsLength);
+    }
+
+    /**
+     * Set actions_len
+     * @param actionsLength
+     */
+    public OFPacketOut setActionsLength(short actionsLength) {
+        this.actionsLength = actionsLength;
+        return this;
+    }
+
+    /**
+     * Returns the actions contained in this message
+     * @return a list of ordered OFAction objects
+     */
+    public List<OFAction> getActions() {
+        return this.actions;
+    }
+
+    /**
+     * Sets the list of actions on this message
+     * @param actions a list of ordered OFAction objects
+     */
+    public OFPacketOut setActions(List<OFAction> actions) {
+        this.actions = actions;
+        return this;
+    }
+
+    @Override
+    public void setActionFactory(OFActionFactory actionFactory) {
+        this.actionFactory = actionFactory;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.bufferId = data.readInt();
+        this.inPort = data.readShort();
+        this.actionsLength = data.readShort();
+        if ( this.actionFactory == null)
+            throw new RuntimeException("ActionFactory not set");
+        this.actions = this.actionFactory.parseActions(data, getActionsLengthU());
+        this.packetData = new byte[getLengthU() - MINIMUM_LENGTH - getActionsLengthU()];
+        data.readBytes(this.packetData);
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeInt(bufferId);
+        data.writeShort(inPort);
+        data.writeShort(actionsLength);
+        for (OFAction action : actions) {
+            action.writeTo(data);
+        }
+        if (this.packetData != null)
+            data.writeBytes(this.packetData);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 293;
+        int result = super.hashCode();
+        result = prime * result + ((actions == null) ? 0 : actions.hashCode());
+        result = prime * result + actionsLength;
+        result = prime * result + bufferId;
+        result = prime * result + inPort;
+        result = prime * result + Arrays.hashCode(packetData);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFPacketOut)) {
+            return false;
+        }
+        OFPacketOut other = (OFPacketOut) obj;
+        if (actions == null) {
+            if (other.actions != null) {
+                return false;
+            }
+        } else if (!actions.equals(other.actions)) {
+            return false;
+        }
+        if (actionsLength != other.actionsLength) {
+            return false;
+        }
+        if (bufferId != other.bufferId) {
+            return false;
+        }
+        if (inPort != other.inPort) {
+            return false;
+        }
+        if (!Arrays.equals(packetData, other.packetData)) {
+            return false;
+        }
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "OFPacketOut [actionFactory=" + actionFactory + ", actions="
+                + actions + ", actionsLength=" + actionsLength + ", bufferId=0x"
+                + Integer.toHexString(bufferId) + ", inPort=" + inPort + ", packetData="
+                + Arrays.toString(packetData) + "]";
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPhysicalPort.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPhysicalPort.java
new file mode 100644
index 0000000..58fdae5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPhysicalPort.java
@@ -0,0 +1,470 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import net.floodlightcontroller.core.web.serializers.ByteArrayMACSerializer;
+import net.floodlightcontroller.core.web.serializers.UShortSerializer;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents ofp_phy_port
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 25, 2010
+ */
+public class OFPhysicalPort {
+    public static int MINIMUM_LENGTH = 48;
+    public static int OFP_ETH_ALEN = 6;
+
+    public enum OFPortConfig {
+        OFPPC_PORT_DOWN    (1 << 0) {
+            public String toString() {
+                return "port-down (0x1)";
+            }
+        },
+        OFPPC_NO_STP       (1 << 1) {
+            public String toString() {
+                return "no-stp (0x2)";
+            }
+        },
+        OFPPC_NO_RECV      (1 << 2) {
+            public String toString() {
+                return "no-recv (0x4)";
+            }
+        },
+        OFPPC_NO_RECV_STP  (1 << 3) {
+            public String toString() {
+                return "no-recv-stp (0x8)";
+            }
+        },
+        OFPPC_NO_FLOOD     (1 << 4) {
+            public String toString() {
+                return "no-flood (0x10)";
+            }
+        },
+        OFPPC_NO_FWD       (1 << 5) {
+            public String toString() {
+                return "no-fwd (0x20)";
+            }
+        },
+        OFPPC_NO_PACKET_IN (1 << 6) {
+            public String toString() {
+                return "no-pkt-in (0x40)";
+            }
+        };
+
+        protected int value;
+
+        private OFPortConfig(int value) {
+            this.value = value;
+        }
+
+        /**
+         * @return the value
+         */
+        public int getValue() {
+            return value;
+        }
+    }
+
+    public enum OFPortState {
+        OFPPS_LINK_DOWN   (1 << 0) {
+            public String toString() {
+                return "link-down (0x1)";
+            }
+        },
+        OFPPS_STP_LISTEN  (0 << 8) {
+            public String toString() {
+                return "listen (0x0)";
+            }
+        },
+        OFPPS_STP_LEARN   (1 << 8) {
+            public String toString() {
+                return "learn-no-relay (0x100)";
+            }
+        },
+        OFPPS_STP_FORWARD (2 << 8) {
+            public String toString() {
+                return "forward (0x200)";
+            }
+        },
+        OFPPS_STP_BLOCK   (3 << 8) {
+            public String toString() {
+                return "block-broadcast (0x300)";
+            }
+        },
+        OFPPS_STP_MASK    (3 << 8) {
+            public String toString() {
+                return "block-broadcast (0x300)";
+            }
+        };
+
+        protected int value;
+
+        private OFPortState(int value) {
+            this.value = value;
+        }
+
+        /**
+         * @return the value
+         */
+        public int getValue() {
+            return value;
+        }
+    }
+
+    public enum OFPortFeatures {
+        OFPPF_10MB_HD    (1 << 0) {
+            public String toString() {
+                return "10mb-hd (0x1)";
+            }
+        },
+        OFPPF_10MB_FD    (1 << 1) {
+            public String toString() {
+                return "10mb-fd (0x2)";
+            }
+        },
+        OFPPF_100MB_HD   (1 << 2) {
+            public String toString() {
+                return "100mb-hd (0x4)";
+            }
+        },
+        OFPPF_100MB_FD   (1 << 3) {
+            public String toString() {
+                return "100mb-fd (0x8)";
+            }
+        },
+        OFPPF_1GB_HD     (1 << 4) {
+            public String toString() {
+                return "1gb-hd (0x10)";
+            }
+        },
+        OFPPF_1GB_FD     (1 << 5) {
+            public String toString() {
+                return "1gb-fd (0x20)";
+            }
+        },
+        OFPPF_10GB_FD    (1 << 6) {
+            public String toString() {
+                return "10gb-fd (0x40)";
+            }
+        },
+        OFPPF_COPPER     (1 << 7) {
+            public String toString() {
+                return "copper (0x80)";
+            }
+        },
+        OFPPF_FIBER      (1 << 8) {
+            public String toString() {
+                return "fiber (0x100)";
+            }
+        },
+        OFPPF_AUTONEG    (1 << 9) {
+            public String toString() {
+                return "autoneg (0x200)";
+            }
+        },
+        OFPPF_PAUSE      (1 << 10) {
+            public String toString() {
+                return "pause (0x400)";
+            }
+        },
+        OFPPF_PAUSE_ASYM (1 << 11) {
+            public String toString() {
+                return "pause-asym (0x800)";
+            }
+        };
+
+        protected int value;
+
+        private OFPortFeatures(int value) {
+            this.value = value;
+        }
+
+        /**
+         * @return the value
+         */
+        public int getValue() {
+            return value;
+        }
+    }
+
+    protected short portNumber;
+    protected byte[] hardwareAddress;
+    protected String name;
+    protected int config;
+    protected int state;
+    protected int currentFeatures;
+    protected int advertisedFeatures;
+    protected int supportedFeatures;
+    protected int peerFeatures;
+
+    /**
+     * @return the portNumber
+     */
+    @JsonSerialize(using=UShortSerializer.class)
+    public short getPortNumber() {
+        return portNumber;
+    }
+
+    /**
+     * @param portNumber the portNumber to set
+     */
+    public void setPortNumber(short portNumber) {
+        this.portNumber = portNumber;
+    }
+
+    /**
+     * @return the hardwareAddress
+     */
+    @JsonSerialize(using=ByteArrayMACSerializer.class)
+    public byte[] getHardwareAddress() {
+        return hardwareAddress;
+    }
+
+    /**
+     * @param hardwareAddress the hardwareAddress to set
+     */
+    public void setHardwareAddress(byte[] hardwareAddress) {
+        if (hardwareAddress.length != OFP_ETH_ALEN)
+            throw new RuntimeException("Hardware address must have length "
+                    + OFP_ETH_ALEN);
+        this.hardwareAddress = hardwareAddress;
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return the config
+     */
+    public int getConfig() {
+        return config;
+    }
+
+    /**
+     * @param config the config to set
+     */
+    public void setConfig(int config) {
+        this.config = config;
+    }
+
+    /**
+     * @return the state
+     */
+    public int getState() {
+        return state;
+    }
+
+    /**
+     * @param state the state to set
+     */
+    public void setState(int state) {
+        this.state = state;
+    }
+
+    /**
+     * @return the currentFeatures
+     */
+    public int getCurrentFeatures() {
+        return currentFeatures;
+    }
+
+    /**
+     * @param currentFeatures the currentFeatures to set
+     */
+    public void setCurrentFeatures(int currentFeatures) {
+        this.currentFeatures = currentFeatures;
+    }
+
+    /**
+     * @return the advertisedFeatures
+     */
+    public int getAdvertisedFeatures() {
+        return advertisedFeatures;
+    }
+
+    /**
+     * @param advertisedFeatures the advertisedFeatures to set
+     */
+    public void setAdvertisedFeatures(int advertisedFeatures) {
+        this.advertisedFeatures = advertisedFeatures;
+    }
+
+    /**
+     * @return the supportedFeatures
+     */
+    public int getSupportedFeatures() {
+        return supportedFeatures;
+    }
+
+    /**
+     * @param supportedFeatures the supportedFeatures to set
+     */
+    public void setSupportedFeatures(int supportedFeatures) {
+        this.supportedFeatures = supportedFeatures;
+    }
+
+    /**
+     * @return the peerFeatures
+     */
+    public int getPeerFeatures() {
+        return peerFeatures;
+    }
+
+    /**
+     * @param peerFeatures the peerFeatures to set
+     */
+    public void setPeerFeatures(int peerFeatures) {
+        this.peerFeatures = peerFeatures;
+    }
+
+    /**
+     * Read this message off the wire from the specified ByteBuffer
+     * @param data
+     */
+    public void readFrom(ChannelBuffer data) {
+        this.portNumber = data.readShort();
+        if (this.hardwareAddress == null)
+            this.hardwareAddress = new byte[OFP_ETH_ALEN];
+        data.readBytes(this.hardwareAddress);
+        byte[] name = new byte[16];
+        data.readBytes(name);
+        // find the first index of 0
+        int index = 0;
+        for (byte b : name) {
+            if (0 == b)
+                break;
+            ++index;
+        }
+        this.name = new String(Arrays.copyOf(name, index),
+                Charset.forName("ascii"));
+        this.config = data.readInt();
+        this.state = data.readInt();
+        this.currentFeatures = data.readInt();
+        this.advertisedFeatures = data.readInt();
+        this.supportedFeatures = data.readInt();
+        this.peerFeatures = data.readInt();
+    }
+
+    /**
+     * Write this message's binary format to the specified ByteBuffer
+     * @param data
+     */
+    public void writeTo(ChannelBuffer data) {
+        data.writeShort(this.portNumber);
+        data.writeBytes(hardwareAddress);
+        try {
+            byte[] name = this.name.getBytes("ASCII");
+            if (name.length < 16) {
+                data.writeBytes(name);
+                for (int i = name.length; i < 16; ++i) {
+                    data.writeByte((byte) 0);
+                }
+            } else {
+                data.writeBytes(name, 0, 15);
+                data.writeByte((byte) 0);
+            }
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        data.writeInt(this.config);
+        data.writeInt(this.state);
+        data.writeInt(this.currentFeatures);
+        data.writeInt(this.advertisedFeatures);
+        data.writeInt(this.supportedFeatures);
+        data.writeInt(this.peerFeatures);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 307;
+        int result = 1;
+        result = prime * result + advertisedFeatures;
+        result = prime * result + config;
+        result = prime * result + currentFeatures;
+        result = prime * result + Arrays.hashCode(hardwareAddress);
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + peerFeatures;
+        result = prime * result + portNumber;
+        result = prime * result + state;
+        result = prime * result + supportedFeatures;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFPhysicalPort)) {
+            return false;
+        }
+        OFPhysicalPort other = (OFPhysicalPort) obj;
+        if (advertisedFeatures != other.advertisedFeatures) {
+            return false;
+        }
+        if (config != other.config) {
+            return false;
+        }
+        if (currentFeatures != other.currentFeatures) {
+            return false;
+        }
+        if (!Arrays.equals(hardwareAddress, other.hardwareAddress)) {
+            return false;
+        }
+        if (name == null) {
+            if (other.name != null) {
+                return false;
+            }
+        } else if (!name.equals(other.name)) {
+            return false;
+        }
+        if (peerFeatures != other.peerFeatures) {
+            return false;
+        }
+        if (portNumber != other.portNumber) {
+            return false;
+        }
+        if (state != other.state) {
+            return false;
+        }
+        if (supportedFeatures != other.supportedFeatures) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPort.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPort.java
new file mode 100644
index 0000000..93301bc
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPort.java
@@ -0,0 +1,43 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+public enum OFPort {
+    OFPP_MAX                ((short)0xff00),
+    OFPP_IN_PORT            ((short)0xfff8),
+    OFPP_TABLE              ((short)0xfff9),
+    OFPP_NORMAL             ((short)0xfffa),
+    OFPP_FLOOD              ((short)0xfffb),
+    OFPP_ALL                ((short)0xfffc),
+    OFPP_CONTROLLER         ((short)0xfffd),
+    OFPP_LOCAL              ((short)0xfffe),
+    OFPP_NONE               ((short)0xffff);
+
+    protected short value;
+
+    private OFPort(short value) {
+        this.value = value;
+    }
+
+    /**
+     * @return the value
+     */
+    public short getValue() {
+        return value;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortMod.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortMod.java
new file mode 100644
index 0000000..876e856
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortMod.java
@@ -0,0 +1,182 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.Arrays;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_port_mod message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFPortMod extends OFMessage {
+    public static int MINIMUM_LENGTH = 32;
+
+    protected short portNumber;
+    protected byte[] hardwareAddress;
+    protected int config;
+    protected int mask;
+    protected int advertise;
+
+    public OFPortMod() {
+        super();
+        this.type = OFType.PORT_MOD;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    /**
+     * @return the portNumber
+     */
+    public short getPortNumber() {
+        return portNumber;
+    }
+
+    /**
+     * @param portNumber the portNumber to set
+     */
+    public void setPortNumber(short portNumber) {
+        this.portNumber = portNumber;
+    }
+
+    /**
+     * @return the hardwareAddress
+     */
+    public byte[] getHardwareAddress() {
+        return hardwareAddress;
+    }
+
+    /**
+     * @param hardwareAddress the hardwareAddress to set
+     */
+    public void setHardwareAddress(byte[] hardwareAddress) {
+        if (hardwareAddress.length != OFPhysicalPort.OFP_ETH_ALEN)
+            throw new RuntimeException("Hardware address must have length "
+                    + OFPhysicalPort.OFP_ETH_ALEN);
+        this.hardwareAddress = hardwareAddress;
+    }
+
+    /**
+     * @return the config
+     */
+    public int getConfig() {
+        return config;
+    }
+
+    /**
+     * @param config the config to set
+     */
+    public void setConfig(int config) {
+        this.config = config;
+    }
+
+    /**
+     * @return the mask
+     */
+    public int getMask() {
+        return mask;
+    }
+
+    /**
+     * @param mask the mask to set
+     */
+    public void setMask(int mask) {
+        this.mask = mask;
+    }
+
+    /**
+     * @return the advertise
+     */
+    public int getAdvertise() {
+        return advertise;
+    }
+
+    /**
+     * @param advertise the advertise to set
+     */
+    public void setAdvertise(int advertise) {
+        this.advertise = advertise;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.portNumber = data.readShort();
+        if (this.hardwareAddress == null)
+            this.hardwareAddress = new byte[OFPhysicalPort.OFP_ETH_ALEN];
+        data.readBytes(this.hardwareAddress);
+        this.config = data.readInt();
+        this.mask = data.readInt();
+        this.advertise = data.readInt();
+        data.readInt(); // pad
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeShort(this.portNumber);
+        data.writeBytes(this.hardwareAddress);
+        data.writeInt(this.config);
+        data.writeInt(this.mask);
+        data.writeInt(this.advertise);
+        data.writeInt(0); // pad
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 311;
+        int result = super.hashCode();
+        result = prime * result + advertise;
+        result = prime * result + config;
+        result = prime * result + Arrays.hashCode(hardwareAddress);
+        result = prime * result + mask;
+        result = prime * result + portNumber;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFPortMod)) {
+            return false;
+        }
+        OFPortMod other = (OFPortMod) obj;
+        if (advertise != other.advertise) {
+            return false;
+        }
+        if (config != other.config) {
+            return false;
+        }
+        if (!Arrays.equals(hardwareAddress, other.hardwareAddress)) {
+            return false;
+        }
+        if (mask != other.mask) {
+            return false;
+        }
+        if (portNumber != other.portNumber) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortStatus.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortStatus.java
new file mode 100644
index 0000000..8bde6e7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortStatus.java
@@ -0,0 +1,126 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_port_status message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFPortStatus extends OFMessage {
+    public static int MINIMUM_LENGTH = 64;
+
+    public enum OFPortReason {
+        OFPPR_ADD,
+        OFPPR_DELETE,
+        OFPPR_MODIFY
+    }
+
+    protected byte reason;
+    protected OFPhysicalPort desc;
+
+    /**
+     * @return the reason
+     */
+    public byte getReason() {
+        return reason;
+    }
+
+    /**
+     * @param reason the reason to set
+     */
+    public void setReason(byte reason) {
+        this.reason = reason;
+    }
+
+    /**
+     * @return the desc
+     */
+    public OFPhysicalPort getDesc() {
+        return desc;
+    }
+
+    /**
+     * @param desc the desc to set
+     */
+    public void setDesc(OFPhysicalPort desc) {
+        this.desc = desc;
+    }
+
+    public OFPortStatus() {
+        super();
+        this.type = OFType.PORT_STATUS;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.reason = data.readByte();
+        data.readerIndex(data.readerIndex() + 7); // skip 7 bytes of padding
+        if (this.desc == null)
+            this.desc = new OFPhysicalPort();
+        this.desc.readFrom(data);
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeByte(this.reason);
+        for (int i = 0; i < 7; ++i)
+            data.writeByte((byte) 0);
+        this.desc.writeTo(data);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 313;
+        int result = super.hashCode();
+        result = prime * result + ((desc == null) ? 0 : desc.hashCode());
+        result = prime * result + reason;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFPortStatus)) {
+            return false;
+        }
+        OFPortStatus other = (OFPortStatus) obj;
+        if (desc == null) {
+            if (other.desc != null) {
+                return false;
+            }
+        } else if (!desc.equals(other.desc)) {
+            return false;
+        }
+        if (reason != other.reason) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSetConfig.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSetConfig.java
new file mode 100644
index 0000000..4b23564
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSetConfig.java
@@ -0,0 +1,29 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+/**
+ * Represents an OFPT_SET_CONFIG type message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFSetConfig extends OFSwitchConfig {
+    public OFSetConfig() {
+        super();
+        this.type = OFType.SET_CONFIG;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java
new file mode 100644
index 0000000..cf6ace3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java
@@ -0,0 +1,157 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.factory.OFStatisticsFactory;
+import org.openflow.protocol.factory.OFStatisticsFactoryAware;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+
+
+/**
+ * Base class for statistics requests/replies
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 27, 2010
+ */
+public abstract class OFStatisticsMessageBase extends OFMessage implements
+        OFStatisticsFactoryAware {
+    public static int MINIMUM_LENGTH = 12;
+
+    protected OFStatisticsFactory statisticsFactory;
+    protected OFStatisticsType statisticType;
+    protected short flags;
+    protected List<OFStatistics> statistics;
+
+    /**
+     * @return the statisticType
+     */
+    public OFStatisticsType getStatisticType() {
+        return statisticType;
+    }
+
+    /**
+     * @param statisticType the statisticType to set
+     */
+    public void setStatisticType(OFStatisticsType statisticType) {
+        this.statisticType = statisticType;
+    }
+
+    /**
+     * @return the flags
+     */
+    public short getFlags() {
+        return flags;
+    }
+
+    /**
+     * @param flags the flags to set
+     */
+    public void setFlags(short flags) {
+        this.flags = flags;
+    }
+
+    /**
+     * @return the statistics
+     */
+    public List<OFStatistics> getStatistics() {
+        return statistics;
+    }
+
+    /**
+     * @param statistics the statistics to set
+     */
+    public void setStatistics(List<OFStatistics> statistics) {
+        this.statistics = statistics;
+    }
+
+    @Override
+    public void setStatisticsFactory(OFStatisticsFactory statisticsFactory) {
+        this.statisticsFactory = statisticsFactory;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.statisticType = OFStatisticsType.valueOf(data.readShort(), this
+                .getType());
+        this.flags = data.readShort();
+        if (this.statisticsFactory == null)
+            throw new RuntimeException("OFStatisticsFactory not set");
+        this.statistics = statisticsFactory.parseStatistics(this.getType(),
+                this.statisticType, data, super.getLengthU() - MINIMUM_LENGTH);
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeShort(this.statisticType.getTypeValue());
+        data.writeShort(this.flags);
+        if (this.statistics != null) {
+            for (OFStatistics statistic : this.statistics) {
+                statistic.writeTo(data);
+            }
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 317;
+        int result = super.hashCode();
+        result = prime * result + flags;
+        result = prime * result
+                + ((statisticType == null) ? 0 : statisticType.hashCode());
+        result = prime * result
+                + ((statistics == null) ? 0 : statistics.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFStatisticsMessageBase)) {
+            return false;
+        }
+        OFStatisticsMessageBase other = (OFStatisticsMessageBase) obj;
+        if (flags != other.flags) {
+            return false;
+        }
+        if (statisticType == null) {
+            if (other.statisticType != null) {
+                return false;
+            }
+        } else if (!statisticType.equals(other.statisticType)) {
+            return false;
+        }
+        if (statistics == null) {
+            if (other.statistics != null) {
+                return false;
+            }
+        } else if (!statistics.equals(other.statistics)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsReply.java
new file mode 100644
index 0000000..ddc7267
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsReply.java
@@ -0,0 +1,46 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_stats_reply message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFStatisticsReply extends OFStatisticsMessageBase {
+    public enum OFStatisticsReplyFlags {
+        REPLY_MORE      (1 << 0);
+
+        protected short type;
+
+        OFStatisticsReplyFlags(int type) {
+            this.type = (short) type;
+        }
+
+        public short getTypeValue() {
+            return type;
+        }
+    }
+
+    public OFStatisticsReply() {
+        super();
+        this.type = OFType.STATS_REPLY;
+        this.length = U16.t(OFStatisticsMessageBase.MINIMUM_LENGTH);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsRequest.java
new file mode 100644
index 0000000..d1d8010
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsRequest.java
@@ -0,0 +1,32 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_stats_request message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFStatisticsRequest extends OFStatisticsMessageBase {
+    public OFStatisticsRequest() {
+        super();
+        this.type = OFType.STATS_REQUEST;
+        this.length = U16.t(OFStatisticsMessageBase.MINIMUM_LENGTH);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSwitchConfig.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSwitchConfig.java
new file mode 100644
index 0000000..e04e3fa
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSwitchConfig.java
@@ -0,0 +1,118 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Base class representing ofp_switch_config based messages
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public abstract class OFSwitchConfig extends OFMessage {
+    public static int MINIMUM_LENGTH = 12;
+
+    public enum OFConfigFlags {
+        OFPC_FRAG_NORMAL,
+        OFPC_FRAG_DROP,
+        OFPC_FRAG_REASM,
+        OFPC_FRAG_MASK
+    }
+
+    protected short flags;
+    protected short missSendLength;
+
+    public OFSwitchConfig() {
+        super();
+        super.setLengthU(MINIMUM_LENGTH);
+    }
+
+    /**
+     * @return the flags
+     */
+    public short getFlags() {
+        return flags;
+    }
+
+    /**
+     * @param flags the flags to set
+     */
+    public OFSwitchConfig setFlags(short flags) {
+        this.flags = flags;
+        return this;
+    }
+
+    /**
+     * @return the missSendLength
+     */
+    public short getMissSendLength() {
+        return missSendLength;
+    }
+
+    /**
+     * @param missSendLength the missSendLength to set
+     */
+    public OFSwitchConfig setMissSendLength(short missSendLength) {
+        this.missSendLength = missSendLength;
+        return this;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.flags = data.readShort();
+        this.missSendLength = data.readShort();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeShort(this.flags);
+        data.writeShort(this.missSendLength);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 331;
+        int result = super.hashCode();
+        result = prime * result + flags;
+        result = prime * result + missSendLength;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFSwitchConfig)) {
+            return false;
+        }
+        OFSwitchConfig other = (OFSwitchConfig) obj;
+        if (flags != other.flags) {
+            return false;
+        }
+        if (missSendLength != other.missSendLength) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFType.java
new file mode 100644
index 0000000..c828f0a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFType.java
@@ -0,0 +1,239 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * List of OpenFlow types and mappings to wire protocol value and derived
+ * classes
+ *
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public enum OFType {
+    HELLO               (0, OFHello.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFHello();
+                            }}),
+    ERROR               (1, OFError.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFError();
+                            }}),
+    ECHO_REQUEST        (2, OFEchoRequest.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFEchoRequest();
+                            }}),
+    ECHO_REPLY          (3, OFEchoReply.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFEchoReply();
+                            }}),
+    VENDOR              (4, OFVendor.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFVendor();
+                            }}),
+    FEATURES_REQUEST    (5, OFFeaturesRequest.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFFeaturesRequest();
+                            }}),
+    FEATURES_REPLY      (6, OFFeaturesReply.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFFeaturesReply();
+                            }}),
+    GET_CONFIG_REQUEST  (7, OFGetConfigRequest.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFGetConfigRequest();
+                            }}),
+    GET_CONFIG_REPLY    (8, OFGetConfigReply.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFGetConfigReply();
+                            }}),
+    SET_CONFIG          (9, OFSetConfig.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFSetConfig();
+                            }}),
+    PACKET_IN           (10, OFPacketIn.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFPacketIn();
+                            }}),
+    FLOW_REMOVED        (11, OFFlowRemoved.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFFlowRemoved();
+                            }}),
+    PORT_STATUS         (12, OFPortStatus.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFPortStatus();
+                            }}),
+    PACKET_OUT          (13, OFPacketOut.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFPacketOut();
+                            }}),
+    FLOW_MOD            (14, OFFlowMod.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFFlowMod();
+                            }}),
+    PORT_MOD            (15, OFPortMod.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFPortMod();
+                            }}),
+    STATS_REQUEST       (16, OFStatisticsRequest.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFStatisticsRequest();
+                            }}),
+    STATS_REPLY         (17, OFStatisticsReply.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFStatisticsReply();
+                            }}),
+    BARRIER_REQUEST     (18, OFBarrierRequest.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFBarrierRequest();
+                            }}),
+    BARRIER_REPLY       (19, OFBarrierReply.class, new Instantiable<OFMessage>() {
+                            @Override
+                            public OFMessage instantiate() {
+                                return new OFBarrierReply();
+                            }});
+
+    static OFType[] mapping;
+
+    protected Class<? extends OFMessage> clazz;
+    protected Constructor<? extends OFMessage> constructor;
+    protected Instantiable<OFMessage> instantiable;
+    protected byte type;
+
+    /**
+     * Store some information about the OpenFlow type, including wire protocol
+     * type number, length, and derived class
+     *
+     * @param type Wire protocol number associated with this OFType
+     * @param clazz The Java class corresponding to this type of OpenFlow
+     *              message
+     * @param instantiator An Instantiator<OFMessage> implementation that creates an
+     *          instance of the specified OFMessage
+     */
+    OFType(int type, Class<? extends OFMessage> clazz, Instantiable<OFMessage> instantiator) {
+        this.type = (byte) type;
+        this.clazz = clazz;
+        this.instantiable = instantiator;
+        try {
+            this.constructor = clazz.getConstructor(new Class[]{});
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Failure getting constructor for class: " + clazz, e);
+        }
+        OFType.addMapping(this.type, this);
+    }
+
+    /**
+     * Adds a mapping from type value to OFType enum
+     *
+     * @param i OpenFlow wire protocol type
+     * @param t type
+     */
+    static public void addMapping(byte i, OFType t) {
+        if (mapping == null)
+            mapping = new OFType[32];
+        OFType.mapping[i] = t;
+    }
+
+    /**
+     * Remove a mapping from type value to OFType enum
+     *
+     * @param i OpenFlow wire protocol type
+     */
+    static public void removeMapping(byte i) {
+        OFType.mapping[i] = null;
+    }
+
+    /**
+     * Given a wire protocol OpenFlow type number, return the OFType associated
+     * with it
+     *
+     * @param i wire protocol number
+     * @return OFType enum type
+     */
+
+    static public OFType valueOf(Byte i) {
+        return OFType.mapping[i];
+    }
+
+    /**
+     * @return Returns the wire protocol value corresponding to this OFType
+     */
+    public byte getTypeValue() {
+        return this.type;
+    }
+
+    /**
+     * @return return the OFMessage subclass corresponding to this OFType
+     */
+    public Class<? extends OFMessage> toClass() {
+        return clazz;
+    }
+
+    /**
+     * Returns the no-argument Constructor of the implementation class for
+     * this OFType
+     * @return the constructor
+     */
+    public Constructor<? extends OFMessage> getConstructor() {
+        return constructor;
+    }
+
+    /**
+     * Returns a new instance of the OFMessage represented by this OFType
+     * @return the new object
+     */
+    public OFMessage newInstance() {
+        return instantiable.instantiate();
+    }
+
+    /**
+     * @return the instantiable
+     */
+    public Instantiable<OFMessage> getInstantiable() {
+        return instantiable;
+    }
+
+    /**
+     * @param instantiable the instantiable to set
+     */
+    public void setInstantiable(Instantiable<OFMessage> instantiable) {
+        this.instantiable = instantiable;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFVendor.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFVendor.java
new file mode 100644
index 0000000..8ecb862
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFVendor.java
@@ -0,0 +1,131 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+import org.openflow.protocol.factory.OFVendorDataFactory;
+import org.openflow.protocol.factory.OFVendorDataFactoryAware;
+import org.openflow.protocol.vendor.OFVendorData;
+
+/**
+ * Represents ofp_vendor_header
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFVendor extends OFMessage implements OFVendorDataFactoryAware {
+    public static int MINIMUM_LENGTH = 12;
+
+    protected int vendor;
+    protected OFVendorData vendorData;
+    protected OFVendorDataFactory vendorDataFactory;
+
+    public OFVendor() {
+        super();
+        this.type = OFType.VENDOR;
+        this.length = U16.t(MINIMUM_LENGTH);
+    }
+
+    /**
+     * @return the vendor
+     */
+    public int getVendor() {
+        return vendor;
+    }
+
+    /**
+     * @param vendor the vendor to set
+     */
+    public void setVendor(int vendor) {
+        this.vendor = vendor;
+    }
+
+    /**
+     * @return the data
+     */
+    public OFVendorData getVendorData() {
+        return vendorData;
+    }
+
+    /**
+     * @param data the data to set
+     */
+    public void setVendorData(OFVendorData vendorData) {
+        this.vendorData = vendorData;
+    }
+
+    @Override
+    public void setVendorDataFactory(OFVendorDataFactory vendorDataFactory) {
+        this.vendorDataFactory = vendorDataFactory;
+    }
+      
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.vendor = data.readInt();
+        if (vendorDataFactory == null)
+            throw new RuntimeException("OFVendorDataFactory not set");
+            
+        this.vendorData = vendorDataFactory.parseVendorData(vendor,
+                data, super.getLengthU() - MINIMUM_LENGTH);
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeInt(this.vendor);
+        if (vendorData != null)
+            vendorData.writeTo(data);
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 337;
+        int result = super.hashCode();
+        result = prime * result + vendor;
+        if (vendorData != null)
+            result = prime * result + vendorData.hashCode();
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        OFVendor other = (OFVendor) obj;
+        if (vendor != other.vendor)
+            return false;
+        if (vendorData == null) {
+            if (other.vendorData != null) {
+                return false;
+            }
+        } else if (!vendorData.equals(other.vendorData)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFAction.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFAction.java
new file mode 100644
index 0000000..57b5dc1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFAction.java
@@ -0,0 +1,173 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * The base class for all OpenFlow Actions.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFAction implements Cloneable {
+    /**
+     * Note the true minimum length for this header is 8 including a pad to 64
+     * bit alignment, however as this base class is used for demuxing an
+     * incoming Action, it is only necessary to read the first 4 bytes.  All
+     * Actions extending this class are responsible for reading/writing the
+     * first 8 bytes, including the pad if necessary.
+     */
+    public static int MINIMUM_LENGTH = 4;
+    public static int OFFSET_LENGTH = 2;
+    public static int OFFSET_TYPE = 0;
+
+    protected OFActionType type;
+    protected short length;
+
+    /**
+     * Get the length of this message
+     *
+     * @return
+     */
+    public short getLength() {
+        return length;
+    }
+
+    /**
+     * Get the length of this message, unsigned
+     *
+     * @return
+     */
+    public int getLengthU() {
+        return U16.f(length);
+    }
+
+    /**
+     * Set the length of this message
+     *
+     * @param length
+     */
+    public OFAction setLength(short length) {
+        this.length = length;
+        return this;
+    }
+
+    /**
+     * Get the type of this message
+     *
+     * @return OFActionType enum
+     */
+    public OFActionType getType() {
+        return this.type;
+    }
+
+    /**
+     * Set the type of this message
+     *
+     * @param type
+     */
+    public void setType(OFActionType type) {
+        this.type = type;
+    }
+
+    /**
+     * Returns a summary of the message
+     * @return "ofmsg=v=$version;t=$type:l=$len:xid=$xid"
+     */
+    public String toString() {
+        return "ofaction" +
+            ";t=" + this.getType() +
+            ";l=" + this.getLength();
+    }
+    
+    /**
+     * Given the output from toString(), 
+     * create a new OFAction
+     * @param val
+     * @return
+     */
+    public static OFAction fromString(String val) {
+        String tokens[] = val.split(";");
+        if (!tokens[0].equals("ofaction"))
+            throw new IllegalArgumentException("expected 'ofaction' but got '" + 
+                    tokens[0] + "'");
+        String type_tokens[] = tokens[1].split("="); 
+        String len_tokens[] = tokens[2].split("=");
+        OFAction action = new OFAction();
+        action.setLength(Short.valueOf(len_tokens[1]));
+        action.setType(OFActionType.valueOf(type_tokens[1]));
+        return action;
+    }
+
+    public void readFrom(ChannelBuffer data) {
+        this.type = OFActionType.valueOf(data.readShort());
+        this.length = data.readShort();
+        // Note missing PAD, see MINIMUM_LENGTH comment for details
+    }
+
+    public void writeTo(ChannelBuffer data) {
+        data.writeShort(type.getTypeValue());
+        data.writeShort(length);
+        // Note missing PAD, see MINIMUM_LENGTH comment for details
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 347;
+        int result = 1;
+        result = prime * result + length;
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFAction)) {
+            return false;
+        }
+        OFAction other = (OFAction) obj;
+        if (length != other.length) {
+            return false;
+        }
+        if (type == null) {
+            if (other.type != null) {
+                return false;
+            }
+        } else if (!type.equals(other.type)) {
+            return false;
+        }
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public OFAction clone() throws CloneNotSupportedException {
+        return (OFAction) super.clone();
+    }
+    
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java
new file mode 100644
index 0000000..6832728
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java
@@ -0,0 +1,98 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+import java.util.Arrays;
+
+import net.floodlightcontroller.core.web.serializers.ByteArrayMACSerializer;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFPhysicalPort;
+
+/**
+ * Represents an ofp_action_dl_addr
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public abstract class OFActionDataLayer extends OFAction {
+    public static int MINIMUM_LENGTH = 16;
+
+    protected byte[] dataLayerAddress;
+
+    /**
+     * @return the dataLayerAddress
+     */
+    @JsonSerialize(using=ByteArrayMACSerializer.class)
+    public byte[] getDataLayerAddress() {
+        return dataLayerAddress;
+    }
+
+    /**
+     * @param dataLayerAddress the dataLayerAddress to set
+     */
+    public void setDataLayerAddress(byte[] dataLayerAddress) {
+        this.dataLayerAddress = dataLayerAddress;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        if (this.dataLayerAddress == null)
+            this.dataLayerAddress = new byte[OFPhysicalPort.OFP_ETH_ALEN];
+        data.readBytes(this.dataLayerAddress);
+        data.readInt();
+        data.readShort();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeBytes(this.dataLayerAddress);
+        data.writeInt(0);
+        data.writeShort((short) 0);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 347;
+        int result = super.hashCode();
+        result = prime * result + Arrays.hashCode(dataLayerAddress);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFActionDataLayer)) {
+            return false;
+        }
+        OFActionDataLayer other = (OFActionDataLayer) obj;
+        if (!Arrays.equals(dataLayerAddress, other.dataLayerAddress)) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerDestination.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerDestination.java
new file mode 100644
index 0000000..48b8d0f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerDestination.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionDataLayerDestination extends OFActionDataLayer {
+    public OFActionDataLayerDestination() {
+        super();
+        super.setType(OFActionType.SET_DL_DST);
+        super.setLength((short) OFActionDataLayer.MINIMUM_LENGTH);
+    }
+    
+    public OFActionDataLayerDestination(byte[] address) {
+        this();
+        this.dataLayerAddress = address;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerSource.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerSource.java
new file mode 100644
index 0000000..e04561c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerSource.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionDataLayerSource extends OFActionDataLayer {
+    public OFActionDataLayerSource() {
+        super();
+        super.setType(OFActionType.SET_DL_SRC);
+        super.setLength((short) OFActionDataLayer.MINIMUM_LENGTH);
+    }
+    
+    public OFActionDataLayerSource(byte[] address) {
+        this();
+        this.dataLayerAddress = address;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionEnqueue.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionEnqueue.java
new file mode 100644
index 0000000..0ec2fa3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionEnqueue.java
@@ -0,0 +1,124 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_enqueue
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionEnqueue extends OFAction {
+    public static int MINIMUM_LENGTH = 16;
+
+    protected short port;
+    protected int queueId;
+
+    public OFActionEnqueue() {
+        super.setType(OFActionType.OPAQUE_ENQUEUE);
+        super.setLength((short) MINIMUM_LENGTH);
+    }
+    
+    public OFActionEnqueue(short port, int queueId) {
+        this();
+        this.port = port;
+        this.queueId = queueId;
+    }
+
+    /**
+     * Get the output port
+     * @return
+     */
+    public short getPort() {
+        return this.port;
+    }
+
+    /**
+     * Set the output port
+     * @param port
+     */
+    public void setPort(short port) {
+        this.port = port;
+    }
+
+    /**
+     * @return the queueId
+     */
+    public int getQueueId() {
+        return queueId;
+    }
+
+    /**
+     * @param queueId the queueId to set
+     */
+    public void setQueueId(int queueId) {
+        this.queueId = queueId;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.port = data.readShort();
+        data.readShort();
+        data.readInt();
+        this.queueId = data.readInt();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeShort(this.port);
+        data.writeShort((short) 0);
+        data.writeInt(0);
+        data.writeInt(this.queueId);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 349;
+        int result = super.hashCode();
+        result = prime * result + port;
+        result = prime * result + queueId;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFActionEnqueue)) {
+            return false;
+        }
+        OFActionEnqueue other = (OFActionEnqueue) obj;
+        if (port != other.port) {
+            return false;
+        }
+        if (queueId != other.queueId) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerAddress.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerAddress.java
new file mode 100644
index 0000000..dc65ae9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerAddress.java
@@ -0,0 +1,86 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_nw_addr
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public abstract class OFActionNetworkLayerAddress extends OFAction {
+    public static int MINIMUM_LENGTH = 8;
+
+    protected int networkAddress;
+
+    /**
+     * @return the networkAddress
+     */
+    public int getNetworkAddress() {
+        return networkAddress;
+    }
+
+    /**
+     * @param networkAddress the networkAddress to set
+     */
+    public void setNetworkAddress(int networkAddress) {
+        this.networkAddress = networkAddress;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.networkAddress = data.readInt();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeInt(this.networkAddress);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 353;
+        int result = super.hashCode();
+        result = prime * result + networkAddress;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFActionNetworkLayerAddress)) {
+            return false;
+        }
+        OFActionNetworkLayerAddress other = (OFActionNetworkLayerAddress) obj;
+        if (networkAddress != other.networkAddress) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerDestination.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerDestination.java
new file mode 100644
index 0000000..13c14ff
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerDestination.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionNetworkLayerDestination extends OFActionNetworkLayerAddress {
+    public OFActionNetworkLayerDestination() {
+        super();
+        super.setType(OFActionType.SET_NW_DST);
+        super.setLength((short) OFActionNetworkLayerAddress.MINIMUM_LENGTH);
+    }
+    
+    public OFActionNetworkLayerDestination(int ip) {
+        this();
+        this.networkAddress = ip;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerSource.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerSource.java
new file mode 100644
index 0000000..ef1d005
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerSource.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionNetworkLayerSource extends OFActionNetworkLayerAddress {
+    public OFActionNetworkLayerSource() {
+        super();
+        super.setType(OFActionType.SET_NW_SRC);
+        super.setLength((short) OFActionNetworkLayerAddress.MINIMUM_LENGTH);
+    }
+    
+    public OFActionNetworkLayerSource(int ip) {
+        this();
+        this.networkAddress = ip;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkTypeOfService.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkTypeOfService.java
new file mode 100644
index 0000000..0d38180
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkTypeOfService.java
@@ -0,0 +1,101 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_enqueue
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionNetworkTypeOfService extends OFAction {
+    public static int MINIMUM_LENGTH = 8;
+
+    protected byte networkTypeOfService;
+
+    public OFActionNetworkTypeOfService() {
+        super.setType(OFActionType.SET_NW_TOS);
+        super.setLength((short) MINIMUM_LENGTH);
+    }
+    
+    public OFActionNetworkTypeOfService(byte tos) {
+        this();
+        this.networkTypeOfService = tos;
+    }
+    
+
+    /**
+     * @return the networkTypeOfService
+     */
+    public byte getNetworkTypeOfService() {
+        return networkTypeOfService;
+    }
+
+    /**
+     * @param networkTypeOfService the networkTypeOfService to set
+     */
+    public void setNetworkTypeOfService(byte networkTypeOfService) {
+        this.networkTypeOfService = networkTypeOfService;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.networkTypeOfService = data.readByte();
+        data.readShort();
+        data.readByte();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeByte(this.networkTypeOfService);
+        data.writeShort((short) 0);
+        data.writeByte((byte) 0);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 359;
+        int result = super.hashCode();
+        result = prime * result + networkTypeOfService;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFActionNetworkTypeOfService)) {
+            return false;
+        }
+        OFActionNetworkTypeOfService other = (OFActionNetworkTypeOfService) obj;
+        if (networkTypeOfService != other.networkTypeOfService) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionOutput.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionOutput.java
new file mode 100644
index 0000000..b9521d0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionOutput.java
@@ -0,0 +1,158 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ */
+public class OFActionOutput extends OFAction implements Cloneable {
+    public static int MINIMUM_LENGTH = 8;
+
+    protected short port;
+    protected short maxLength;
+
+    public OFActionOutput() {
+        super.setType(OFActionType.OUTPUT);
+        super.setLength((short) MINIMUM_LENGTH);
+    }
+
+    /**
+     * Create an Output Action sending packets out the specified
+     * OpenFlow port.
+     *
+     * This is the most common creation pattern for OFActions.
+     *
+     * @param port
+     */
+
+    public OFActionOutput(short port) {
+        this(port, (short) 65535);
+    }
+
+    /**
+     * Create an Output Action specifying both the port AND
+     * the snaplen of the packet to send out that port.
+     * The length field is only meaningful when port == OFPort.OFPP_CONTROLLER
+     * @param port
+     * @param maxLength The maximum number of bytes of the packet to send.
+     * Most hardware only supports this value for OFPP_CONTROLLER
+     */
+
+    public OFActionOutput(short port, short maxLength) {
+        super();
+        super.setType(OFActionType.OUTPUT);
+        super.setLength((short) MINIMUM_LENGTH);
+        this.port = port;
+        this.maxLength = maxLength;
+    }
+
+    /**
+     * Get the output port
+     * @return
+     */
+    public short getPort() {
+        return this.port;
+    }
+
+    /**
+     * Set the output port
+     * @param port
+     */
+    public OFActionOutput setPort(short port) {
+        this.port = port;
+        return this;
+    }
+
+    /**
+     * Get the max length to send to the controller
+     * @return
+     */
+    public short getMaxLength() {
+        return this.maxLength;
+    }
+
+    /**
+     * Set the max length to send to the controller
+     * @param maxLength
+     */
+    public OFActionOutput setMaxLength(short maxLength) {
+        this.maxLength = maxLength;
+        return this;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.port = data.readShort();
+        this.maxLength = data.readShort();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeShort(port);
+        data.writeShort(maxLength);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 367;
+        int result = super.hashCode();
+        result = prime * result + maxLength;
+        result = prime * result + port;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFActionOutput)) {
+            return false;
+        }
+        OFActionOutput other = (OFActionOutput) obj;
+        if (maxLength != other.maxLength) {
+            return false;
+        }
+        if (port != other.port) {
+            return false;
+        }
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "OFActionOutput [maxLength=" + maxLength + ", port=" + U16.f(port)
+                + ", length=" + length + ", type=" + type + "]";
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionStripVirtualLan.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionStripVirtualLan.java
new file mode 100644
index 0000000..7d6b849
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionStripVirtualLan.java
@@ -0,0 +1,53 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+
+/**
+ * Represents an ofp_action_strip_vlan
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionStripVirtualLan extends OFAction {
+    public static int MINIMUM_LENGTH = 8;
+
+    public OFActionStripVirtualLan() {
+        super();
+        super.setType(OFActionType.STRIP_VLAN);
+        super.setLength((short) MINIMUM_LENGTH);
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        // PAD
+        data.readInt();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        // PAD
+        data.writeInt(0);
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayer.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayer.java
new file mode 100644
index 0000000..0bc09c9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayer.java
@@ -0,0 +1,88 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_tp_port
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public abstract class OFActionTransportLayer extends OFAction {
+    public static int MINIMUM_LENGTH = 8;
+
+    protected short transportPort;
+
+    /**
+     * @return the transportPort
+     */
+    public short getTransportPort() {
+        return transportPort;
+    }
+
+    /**
+     * @param transportPort the transportPort to set
+     */
+    public void setTransportPort(short transportPort) {
+        this.transportPort = transportPort;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.transportPort = data.readShort();
+        data.readShort();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeShort(this.transportPort);
+        data.writeShort((short) 0);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 373;
+        int result = super.hashCode();
+        result = prime * result + transportPort;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFActionTransportLayer)) {
+            return false;
+        }
+        OFActionTransportLayer other = (OFActionTransportLayer) obj;
+        if (transportPort != other.transportPort) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerDestination.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerDestination.java
new file mode 100644
index 0000000..7e7b0f1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerDestination.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionTransportLayerDestination extends OFActionTransportLayer {
+    public OFActionTransportLayerDestination() {
+        super();
+        super.setType(OFActionType.SET_TP_DST);
+        super.setLength((short) OFActionTransportLayer.MINIMUM_LENGTH);
+    }
+    
+    public OFActionTransportLayerDestination(short port) {
+        this();
+        this.transportPort = port;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerSource.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerSource.java
new file mode 100644
index 0000000..385aa53
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerSource.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionTransportLayerSource extends OFActionTransportLayer {
+    public OFActionTransportLayerSource() {
+        super();
+        super.setType(OFActionType.SET_TP_SRC);
+        super.setLength((short) OFActionTransportLayer.MINIMUM_LENGTH);
+    }
+    
+    public OFActionTransportLayerSource(short port) {
+        this();
+        this.transportPort = port;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionType.java
new file mode 100644
index 0000000..b0c2c47
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionType.java
@@ -0,0 +1,203 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ *
+ */
+package org.openflow.protocol.action;
+
+import java.lang.reflect.Constructor;
+
+import org.openflow.protocol.Instantiable;
+
+/**
+ * List of OpenFlow Action types and mappings to wire protocol value and
+ * derived classes
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public enum OFActionType {
+    OUTPUT              (0, OFActionOutput.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionOutput();
+                            }}),
+    SET_VLAN_ID        (1, OFActionVirtualLanIdentifier.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionVirtualLanIdentifier();
+                            }}),
+    SET_VLAN_PCP        (2, OFActionVirtualLanPriorityCodePoint.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionVirtualLanPriorityCodePoint();
+                            }}),
+    STRIP_VLAN          (3, OFActionStripVirtualLan.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionStripVirtualLan();
+                            }}),
+    SET_DL_SRC          (4, OFActionDataLayerSource.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionDataLayerSource();
+                            }}),
+    SET_DL_DST          (5, OFActionDataLayerDestination.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionDataLayerDestination();
+                            }}),
+    SET_NW_SRC          (6, OFActionNetworkLayerSource.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionNetworkLayerSource();
+                            }}),
+    SET_NW_DST          (7, OFActionNetworkLayerDestination.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionNetworkLayerDestination();
+                            }}),
+    SET_NW_TOS          (8, OFActionNetworkTypeOfService.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionNetworkTypeOfService();
+                            }}),
+    SET_TP_SRC          (9, OFActionTransportLayerSource.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionTransportLayerSource();
+                            }}),
+    SET_TP_DST          (10, OFActionTransportLayerDestination.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionTransportLayerDestination();
+                            }}),
+    OPAQUE_ENQUEUE      (11, OFActionEnqueue.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionEnqueue();
+                            }}),
+    VENDOR              (0xffff, OFActionVendor.class, new Instantiable<OFAction>() {
+                            @Override
+                            public OFAction instantiate() {
+                                return new OFActionVendor();
+                            }});
+
+    protected static OFActionType[] mapping;
+
+    protected Class<? extends OFAction> clazz;
+    protected Constructor<? extends OFAction> constructor;
+    protected Instantiable<OFAction> instantiable;
+    protected int minLen;
+    protected short type;
+
+    /**
+     * Store some information about the OpenFlow Action type, including wire
+     * protocol type number, length, and derrived class
+     *
+     * @param type Wire protocol number associated with this OFType
+     * @param clazz The Java class corresponding to this type of OpenFlow Action
+     * @param instantiable the instantiable for the OFAction this type represents
+     */
+    OFActionType(int type, Class<? extends OFAction> clazz, Instantiable<OFAction> instantiable) {
+        this.type = (short) type;
+        this.clazz = clazz;
+        this.instantiable = instantiable;
+        try {
+            this.constructor = clazz.getConstructor(new Class[]{});
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Failure getting constructor for class: " + clazz, e);
+        }
+        OFActionType.addMapping(this.type, this);
+    }
+
+    /**
+     * Adds a mapping from type value to OFActionType enum
+     *
+     * @param i OpenFlow wire protocol Action type value
+     * @param t type
+     */
+    static public void addMapping(short i, OFActionType t) {
+        if (mapping == null)
+            mapping = new OFActionType[16];
+        // bring higher mappings down to the edge of our array
+        if (i < 0)
+            i = (short) (16 + i);
+        OFActionType.mapping[i] = t;
+    }
+
+    /**
+     * Given a wire protocol OpenFlow type number, return the OFType associated
+     * with it
+     *
+     * @param i wire protocol number
+     * @return OFType enum type
+     */
+
+    static public OFActionType valueOf(short i) {
+        if (i < 0)
+            i = (short) (16+i);
+        return OFActionType.mapping[i];
+    }
+
+    /**
+     * @return Returns the wire protocol value corresponding to this
+     *         OFActionType
+     */
+    public short getTypeValue() {
+        return this.type;
+    }
+
+    /**
+     * @return return the OFAction subclass corresponding to this OFActionType
+     */
+    public Class<? extends OFAction> toClass() {
+        return clazz;
+    }
+
+    /**
+     * Returns the no-argument Constructor of the implementation class for
+     * this OFActionType
+     * @return the constructor
+     */
+    public Constructor<? extends OFAction> getConstructor() {
+        return constructor;
+    }
+
+    /**
+     * Returns a new instance of the OFAction represented by this OFActionType
+     * @return the new object
+     */
+    public OFAction newInstance() {
+        return instantiable.instantiate();
+    }
+
+    /**
+     * @return the instantiable
+     */
+    public Instantiable<OFAction> getInstantiable() {
+        return instantiable;
+    }
+
+    /**
+     * @param instantiable the instantiable to set
+     */
+    public void setInstantiable(Instantiable<OFAction> instantiable) {
+        this.instantiable = instantiable;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVendor.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVendor.java
new file mode 100644
index 0000000..b5a15c2
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVendor.java
@@ -0,0 +1,89 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionVendor extends OFAction {
+    public static int MINIMUM_LENGTH = 8;
+
+    protected int vendor;
+
+    public OFActionVendor() {
+        super();
+        super.setType(OFActionType.VENDOR);
+        super.setLength((short) MINIMUM_LENGTH);
+    }
+
+    /**
+     * @return the vendor
+     */
+    public int getVendor() {
+        return vendor;
+    }
+
+    /**
+     * @param vendor the vendor to set
+     */
+    public void setVendor(int vendor) {
+        this.vendor = vendor;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.vendor = data.readInt();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeInt(this.vendor);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 379;
+        int result = super.hashCode();
+        result = prime * result + vendor;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFActionVendor)) {
+            return false;
+        }
+        OFActionVendor other = (OFActionVendor) obj;
+        if (vendor != other.vendor) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanIdentifier.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanIdentifier.java
new file mode 100644
index 0000000..5bd0e0b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanIdentifier.java
@@ -0,0 +1,98 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_vlan_vid
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionVirtualLanIdentifier extends OFAction {
+    public static int MINIMUM_LENGTH = 8;
+
+    protected short virtualLanIdentifier;
+
+    public OFActionVirtualLanIdentifier() {
+        super.setType(OFActionType.SET_VLAN_ID);
+        super.setLength((short) MINIMUM_LENGTH);
+    }
+    
+    public OFActionVirtualLanIdentifier(short vlanId) {
+        this();
+        this.virtualLanIdentifier = vlanId;
+    }
+
+    /**
+     * @return the virtualLanIdentifier
+     */
+    public short getVirtualLanIdentifier() {
+        return virtualLanIdentifier;
+    }
+
+    /**
+     * @param virtualLanIdentifier the virtualLanIdentifier to set
+     */
+    public void setVirtualLanIdentifier(short virtualLanIdentifier) {
+        this.virtualLanIdentifier = virtualLanIdentifier;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.virtualLanIdentifier = data.readShort();
+        data.readShort();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeShort(this.virtualLanIdentifier);
+        data.writeShort((short) 0);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 383;
+        int result = super.hashCode();
+        result = prime * result + virtualLanIdentifier;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFActionVirtualLanIdentifier)) {
+            return false;
+        }
+        OFActionVirtualLanIdentifier other = (OFActionVirtualLanIdentifier) obj;
+        if (virtualLanIdentifier != other.virtualLanIdentifier) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanPriorityCodePoint.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanPriorityCodePoint.java
new file mode 100644
index 0000000..9202df3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanPriorityCodePoint.java
@@ -0,0 +1,100 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_vlan_pcp
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionVirtualLanPriorityCodePoint extends OFAction {
+    public static int MINIMUM_LENGTH = 8;
+
+    protected byte virtualLanPriorityCodePoint;
+
+    public OFActionVirtualLanPriorityCodePoint() {
+        super.setType(OFActionType.SET_VLAN_PCP);
+        super.setLength((short) MINIMUM_LENGTH);
+    }
+    
+    public OFActionVirtualLanPriorityCodePoint(byte priority) {
+        this();
+        this.virtualLanPriorityCodePoint = priority;
+    }
+
+    /**
+     * @return the virtualLanPriorityCodePoint
+     */
+    public byte getVirtualLanPriorityCodePoint() {
+        return virtualLanPriorityCodePoint;
+    }
+
+    /**
+     * @param virtualLanPriorityCodePoint the virtualLanPriorityCodePoint to set
+     */
+    public void setVirtualLanPriorityCodePoint(byte virtualLanPriorityCodePoint) {
+        this.virtualLanPriorityCodePoint = virtualLanPriorityCodePoint;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        super.readFrom(data);
+        this.virtualLanPriorityCodePoint = data.readByte();
+        data.readShort(); // pad
+        data.readByte(); // pad
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeByte(this.virtualLanPriorityCodePoint);
+        data.writeShort((short) 0);
+        data.writeByte((byte) 0);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 389;
+        int result = super.hashCode();
+        result = prime * result + virtualLanPriorityCodePoint;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof OFActionVirtualLanPriorityCodePoint)) {
+            return false;
+        }
+        OFActionVirtualLanPriorityCodePoint other = (OFActionVirtualLanPriorityCodePoint) obj;
+        if (virtualLanPriorityCodePoint != other.virtualLanPriorityCodePoint) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/BasicFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/BasicFactory.java
new file mode 100644
index 0000000..7b15e82
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/BasicFactory.java
@@ -0,0 +1,294 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.factory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionType;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.protocol.statistics.OFVendorStatistics;
+import org.openflow.protocol.vendor.OFByteArrayVendorData;
+import org.openflow.protocol.vendor.OFVendorData;
+import org.openflow.protocol.vendor.OFVendorDataType;
+import org.openflow.protocol.vendor.OFVendorId;
+
+
+/**
+ * A basic OpenFlow factory that supports naive creation of both Messages and
+ * Actions.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ *
+ */
+public class BasicFactory implements OFMessageFactory, OFActionFactory,
+        OFStatisticsFactory, OFVendorDataFactory {
+    @Override
+    public OFMessage getMessage(OFType t) {
+        return t.newInstance();
+    }
+
+    @Override
+    public List<OFMessage> parseMessage(ChannelBuffer data) throws MessageParseException {
+        List<OFMessage> msglist = new ArrayList<OFMessage>();
+        OFMessage msg = null;
+
+        while (data.readableBytes() >= OFMessage.MINIMUM_LENGTH) {
+            data.markReaderIndex();
+            msg = this.parseMessageOne(data);
+            if (msg == null) {
+                data.resetReaderIndex();
+                break;
+            }
+            else {
+                msglist.add(msg);
+            }
+        }
+
+        if (msglist.size() == 0) {
+            return null;
+        }
+        return msglist;
+
+    }
+
+    public OFMessage parseMessageOne(ChannelBuffer data) throws MessageParseException {
+        try {
+            OFMessage demux = new OFMessage();
+            OFMessage ofm = null;
+
+            if (data.readableBytes() < OFMessage.MINIMUM_LENGTH)
+                return ofm;
+
+            data.markReaderIndex();
+            demux.readFrom(data);
+            data.resetReaderIndex();
+
+            if (demux.getLengthU() > data.readableBytes())
+                return ofm;
+
+            ofm = getMessage(demux.getType());
+            if (ofm == null)
+                return null;
+
+            if (ofm instanceof OFActionFactoryAware) {
+                ((OFActionFactoryAware)ofm).setActionFactory(this);
+            }
+            if (ofm instanceof OFMessageFactoryAware) {
+                ((OFMessageFactoryAware)ofm).setMessageFactory(this);
+            }
+            if (ofm instanceof OFStatisticsFactoryAware) {
+                ((OFStatisticsFactoryAware)ofm).setStatisticsFactory(this);
+            }
+            if (ofm instanceof OFVendorDataFactoryAware) {
+                ((OFVendorDataFactoryAware)ofm).setVendorDataFactory(this);
+            }
+            ofm.readFrom(data);
+            if (OFMessage.class.equals(ofm.getClass())) {
+                // advance the position for un-implemented messages
+                data.readerIndex(data.readerIndex()+(ofm.getLengthU() -
+                        OFMessage.MINIMUM_LENGTH));
+            }
+
+            return ofm;
+        } catch (Exception e) {
+            /* Write the offending data along with the error message */
+            data.resetReaderIndex();
+            String msg =
+                    "Message Parse Error for packet:" +  dumpBuffer(data) +
+                    "\nException: " + e.toString();
+            data.resetReaderIndex();
+
+            throw new MessageParseException(msg, e);
+        }
+    }
+
+    @Override
+    public OFAction getAction(OFActionType t) {
+        return t.newInstance();
+    }
+
+    @Override
+    public List<OFAction> parseActions(ChannelBuffer data, int length) {
+        return parseActions(data, length, 0);
+    }
+
+    @Override
+    public List<OFAction> parseActions(ChannelBuffer data, int length, int limit) {
+        List<OFAction> results = new ArrayList<OFAction>();
+        OFAction demux = new OFAction();
+        OFAction ofa;
+        int end = data.readerIndex() + length;
+
+        while (limit == 0 || results.size() <= limit) {
+            if ((data.readableBytes() < OFAction.MINIMUM_LENGTH ||
+                (data.readerIndex() + OFAction.MINIMUM_LENGTH) > end))
+                return results;
+
+            data.markReaderIndex();
+            demux.readFrom(data);
+            data.resetReaderIndex();
+
+            if ((demux.getLengthU() > data.readableBytes() ||
+                (data.readerIndex() + demux.getLengthU()) > end))
+                return results;
+
+            ofa = getAction(demux.getType());
+            ofa.readFrom(data);
+            if (OFAction.class.equals(ofa.getClass())) {
+                // advance the position for un-implemented messages
+                data.readerIndex(data.readerIndex()+(ofa.getLengthU() -
+                        OFAction.MINIMUM_LENGTH));
+            }
+            results.add(ofa);
+        }
+
+        return results;
+    }
+
+    @Override
+    public OFActionFactory getActionFactory() {
+        return this;
+    }
+
+    @Override
+    public OFStatistics getStatistics(OFType t, OFStatisticsType st) {
+        return st.newInstance(t);
+    }
+
+    @Override
+    public List<OFStatistics> parseStatistics(OFType t, OFStatisticsType st,
+                                              ChannelBuffer data, int length) {
+        return parseStatistics(t, st, data, length, 0);
+    }
+
+    /**
+     * @param t
+     *            OFMessage type: should be one of stats_request or stats_reply
+     * @param st
+     *            statistics type of this message, e.g., DESC, TABLE
+     * @param data
+     *            buffer to read from
+     * @param length
+     *            length of statistics
+     * @param limit
+     *            number of statistics to grab; 0 == all
+     * 
+     * @return list of statistics
+     */
+
+    @Override
+    public List<OFStatistics> parseStatistics(OFType t, OFStatisticsType st,
+            ChannelBuffer data, int length, int limit) {
+        List<OFStatistics> results = new ArrayList<OFStatistics>();
+        OFStatistics statistics = getStatistics(t, st);
+
+        int start = data.readerIndex();
+        int count = 0;
+
+        while (limit == 0 || results.size() <= limit) {
+            // TODO Create a separate MUX/DEMUX path for vendor stats
+            if (statistics instanceof OFVendorStatistics)
+                ((OFVendorStatistics)statistics).setLength(length);
+
+            /**
+             * can't use data.remaining() here, b/c there could be other data
+             * buffered past this message
+             */
+            if ((length - count) >= statistics.getLength()) {
+                if (statistics instanceof OFActionFactoryAware)
+                    ((OFActionFactoryAware)statistics).setActionFactory(this);
+                statistics.readFrom(data);
+                results.add(statistics);
+                count += statistics.getLength();
+                statistics = getStatistics(t, st);
+            } else {
+                if (count < length) {
+                    /**
+                     * Nasty case: partial/incomplete statistic found even
+                     * though we have a full message. Found when NOX sent
+                     * agg_stats request with wrong agg statistics length (52
+                     * instead of 56)
+                     * 
+                     * just throw the rest away, or we will break framing
+                     */
+                    data.readerIndex(start + length);
+                }
+                return results;
+            }
+        }
+        return results; // empty; no statistics at all
+    }
+
+
+    @Override
+    public OFVendorData getVendorData(OFVendorId vendorId,
+                                      OFVendorDataType vendorDataType) {
+        if (vendorDataType == null)
+            return null;
+        
+        return vendorDataType.newInstance();
+    }
+
+    /**
+     * Attempts to parse and return the OFVendorData contained in the given
+     * ChannelBuffer, beginning right after the vendor id.
+     * @param vendor the vendor id that was parsed from the OFVendor message.
+     * @param data the ChannelBuffer from which to parse the vendor data
+     * @param length the length to the end of the enclosing message.
+     * @return an OFVendorData instance
+     */
+    public OFVendorData parseVendorData(int vendor, ChannelBuffer data,
+            int length) {
+        OFVendorDataType vendorDataType = null;
+        OFVendorId vendorId = OFVendorId.lookupVendorId(vendor);
+        if (vendorId != null) {
+            data.markReaderIndex();
+            vendorDataType = vendorId.parseVendorDataType(data, length);
+            data.resetReaderIndex();
+        }
+        
+        OFVendorData vendorData = getVendorData(vendorId, vendorDataType);
+        if (vendorData == null)
+            vendorData = new OFByteArrayVendorData();
+
+        vendorData.readFrom(data, length);
+        
+        return vendorData;
+    }
+
+    public static String dumpBuffer(ChannelBuffer data) {
+        // NOTE: Reads all the bytes in buffer from current read offset.
+        // Set/Reset ReaderIndex if you want to read from a different location
+        int len = data.readableBytes();
+        StringBuffer sb = new StringBuffer();
+        for (int i=0 ; i<len; i++) {
+            if (i%32 == 0) sb.append("\n");
+            if (i%4 == 0) sb.append(" ");
+            sb.append(String.format("%02x", data.getUnsignedByte(i)));
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/MessageParseException.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/MessageParseException.java
new file mode 100644
index 0000000..b685e5d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/MessageParseException.java
@@ -0,0 +1,29 @@
+package org.openflow.protocol.factory;
+
+/**
+ * Exception thrown when an openflow message fails to parse properly
+ */
+public class MessageParseException extends Exception {
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -75893812926304726L;
+
+    public MessageParseException() {
+        super();
+    }
+
+    public MessageParseException(String message, Throwable cause) {
+        super(message, cause);
+        this.setStackTrace(cause.getStackTrace());
+    }
+
+    public MessageParseException(String message) {
+        super(message);
+    }
+
+    public MessageParseException(Throwable cause) {
+        super(cause);
+        this.setStackTrace(cause.getStackTrace());
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactory.java
new file mode 100644
index 0000000..c3cd062
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactory.java
@@ -0,0 +1,61 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.factory;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionType;
+
+
+/**
+ * The interface to factories used for retrieving OFAction instances. All
+ * methods are expected to be thread-safe.
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFActionFactory {
+    /**
+     * Retrieves an OFAction instance corresponding to the specified
+     * OFActionType
+     * @param t the type of the OFAction to be retrieved
+     * @return an OFAction instance
+     */
+    public OFAction getAction(OFActionType t);
+
+    /**
+     * Attempts to parse and return all OFActions contained in the given
+     * ByteBuffer, beginning at the ByteBuffer's position, and ending at
+     * position+length.
+     * @param data the ChannelBuffer to parse for OpenFlow actions
+     * @param length the number of Bytes to examine for OpenFlow actions
+     * @return a list of OFAction instances
+     */
+    public List<OFAction> parseActions(ChannelBuffer data, int length);
+
+    /**
+     * Attempts to parse and return all OFActions contained in the given
+     * ByteBuffer, beginning at the ByteBuffer's position, and ending at
+     * position+length.
+     * @param data the ChannelBuffer to parse for OpenFlow actions
+     * @param length the number of Bytes to examine for OpenFlow actions
+     * @param limit the maximum number of messages to return, 0 means no limit
+     * @return a list of OFAction instances
+     */
+    public List<OFAction> parseActions(ChannelBuffer data, int length, int limit);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactoryAware.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactoryAware.java
new file mode 100644
index 0000000..a97a95c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactoryAware.java
@@ -0,0 +1,31 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.factory;
+
+/**
+ * Objects implementing this interface are expected to be instantiated with an
+ * instance of an OFActionFactory
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFActionFactoryAware {
+    /**
+     * Sets the OFActionFactory
+     * @param actionFactory
+     */
+    public void setActionFactory(OFActionFactory actionFactory);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactory.java
new file mode 100644
index 0000000..8bb7045
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactory.java
@@ -0,0 +1,55 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.factory;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+
+
+/**
+ * The interface to factories used for retrieving OFMessage instances. All
+ * methods are expected to be thread-safe.
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFMessageFactory {
+    /**
+     * Retrieves an OFMessage instance corresponding to the specified OFType
+     * @param t the type of the OFMessage to be retrieved
+     * @return an OFMessage instance
+     */
+    public OFMessage getMessage(OFType t);
+
+    /**
+     * Attempts to parse and return a OFMessages contained in the given
+     * ChannelBuffer, beginning at the ChannelBuffer's position, and ending at the
+     * after the first parsed message
+     * @param data the ChannelBuffer to parse for an OpenFlow message
+     * @return a list of OFMessage instances
+     * @throws MessageParseException 
+     */
+    public List<OFMessage> parseMessage(ChannelBuffer data) throws MessageParseException;
+
+    /**
+     * Retrieves an OFActionFactory
+     * @return an OFActionFactory
+     */
+    public OFActionFactory getActionFactory();
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactoryAware.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactoryAware.java
new file mode 100644
index 0000000..adb1421
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactoryAware.java
@@ -0,0 +1,35 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package org.openflow.protocol.factory;
+
+/**
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ *
+ */
+public interface OFMessageFactoryAware {
+
+       /**
+        * Sets the message factory for this object
+        * 
+        * @param factory
+        */
+       void setMessageFactory(OFMessageFactory factory);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactory.java
new file mode 100644
index 0000000..32eb3cb
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactory.java
@@ -0,0 +1,72 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.factory;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+
+
+/**
+ * The interface to factories used for retrieving OFStatistics instances. All
+ * methods are expected to be thread-safe.
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFStatisticsFactory {
+    /**
+     * Retrieves an OFStatistics instance corresponding to the specified
+     * OFStatisticsType
+     * @param t the type of the containing OFMessage, only accepts statistics
+     *           request or reply
+     * @param st the type of the OFStatistics to be retrieved
+     * @return an OFStatistics instance
+     */
+    public OFStatistics getStatistics(OFType t, OFStatisticsType st);
+
+    /**
+     * Attempts to parse and return all OFStatistics contained in the given
+     * ByteBuffer, beginning at the ByteBuffer's position, and ending at
+     * position+length.
+     * @param t the type of the containing OFMessage, only accepts statistics
+     *           request or reply
+     * @param st the type of the OFStatistics to be retrieved
+     * @param data the ChannelBuffer to parse for OpenFlow Statistics
+     * @param length the number of Bytes to examine for OpenFlow Statistics
+     * @return a list of OFStatistics instances
+     */
+    public List<OFStatistics> parseStatistics(OFType t,
+            OFStatisticsType st, ChannelBuffer data, int length);
+
+    /**
+     * Attempts to parse and return all OFStatistics contained in the given
+     * ByteBuffer, beginning at the ByteBuffer's position, and ending at
+     * position+length.
+     * @param t the type of the containing OFMessage, only accepts statistics
+     *           request or reply
+     * @param st the type of the OFStatistics to be retrieved
+     * @param data the ChannelBuffer to parse for OpenFlow Statistics
+     * @param length the number of Bytes to examine for OpenFlow Statistics
+     * @param limit the maximum number of messages to return, 0 means no limit
+     * @return a list of OFStatistics instances
+     */
+    public List<OFStatistics> parseStatistics(OFType t,
+            OFStatisticsType st, ChannelBuffer data, int length, int limit);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactoryAware.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactoryAware.java
new file mode 100644
index 0000000..52ab09a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactoryAware.java
@@ -0,0 +1,31 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.factory;
+
+/**
+ * Objects implementing this interface are expected to be instantiated with an
+ * instance of an OFStatisticsFactory
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFStatisticsFactoryAware {
+    /**
+     * Sets the OFStatisticsFactory
+     * @param statisticsFactory
+     */
+    public void setStatisticsFactory(OFStatisticsFactory statisticsFactory);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactory.java
new file mode 100644
index 0000000..d754a4a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactory.java
@@ -0,0 +1,69 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.factory;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.vendor.OFVendorData;
+import org.openflow.protocol.vendor.OFVendorDataType;
+import org.openflow.protocol.vendor.OFVendorId;
+
+/**
+ * The interface to factories used for parsing/creating OFVendorData instances.
+ * All methods are expected to be thread-safe.
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public interface OFVendorDataFactory {
+    /**
+     * Retrieves an OFVendorData instance corresponding to the specified
+     * OFVendorId and OFVendorDataType. There are 3 possible cases for
+     * how this will be called:
+     * 
+     * 1) If the vendor id in the OFVendor message is an unknown value, 
+     *    then this method is called with both vendorId and vendorDataType
+     *    set to null. In this case typically the factory method should
+     *    return an instance of OFGenericVendorData that just contains
+     *    the raw byte array of the vendor data.
+     *    
+     * 2) If the vendor id is known but no vendor data type has been
+     *    registered for the data in the message, then vendorId is set to
+     *    the appropriate OFVendorId instance and OFVendorDataType is set
+     *    to null. This would typically be handled the same way as #1
+     *    
+     * 3) If both the vendor id and and vendor data type are known, then
+     *    typically you'd just call the method in OFVendorDataType to
+     *    instantiate the appropriate subclass of OFVendorData.
+     *    
+     * @param vendorId the vendorId of the containing OFVendor message
+     * @param vendorDataType the type of the OFVendorData to be retrieved
+     * @return an OFVendorData instance
+     */
+    public OFVendorData getVendorData(OFVendorId vendorId,
+            OFVendorDataType vendorDataType);
+    
+    /**
+     * Attempts to parse and return the OFVendorData contained in the given
+     * ChannelBuffer, beginning right after the vendor id.
+     * @param vendorId the vendor id that was parsed from the OFVendor message.
+     * @param data the ChannelBuffer from which to parse the vendor data
+     * @param length the length to the end of the enclosing message.
+     * @return an OFVendorData instance
+     */
+    public OFVendorData parseVendorData(int vendorId, ChannelBuffer data,
+            int length);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactoryAware.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactoryAware.java
new file mode 100644
index 0000000..23614b0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactoryAware.java
@@ -0,0 +1,28 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.factory;
+
+/**
+ * Classes implementing this interface are expected to be instantiated with an
+ * instance of an OFVendorDataFactory
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public interface OFVendorDataFactoryAware {
+    public void setVendorDataFactory(OFVendorDataFactory vendorDataFactory);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java
new file mode 100644
index 0000000..ad57312
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java
@@ -0,0 +1,57 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.util.HexString;
+
+public class OFFeaturesReplyJSONSerializer extends JsonSerializer<OFFeaturesReply> {
+    
+    /**
+     * Performs the serialization of a OFFeaturesReply object
+     */
+    @Override
+    public void serialize(OFFeaturesReply reply, JsonGenerator jGen, SerializerProvider serializer) throws IOException, JsonProcessingException {
+        jGen.writeStartObject();
+        jGen.writeNumberField("actions", reply.getActions());
+        jGen.writeNumberField("buffers", reply.getBuffers());
+        jGen.writeNumberField("capabilities", reply.getCapabilities());
+        jGen.writeStringField("datapathId", HexString.toHexString(reply.getDatapathId()));
+        jGen.writeNumberField("length", reply.getLength());
+        serializer.defaultSerializeField("ports", reply.getPorts(), jGen);
+        jGen.writeNumberField("tables", reply.getTables());
+        jGen.writeStringField("type", reply.getType().toString());
+        jGen.writeNumberField("version", reply.getVersion());
+        jGen.writeNumberField("xid", reply.getXid());
+        jGen.writeEndObject();
+    }
+
+    /**
+     * Tells SimpleModule that we are the serializer for OFFeaturesReply
+     */
+    @Override
+    public Class<OFFeaturesReply> handledType() {
+        return OFFeaturesReply.class;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java
new file mode 100644
index 0000000..69312fe
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java
@@ -0,0 +1,91 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.protocol.OFMatch;
+import org.openflow.util.HexString;
+
+public class OFMatchJSONSerializer extends JsonSerializer<OFMatch> {
+
+    /**
+     * Converts an IP in a 32 bit integer to a dotted-decimal string
+     * @param i The IP address in a 32 bit integer
+     * @return An IP address string in dotted-decimal
+     */
+    private String intToIp(int i) {
+        return ((i >> 24 ) & 0xFF) + "." +
+               ((i >> 16 ) & 0xFF) + "." +
+               ((i >>  8 ) & 0xFF) + "." +
+               ( i        & 0xFF);
+    }
+
+    /**
+     * Performs the serialization of a OFMatch object
+     */
+    @Override
+    public void serialize(OFMatch match, JsonGenerator jGen, 
+                                SerializerProvider serializer) 
+                                throws IOException, JsonProcessingException {
+        jGen.writeStartObject();
+        jGen.writeStringField("dataLayerDestination", 
+                    HexString.toHexString(match.getDataLayerDestination()));
+        jGen.writeStringField("dataLayerSource", 
+                    HexString.toHexString(match.getDataLayerSource()));
+        String dataType = Integer.toHexString(match.getDataLayerType());
+        while (dataType.length() < 4) {
+            dataType = "0".concat(dataType);
+        }
+        jGen.writeStringField("dataLayerType", "0x" + dataType);
+        jGen.writeNumberField("dataLayerVirtualLan", 
+                    match.getDataLayerVirtualLan());
+        jGen.writeNumberField("dataLayerVirtualLanPriorityCodePoint", 
+                    match.getDataLayerVirtualLanPriorityCodePoint());
+        jGen.writeNumberField("inputPort", match.getInputPort());
+        jGen.writeStringField("networkDestination", 
+                    intToIp(match.getNetworkDestination()));
+        jGen.writeNumberField("networkDestinationMaskLen", 
+                    match.getNetworkDestinationMaskLen());
+        jGen.writeNumberField("networkProtocol", match.getNetworkProtocol());
+        jGen.writeStringField("networkSource", 
+                    intToIp(match.getNetworkSource()));
+        jGen.writeNumberField("networkSourceMaskLen", 
+                    match.getNetworkSourceMaskLen());
+        jGen.writeNumberField("networkTypeOfService", 
+                    match.getNetworkTypeOfService());
+        jGen.writeNumberField("transportDestination", 
+                    match.getTransportDestination());
+        jGen.writeNumberField("transportSource", 
+                    match.getTransportSource());
+        jGen.writeNumberField("wildcards", match.getWildcards());
+        jGen.writeEndObject();
+    }
+
+    /**
+     * Tells SimpleModule that we are the serializer for OFMatch
+     */
+    @Override
+    public Class<OFMatch> handledType() {
+        return OFMatch.class;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java
new file mode 100644
index 0000000..7dec16b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java
@@ -0,0 +1,130 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_aggregate_stats_reply structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFAggregateStatisticsReply implements OFStatistics {
+    protected long packetCount;
+    protected long byteCount;
+    protected int flowCount;
+
+    /**
+     * @return the packetCount
+     */
+    public long getPacketCount() {
+        return packetCount;
+    }
+
+    /**
+     * @param packetCount the packetCount to set
+     */
+    public void setPacketCount(long packetCount) {
+        this.packetCount = packetCount;
+    }
+
+    /**
+     * @return the byteCount
+     */
+    public long getByteCount() {
+        return byteCount;
+    }
+
+    /**
+     * @param byteCount the byteCount to set
+     */
+    public void setByteCount(long byteCount) {
+        this.byteCount = byteCount;
+    }
+
+    /**
+     * @return the flowCount
+     */
+    public int getFlowCount() {
+        return flowCount;
+    }
+
+    /**
+     * @param flowCount the flowCount to set
+     */
+    public void setFlowCount(int flowCount) {
+        this.flowCount = flowCount;
+    }
+
+    @Override
+    @JsonIgnore
+    public int getLength() {
+        return 24;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        this.packetCount = data.readLong();
+        this.byteCount = data.readLong();
+        this.flowCount = data.readInt();
+        data.readInt(); // pad
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        data.writeLong(this.packetCount);
+        data.writeLong(this.byteCount);
+        data.writeInt(this.flowCount);
+        data.writeInt(0); // pad
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 397;
+        int result = 1;
+        result = prime * result + (int) (byteCount ^ (byteCount >>> 32));
+        result = prime * result + flowCount;
+        result = prime * result + (int) (packetCount ^ (packetCount >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFAggregateStatisticsReply)) {
+            return false;
+        }
+        OFAggregateStatisticsReply other = (OFAggregateStatisticsReply) obj;
+        if (byteCount != other.byteCount) {
+            return false;
+        }
+        if (flowCount != other.flowCount) {
+            return false;
+        }
+        if (packetCount != other.packetCount) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsRequest.java
new file mode 100644
index 0000000..f41a4f1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsRequest.java
@@ -0,0 +1,135 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMatch;
+
+/**
+ * Represents an ofp_aggregate_stats_request structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFAggregateStatisticsRequest implements OFStatistics {
+    protected OFMatch match;
+    protected byte tableId;
+    protected short outPort;
+
+    /**
+     * @return the match
+     */
+    public OFMatch getMatch() {
+        return match;
+    }
+
+    /**
+     * @param match the match to set
+     */
+    public void setMatch(OFMatch match) {
+        this.match = match;
+    }
+
+    /**
+     * @return the tableId
+     */
+    public byte getTableId() {
+        return tableId;
+    }
+
+    /**
+     * @param tableId the tableId to set
+     */
+    public void setTableId(byte tableId) {
+        this.tableId = tableId;
+    }
+
+    /**
+     * @return the outPort
+     */
+    public short getOutPort() {
+        return outPort;
+    }
+
+    /**
+     * @param outPort the outPort to set
+     */
+    public void setOutPort(short outPort) {
+        this.outPort = outPort;
+    }
+
+    @Override
+    public int getLength() {
+        return 44;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        if (this.match == null)
+            this.match = new OFMatch();
+        this.match.readFrom(data);
+        this.tableId = data.readByte();
+        data.readByte(); // pad
+        this.outPort = data.readShort();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        this.match.writeTo(data);
+        data.writeByte(this.tableId);
+        data.writeByte((byte) 0);
+        data.writeShort(this.outPort);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 401;
+        int result = 1;
+        result = prime * result + ((match == null) ? 0 : match.hashCode());
+        result = prime * result + outPort;
+        result = prime * result + tableId;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFAggregateStatisticsRequest)) {
+            return false;
+        }
+        OFAggregateStatisticsRequest other = (OFAggregateStatisticsRequest) obj;
+        if (match == null) {
+            if (other.match != null) {
+                return false;
+            }
+        } else if (!match.equals(other.match)) {
+            return false;
+        }
+        if (outPort != other.outPort) {
+            return false;
+        }
+        if (tableId != other.tableId) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java
new file mode 100644
index 0000000..6799fa3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java
@@ -0,0 +1,216 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.StringByteSerializer;
+
+/**
+ * Represents an ofp_desc_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFDescriptionStatistics implements OFStatistics {
+    public static int DESCRIPTION_STRING_LENGTH = 256;
+    public static int SERIAL_NUMBER_LENGTH = 32;
+
+    protected String manufacturerDescription;
+    protected String hardwareDescription;
+    protected String softwareDescription;
+    protected String serialNumber;
+    protected String datapathDescription;
+
+    /**
+     * @return the manufacturerDescription
+     */
+    public String getManufacturerDescription() {
+        return manufacturerDescription;
+    }
+
+    /**
+     * @param manufacturerDescription the manufacturerDescription to set
+     */
+    public void setManufacturerDescription(String manufacturerDescription) {
+        this.manufacturerDescription = manufacturerDescription;
+    }
+
+    /**
+     * @return the hardwareDescription
+     */
+    public String getHardwareDescription() {
+        return hardwareDescription;
+    }
+
+    /**
+     * @param hardwareDescription the hardwareDescription to set
+     */
+    public void setHardwareDescription(String hardwareDescription) {
+        this.hardwareDescription = hardwareDescription;
+    }
+
+    /**
+     * @return the softwareDescription
+     */
+    public String getSoftwareDescription() {
+        return softwareDescription;
+    }
+
+    /**
+     * @param softwareDescription the softwareDescription to set
+     */
+    public void setSoftwareDescription(String softwareDescription) {
+        this.softwareDescription = softwareDescription;
+    }
+
+    /**
+     * @return the serialNumber
+     */
+    public String getSerialNumber() {
+        return serialNumber;
+    }
+
+    /**
+     * @param serialNumber the serialNumber to set
+     */
+    public void setSerialNumber(String serialNumber) {
+        this.serialNumber = serialNumber;
+    }
+
+    /**
+     * @return the datapathDescription
+     */
+    public String getDatapathDescription() {
+        return datapathDescription;
+    }
+
+    /**
+     * @param datapathDescription the datapathDescription to set
+     */
+    public void setDatapathDescription(String datapathDescription) {
+        this.datapathDescription = datapathDescription;
+    }
+
+    @Override
+    public int getLength() {
+        return 1056;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        this.manufacturerDescription = StringByteSerializer.readFrom(data,
+                DESCRIPTION_STRING_LENGTH);
+        this.hardwareDescription = StringByteSerializer.readFrom(data,
+                DESCRIPTION_STRING_LENGTH);
+        this.softwareDescription = StringByteSerializer.readFrom(data,
+                DESCRIPTION_STRING_LENGTH);
+        this.serialNumber = StringByteSerializer.readFrom(data,
+                SERIAL_NUMBER_LENGTH);
+        this.datapathDescription = StringByteSerializer.readFrom(data,
+                DESCRIPTION_STRING_LENGTH);
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        StringByteSerializer.writeTo(data, DESCRIPTION_STRING_LENGTH,
+                this.manufacturerDescription);
+        StringByteSerializer.writeTo(data, DESCRIPTION_STRING_LENGTH,
+                this.hardwareDescription);
+        StringByteSerializer.writeTo(data, DESCRIPTION_STRING_LENGTH,
+                this.softwareDescription);
+        StringByteSerializer.writeTo(data, SERIAL_NUMBER_LENGTH,
+                this.serialNumber);
+        StringByteSerializer.writeTo(data, DESCRIPTION_STRING_LENGTH,
+                this.datapathDescription);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 409;
+        int result = 1;
+        result = prime
+                * result
+                + ((datapathDescription == null) ? 0 : datapathDescription
+                        .hashCode());
+        result = prime
+                * result
+                + ((hardwareDescription == null) ? 0 : hardwareDescription
+                        .hashCode());
+        result = prime
+                * result
+                + ((manufacturerDescription == null) ? 0
+                        : manufacturerDescription.hashCode());
+        result = prime * result
+                + ((serialNumber == null) ? 0 : serialNumber.hashCode());
+        result = prime
+                * result
+                + ((softwareDescription == null) ? 0 : softwareDescription
+                        .hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFDescriptionStatistics)) {
+            return false;
+        }
+        OFDescriptionStatistics other = (OFDescriptionStatistics) obj;
+        if (datapathDescription == null) {
+            if (other.datapathDescription != null) {
+                return false;
+            }
+        } else if (!datapathDescription.equals(other.datapathDescription)) {
+            return false;
+        }
+        if (hardwareDescription == null) {
+            if (other.hardwareDescription != null) {
+                return false;
+            }
+        } else if (!hardwareDescription.equals(other.hardwareDescription)) {
+            return false;
+        }
+        if (manufacturerDescription == null) {
+            if (other.manufacturerDescription != null) {
+                return false;
+            }
+        } else if (!manufacturerDescription
+                .equals(other.manufacturerDescription)) {
+            return false;
+        }
+        if (serialNumber == null) {
+            if (other.serialNumber != null) {
+                return false;
+            }
+        } else if (!serialNumber.equals(other.serialNumber)) {
+            return false;
+        }
+        if (softwareDescription == null) {
+            if (other.softwareDescription != null) {
+                return false;
+            }
+        } else if (!softwareDescription.equals(other.softwareDescription)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java
new file mode 100644
index 0000000..bea7f1e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java
@@ -0,0 +1,359 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.factory.OFActionFactory;
+import org.openflow.protocol.factory.OFActionFactoryAware;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_flow_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFFlowStatisticsReply implements OFStatistics, OFActionFactoryAware {
+    public static int MINIMUM_LENGTH = 88;
+
+    protected OFActionFactory actionFactory;
+    protected short length = (short) MINIMUM_LENGTH;
+    protected byte tableId;
+    protected OFMatch match;
+    protected int durationSeconds;
+    protected int durationNanoseconds;
+    protected short priority;
+    protected short idleTimeout;
+    protected short hardTimeout;
+    protected long cookie;
+    protected long packetCount;
+    protected long byteCount;
+    protected List<OFAction> actions;
+
+    /**
+     * @return the tableId
+     */
+    public byte getTableId() {
+        return tableId;
+    }
+
+    /**
+     * @param tableId the tableId to set
+     */
+    public void setTableId(byte tableId) {
+        this.tableId = tableId;
+    }
+
+    /**
+     * @return the match
+     */
+    public OFMatch getMatch() {
+        return match;
+    }
+
+    /**
+     * @param match the match to set
+     */
+    public void setMatch(OFMatch match) {
+        this.match = match;
+    }
+
+    /**
+     * @return the durationSeconds
+     */
+    public int getDurationSeconds() {
+        return durationSeconds;
+    }
+
+    /**
+     * @param durationSeconds the durationSeconds to set
+     */
+    public void setDurationSeconds(int durationSeconds) {
+        this.durationSeconds = durationSeconds;
+    }
+
+    /**
+     * @return the durationNanoseconds
+     */
+    public int getDurationNanoseconds() {
+        return durationNanoseconds;
+    }
+
+    /**
+     * @param durationNanoseconds the durationNanoseconds to set
+     */
+    public void setDurationNanoseconds(int durationNanoseconds) {
+        this.durationNanoseconds = durationNanoseconds;
+    }
+
+    /**
+     * @return the priority
+     */
+    public short getPriority() {
+        return priority;
+    }
+
+    /**
+     * @param priority the priority to set
+     */
+    public void setPriority(short priority) {
+        this.priority = priority;
+    }
+
+    /**
+     * @return the idleTimeout
+     */
+    public short getIdleTimeout() {
+        return idleTimeout;
+    }
+
+    /**
+     * @param idleTimeout the idleTimeout to set
+     */
+    public void setIdleTimeout(short idleTimeout) {
+        this.idleTimeout = idleTimeout;
+    }
+
+    /**
+     * @return the hardTimeout
+     */
+    public short getHardTimeout() {
+        return hardTimeout;
+    }
+
+    /**
+     * @param hardTimeout the hardTimeout to set
+     */
+    public void setHardTimeout(short hardTimeout) {
+        this.hardTimeout = hardTimeout;
+    }
+
+    /**
+     * @return the cookie
+     */
+    public long getCookie() {
+        return cookie;
+    }
+
+    /**
+     * @param cookie the cookie to set
+     */
+    public void setCookie(long cookie) {
+        this.cookie = cookie;
+    }
+
+    /**
+     * @return the packetCount
+     */
+    public long getPacketCount() {
+        return packetCount;
+    }
+
+    /**
+     * @param packetCount the packetCount to set
+     */
+    public void setPacketCount(long packetCount) {
+        this.packetCount = packetCount;
+    }
+
+    /**
+     * @return the byteCount
+     */
+    public long getByteCount() {
+        return byteCount;
+    }
+
+    /**
+     * @param byteCount the byteCount to set
+     */
+    public void setByteCount(long byteCount) {
+        this.byteCount = byteCount;
+    }
+
+    /**
+     * @param length the length to set
+     */
+    public void setLength(short length) {
+        this.length = length;
+    }
+
+    @Override
+    @JsonIgnore
+    public int getLength() {
+        return U16.f(length);
+    }
+
+    /**
+     * @param actionFactory the actionFactory to set
+     */
+    @Override
+    public void setActionFactory(OFActionFactory actionFactory) {
+        this.actionFactory = actionFactory;
+    }
+
+    /**
+     * @return the actions
+     */
+    public List<OFAction> getActions() {
+        return actions;
+    }
+
+    /**
+     * @param actions the actions to set
+     */
+    public void setActions(List<OFAction> actions) {
+        this.actions = actions;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        this.length = data.readShort();
+        this.tableId = data.readByte();
+        data.readByte(); // pad
+        if (this.match == null)
+            this.match = new OFMatch();
+        this.match.readFrom(data);
+        this.durationSeconds = data.readInt();
+        this.durationNanoseconds = data.readInt();
+        this.priority = data.readShort();
+        this.idleTimeout = data.readShort();
+        this.hardTimeout = data.readShort();
+        data.readInt(); // pad
+        data.readShort(); // pad
+        this.cookie = data.readLong();
+        this.packetCount = data.readLong();
+        this.byteCount = data.readLong();
+        if (this.actionFactory == null)
+            throw new RuntimeException("OFActionFactory not set");
+        this.actions = this.actionFactory.parseActions(data, getLength() -
+                MINIMUM_LENGTH);
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        data.writeShort(this.length);
+        data.writeByte(this.tableId);
+        data.writeByte((byte) 0);
+        this.match.writeTo(data);
+        data.writeInt(this.durationSeconds);
+        data.writeInt(this.durationNanoseconds);
+        data.writeShort(this.priority);
+        data.writeShort(this.idleTimeout);
+        data.writeShort(this.hardTimeout);
+        data.writeInt(0); // pad
+        data.writeShort((short)0); // pad
+        data.writeLong(this.cookie);
+        data.writeLong(this.packetCount);
+        data.writeLong(this.byteCount);
+        if (actions != null) {
+            for (OFAction action : actions) {
+                action.writeTo(data);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+    	String str = "match=" + this.match;
+    	str += " tableId=" + this.tableId;
+    	str += " durationSeconds=" + this.durationSeconds;
+    	str += " durationNanoseconds=" + this.durationNanoseconds;
+    	str += " priority=" + this.priority;
+    	str += " idleTimeout=" + this.idleTimeout;
+    	str += " hardTimeout=" + this.hardTimeout;
+    	str += " cookie=" + this.cookie;
+    	str += " packetCount=" + this.packetCount;
+    	str += " byteCount=" + this.byteCount;
+    	str += " action=" + this.actions;
+    	
+    	return str;
+    }
+    
+    @Override
+    public int hashCode() {
+        final int prime = 419;
+        int result = 1;
+        result = prime * result + (int) (byteCount ^ (byteCount >>> 32));
+        result = prime * result + (int) (cookie ^ (cookie >>> 32));
+        result = prime * result + durationNanoseconds;
+        result = prime * result + durationSeconds;
+        result = prime * result + hardTimeout;
+        result = prime * result + idleTimeout;
+        result = prime * result + length;
+        result = prime * result + ((match == null) ? 0 : match.hashCode());
+        result = prime * result + (int) (packetCount ^ (packetCount >>> 32));
+        result = prime * result + priority;
+        result = prime * result + tableId;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFFlowStatisticsReply)) {
+            return false;
+        }
+        OFFlowStatisticsReply other = (OFFlowStatisticsReply) obj;
+        if (byteCount != other.byteCount) {
+            return false;
+        }
+        if (cookie != other.cookie) {
+            return false;
+        }
+        if (durationNanoseconds != other.durationNanoseconds) {
+            return false;
+        }
+        if (durationSeconds != other.durationSeconds) {
+            return false;
+        }
+        if (hardTimeout != other.hardTimeout) {
+            return false;
+        }
+        if (idleTimeout != other.idleTimeout) {
+            return false;
+        }
+        if (length != other.length) {
+            return false;
+        }
+        if (match == null) {
+            if (other.match != null) {
+                return false;
+            }
+        } else if (!match.equals(other.match)) {
+            return false;
+        }
+        if (packetCount != other.packetCount) {
+            return false;
+        }
+        if (priority != other.priority) {
+            return false;
+        }
+        if (tableId != other.tableId) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsRequest.java
new file mode 100644
index 0000000..b21de0c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsRequest.java
@@ -0,0 +1,135 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMatch;
+
+/**
+ * Represents an ofp_flow_stats_request structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFFlowStatisticsRequest implements OFStatistics {
+    protected OFMatch match;
+    protected byte tableId;
+    protected short outPort;
+
+    /**
+     * @return the match
+     */
+    public OFMatch getMatch() {
+        return match;
+    }
+
+    /**
+     * @param match the match to set
+     */
+    public void setMatch(OFMatch match) {
+        this.match = match;
+    }
+
+    /**
+     * @return the tableId
+     */
+    public byte getTableId() {
+        return tableId;
+    }
+
+    /**
+     * @param tableId the tableId to set
+     */
+    public void setTableId(byte tableId) {
+        this.tableId = tableId;
+    }
+
+    /**
+     * @return the outPort
+     */
+    public short getOutPort() {
+        return outPort;
+    }
+
+    /**
+     * @param outPort the outPort to set
+     */
+    public void setOutPort(short outPort) {
+        this.outPort = outPort;
+    }
+
+    @Override
+    public int getLength() {
+        return 44;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        if (this.match == null)
+            this.match = new OFMatch();
+        this.match.readFrom(data);
+        this.tableId = data.readByte();
+        data.readByte(); // pad
+        this.outPort = data.readShort();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        this.match.writeTo(data);
+        data.writeByte(this.tableId);
+        data.writeByte((byte) 0);
+        data.writeShort(this.outPort);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 421;
+        int result = 1;
+        result = prime * result + ((match == null) ? 0 : match.hashCode());
+        result = prime * result + outPort;
+        result = prime * result + tableId;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFFlowStatisticsRequest)) {
+            return false;
+        }
+        OFFlowStatisticsRequest other = (OFFlowStatisticsRequest) obj;
+        if (match == null) {
+            if (other.match != null) {
+                return false;
+            }
+        } else if (!match.equals(other.match)) {
+            return false;
+        }
+        if (outPort != other.outPort) {
+            return false;
+        }
+        if (tableId != other.tableId) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java
new file mode 100644
index 0000000..87a2465
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java
@@ -0,0 +1,352 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_port_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFPortStatisticsReply implements OFStatistics {
+    protected short portNumber;
+    protected long receivePackets;
+    protected long transmitPackets;
+    protected long receiveBytes;
+    protected long transmitBytes;
+    protected long receiveDropped;
+    protected long transmitDropped;
+    protected long receiveErrors;
+    protected long transmitErrors;
+    protected long receiveFrameErrors;
+    protected long receiveOverrunErrors;
+    protected long receiveCRCErrors;
+    protected long collisions;
+
+    /**
+     * @return the portNumber
+     */
+    public short getPortNumber() {
+        return portNumber;
+    }
+
+    /**
+     * @param portNumber the portNumber to set
+     */
+    public void setPortNumber(short portNumber) {
+        this.portNumber = portNumber;
+    }
+
+    /**
+     * @return the receivePackets
+     */
+    public long getreceivePackets() {
+        return receivePackets;
+    }
+
+    /**
+     * @param receivePackets the receivePackets to set
+     */
+    public void setreceivePackets(long receivePackets) {
+        this.receivePackets = receivePackets;
+    }
+
+    /**
+     * @return the transmitPackets
+     */
+    public long getTransmitPackets() {
+        return transmitPackets;
+    }
+
+    /**
+     * @param transmitPackets the transmitPackets to set
+     */
+    public void setTransmitPackets(long transmitPackets) {
+        this.transmitPackets = transmitPackets;
+    }
+
+    /**
+     * @return the receiveBytes
+     */
+    public long getReceiveBytes() {
+        return receiveBytes;
+    }
+
+    /**
+     * @param receiveBytes the receiveBytes to set
+     */
+    public void setReceiveBytes(long receiveBytes) {
+        this.receiveBytes = receiveBytes;
+    }
+
+    /**
+     * @return the transmitBytes
+     */
+    public long getTransmitBytes() {
+        return transmitBytes;
+    }
+
+    /**
+     * @param transmitBytes the transmitBytes to set
+     */
+    public void setTransmitBytes(long transmitBytes) {
+        this.transmitBytes = transmitBytes;
+    }
+
+    /**
+     * @return the receiveDropped
+     */
+    public long getReceiveDropped() {
+        return receiveDropped;
+    }
+
+    /**
+     * @param receiveDropped the receiveDropped to set
+     */
+    public void setReceiveDropped(long receiveDropped) {
+        this.receiveDropped = receiveDropped;
+    }
+
+    /**
+     * @return the transmitDropped
+     */
+    public long getTransmitDropped() {
+        return transmitDropped;
+    }
+
+    /**
+     * @param transmitDropped the transmitDropped to set
+     */
+    public void setTransmitDropped(long transmitDropped) {
+        this.transmitDropped = transmitDropped;
+    }
+
+    /**
+     * @return the receiveErrors
+     */
+    public long getreceiveErrors() {
+        return receiveErrors;
+    }
+
+    /**
+     * @param receiveErrors the receiveErrors to set
+     */
+    public void setreceiveErrors(long receiveErrors) {
+        this.receiveErrors = receiveErrors;
+    }
+
+    /**
+     * @return the transmitErrors
+     */
+    public long getTransmitErrors() {
+        return transmitErrors;
+    }
+
+    /**
+     * @param transmitErrors the transmitErrors to set
+     */
+    public void setTransmitErrors(long transmitErrors) {
+        this.transmitErrors = transmitErrors;
+    }
+
+    /**
+     * @return the receiveFrameErrors
+     */
+    public long getReceiveFrameErrors() {
+        return receiveFrameErrors;
+    }
+
+    /**
+     * @param receiveFrameErrors the receiveFrameErrors to set
+     */
+    public void setReceiveFrameErrors(long receiveFrameErrors) {
+        this.receiveFrameErrors = receiveFrameErrors;
+    }
+
+    /**
+     * @return the receiveOverrunErrors
+     */
+    public long getReceiveOverrunErrors() {
+        return receiveOverrunErrors;
+    }
+
+    /**
+     * @param receiveOverrunErrors the receiveOverrunErrors to set
+     */
+    public void setReceiveOverrunErrors(long receiveOverrunErrors) {
+        this.receiveOverrunErrors = receiveOverrunErrors;
+    }
+
+    /**
+     * @return the receiveCRCErrors
+     */
+    public long getReceiveCRCErrors() {
+        return receiveCRCErrors;
+    }
+
+    /**
+     * @param receiveCRCErrors the receiveCRCErrors to set
+     */
+    public void setReceiveCRCErrors(long receiveCRCErrors) {
+        this.receiveCRCErrors = receiveCRCErrors;
+    }
+
+    /**
+     * @return the collisions
+     */
+    public long getCollisions() {
+        return collisions;
+    }
+
+    /**
+     * @param collisions the collisions to set
+     */
+    public void setCollisions(long collisions) {
+        this.collisions = collisions;
+    }
+
+    @Override
+    @JsonIgnore
+    public int getLength() {
+        return 104;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        this.portNumber = data.readShort();
+        data.readShort(); // pad
+        data.readInt(); // pad
+        this.receivePackets = data.readLong();
+        this.transmitPackets = data.readLong();
+        this.receiveBytes = data.readLong();
+        this.transmitBytes = data.readLong();
+        this.receiveDropped = data.readLong();
+        this.transmitDropped = data.readLong();
+        this.receiveErrors = data.readLong();
+        this.transmitErrors = data.readLong();
+        this.receiveFrameErrors = data.readLong();
+        this.receiveOverrunErrors = data.readLong();
+        this.receiveCRCErrors = data.readLong();
+        this.collisions = data.readLong();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        data.writeShort(this.portNumber);
+        data.writeShort((short) 0); // pad
+        data.writeInt(0); // pad
+        data.writeLong(this.receivePackets);
+        data.writeLong(this.transmitPackets);
+        data.writeLong(this.receiveBytes);
+        data.writeLong(this.transmitBytes);
+        data.writeLong(this.receiveDropped);
+        data.writeLong(this.transmitDropped);
+        data.writeLong(this.receiveErrors);
+        data.writeLong(this.transmitErrors);
+        data.writeLong(this.receiveFrameErrors);
+        data.writeLong(this.receiveOverrunErrors);
+        data.writeLong(this.receiveCRCErrors);
+        data.writeLong(this.collisions);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 431;
+        int result = 1;
+        result = prime * result + (int) (collisions ^ (collisions >>> 32));
+        result = prime * result + portNumber;
+        result = prime * result
+                + (int) (receivePackets ^ (receivePackets >>> 32));
+        result = prime * result + (int) (receiveBytes ^ (receiveBytes >>> 32));
+        result = prime * result
+                + (int) (receiveCRCErrors ^ (receiveCRCErrors >>> 32));
+        result = prime * result
+                + (int) (receiveDropped ^ (receiveDropped >>> 32));
+        result = prime * result
+                + (int) (receiveFrameErrors ^ (receiveFrameErrors >>> 32));
+        result = prime * result
+                + (int) (receiveOverrunErrors ^ (receiveOverrunErrors >>> 32));
+        result = prime * result
+                + (int) (receiveErrors ^ (receiveErrors >>> 32));
+        result = prime * result
+                + (int) (transmitBytes ^ (transmitBytes >>> 32));
+        result = prime * result
+                + (int) (transmitDropped ^ (transmitDropped >>> 32));
+        result = prime * result
+                + (int) (transmitErrors ^ (transmitErrors >>> 32));
+        result = prime * result
+                + (int) (transmitPackets ^ (transmitPackets >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFPortStatisticsReply)) {
+            return false;
+        }
+        OFPortStatisticsReply other = (OFPortStatisticsReply) obj;
+        if (collisions != other.collisions) {
+            return false;
+        }
+        if (portNumber != other.portNumber) {
+            return false;
+        }
+        if (receivePackets != other.receivePackets) {
+            return false;
+        }
+        if (receiveBytes != other.receiveBytes) {
+            return false;
+        }
+        if (receiveCRCErrors != other.receiveCRCErrors) {
+            return false;
+        }
+        if (receiveDropped != other.receiveDropped) {
+            return false;
+        }
+        if (receiveFrameErrors != other.receiveFrameErrors) {
+            return false;
+        }
+        if (receiveOverrunErrors != other.receiveOverrunErrors) {
+            return false;
+        }
+        if (receiveErrors != other.receiveErrors) {
+            return false;
+        }
+        if (transmitBytes != other.transmitBytes) {
+            return false;
+        }
+        if (transmitDropped != other.transmitDropped) {
+            return false;
+        }
+        if (transmitErrors != other.transmitErrors) {
+            return false;
+        }
+        if (transmitPackets != other.transmitPackets) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsRequest.java
new file mode 100644
index 0000000..c07a895
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsRequest.java
@@ -0,0 +1,88 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_port_stats_request structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFPortStatisticsRequest implements OFStatistics {
+    protected short portNumber;
+
+    /**
+     * @return the portNumber
+     */
+    public short getPortNumber() {
+        return portNumber;
+    }
+
+    /**
+     * @param portNumber the portNumber to set
+     */
+    public void setPortNumber(short portNumber) {
+        this.portNumber = portNumber;
+    }
+
+    @Override
+    public int getLength() {
+        return 8;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        this.portNumber = data.readShort();
+        data.readShort(); // pad
+        data.readInt(); // pad
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        data.writeShort(this.portNumber);
+        data.writeShort((short) 0); // pad
+        data.writeInt(0); // pad
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 433;
+        int result = 1;
+        result = prime * result + portNumber;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFPortStatisticsRequest)) {
+            return false;
+        }
+        OFPortStatisticsRequest other = (OFPortStatisticsRequest) obj;
+        if (portNumber != other.portNumber) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java
new file mode 100644
index 0000000..03cbb9c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java
@@ -0,0 +1,175 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_queue_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFQueueStatisticsReply implements OFStatistics {
+    protected short portNumber;
+    protected int queueId;
+    protected long transmitBytes;
+    protected long transmitPackets;
+    protected long transmitErrors;
+
+    /**
+     * @return the portNumber
+     */
+    public short getPortNumber() {
+        return portNumber;
+    }
+
+    /**
+     * @param portNumber the portNumber to set
+     */
+    public void setPortNumber(short portNumber) {
+        this.portNumber = portNumber;
+    }
+
+    /**
+     * @return the queueId
+     */
+    public int getQueueId() {
+        return queueId;
+    }
+
+    /**
+     * @param queueId the queueId to set
+     */
+    public void setQueueId(int queueId) {
+        this.queueId = queueId;
+    }
+
+    /**
+     * @return the transmitBytes
+     */
+    public long getTransmitBytes() {
+        return transmitBytes;
+    }
+
+    /**
+     * @param transmitBytes the transmitBytes to set
+     */
+    public void setTransmitBytes(long transmitBytes) {
+        this.transmitBytes = transmitBytes;
+    }
+
+    /**
+     * @return the transmitPackets
+     */
+    public long getTransmitPackets() {
+        return transmitPackets;
+    }
+
+    /**
+     * @param transmitPackets the transmitPackets to set
+     */
+    public void setTransmitPackets(long transmitPackets) {
+        this.transmitPackets = transmitPackets;
+    }
+
+    /**
+     * @return the transmitErrors
+     */
+    public long getTransmitErrors() {
+        return transmitErrors;
+    }
+
+    /**
+     * @param transmitErrors the transmitErrors to set
+     */
+    public void setTransmitErrors(long transmitErrors) {
+        this.transmitErrors = transmitErrors;
+    }
+
+    @Override
+    @JsonIgnore
+    public int getLength() {
+        return 32;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        this.portNumber = data.readShort();
+        data.readShort(); // pad
+        this.queueId = data.readInt();
+        this.transmitBytes = data.readLong();
+        this.transmitPackets = data.readLong();
+        this.transmitErrors = data.readLong();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        data.writeShort(this.portNumber);
+        data.writeShort((short) 0); // pad
+        data.writeInt(this.queueId);
+        data.writeLong(this.transmitBytes);
+        data.writeLong(this.transmitPackets);
+        data.writeLong(this.transmitErrors);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 439;
+        int result = 1;
+        result = prime * result + portNumber;
+        result = prime * result + queueId;
+        result = prime * result
+                + (int) (transmitBytes ^ (transmitBytes >>> 32));
+        result = prime * result
+                + (int) (transmitErrors ^ (transmitErrors >>> 32));
+        result = prime * result
+                + (int) (transmitPackets ^ (transmitPackets >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFQueueStatisticsReply)) {
+            return false;
+        }
+        OFQueueStatisticsReply other = (OFQueueStatisticsReply) obj;
+        if (portNumber != other.portNumber) {
+            return false;
+        }
+        if (queueId != other.queueId) {
+            return false;
+        }
+        if (transmitBytes != other.transmitBytes) {
+            return false;
+        }
+        if (transmitErrors != other.transmitErrors) {
+            return false;
+        }
+        if (transmitPackets != other.transmitPackets) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsRequest.java
new file mode 100644
index 0000000..3331453
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsRequest.java
@@ -0,0 +1,107 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_queue_stats_request structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFQueueStatisticsRequest implements OFStatistics {
+    protected short portNumber;
+    protected int queueId;
+
+    /**
+     * @return the portNumber
+     */
+    public short getPortNumber() {
+        return portNumber;
+    }
+
+    /**
+     * @param portNumber the portNumber to set
+     */
+    public void setPortNumber(short portNumber) {
+        this.portNumber = portNumber;
+    }
+
+    /**
+     * @return the queueId
+     */
+    public int getQueueId() {
+        return queueId;
+    }
+
+    /**
+     * @param queueId the queueId to set
+     */
+    public void setQueueId(int queueId) {
+        this.queueId = queueId;
+    }
+
+    @Override
+    public int getLength() {
+        return 8;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        this.portNumber = data.readShort();
+        data.readShort(); // pad
+        this.queueId = data.readInt();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        data.writeShort(this.portNumber);
+        data.writeShort((short) 0); // pad
+        data.writeInt(this.queueId);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 443;
+        int result = 1;
+        result = prime * result + portNumber;
+        result = prime * result + queueId;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFQueueStatisticsRequest)) {
+            return false;
+        }
+        OFQueueStatisticsRequest other = (OFQueueStatisticsRequest) obj;
+        if (portNumber != other.portNumber) {
+            return false;
+        }
+        if (queueId != other.queueId) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatistics.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatistics.java
new file mode 100644
index 0000000..5e8f4dd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatistics.java
@@ -0,0 +1,45 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * The base class for all OpenFlow statistics.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public interface OFStatistics {
+    /**
+     * Returns the wire length of this message in bytes
+     * @return the length
+     */
+    public int getLength();
+
+    /**
+     * Read this message off the wire from the specified ByteBuffer
+     * @param data
+     */
+    public void readFrom(ChannelBuffer data);
+
+    /**
+     * Write this message's binary format to the specified ByteBuffer
+     * @param data
+     */
+    public void writeTo(ChannelBuffer data);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatisticsType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatisticsType.java
new file mode 100644
index 0000000..f2b9e30
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatisticsType.java
@@ -0,0 +1,317 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+import java.lang.reflect.Constructor;
+
+import org.openflow.protocol.Instantiable;
+import org.openflow.protocol.OFType;
+
+public enum OFStatisticsType {
+    DESC        (0, OFDescriptionStatistics.class, OFDescriptionStatistics.class,
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFDescriptionStatistics();
+                        }
+                    },
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFDescriptionStatistics();
+                        }
+                    }),
+    FLOW       (1, OFFlowStatisticsRequest.class, OFFlowStatisticsReply.class,
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFFlowStatisticsRequest();
+                        }
+                    },
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFFlowStatisticsReply();
+                        }
+                    }),
+    AGGREGATE  (2, OFAggregateStatisticsRequest.class, OFAggregateStatisticsReply.class,
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFAggregateStatisticsRequest();
+                        }
+                    },
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFAggregateStatisticsReply();
+                        }
+                    }),
+    TABLE      (3, OFTableStatistics.class, OFTableStatistics.class,
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFTableStatistics();
+                        }
+                    },
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFTableStatistics();
+                        }
+                    }),
+    PORT       (4, OFPortStatisticsRequest.class, OFPortStatisticsReply.class,
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFPortStatisticsRequest();
+                        }
+                    },
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFPortStatisticsReply();
+                        }
+                    }),
+    QUEUE      (5, OFQueueStatisticsRequest.class, OFQueueStatisticsReply.class,
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFQueueStatisticsRequest();
+                        }
+                    },
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFQueueStatisticsReply();
+                        }
+                    }),
+    VENDOR     (0xffff, OFVendorStatistics.class, OFVendorStatistics.class,
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFVendorStatistics();
+                        }
+                    },
+                    new Instantiable<OFStatistics>() {
+                        @Override
+                        public OFStatistics instantiate() {
+                            return new OFVendorStatistics();
+                        }
+                    });
+
+    static OFStatisticsType[] requestMapping;
+    static OFStatisticsType[] replyMapping;
+
+    protected Class<? extends OFStatistics> requestClass;
+    protected Constructor<? extends OFStatistics> requestConstructor;
+    protected Instantiable<OFStatistics> requestInstantiable;
+    protected Class<? extends OFStatistics> replyClass;
+    protected Constructor<? extends OFStatistics> replyConstructor;
+    protected Instantiable<OFStatistics> replyInstantiable;
+    protected short type;
+
+    /**
+     * Store some information about the OpenFlow Statistic type, including wire
+     * protocol type number, and derived class
+     *
+     * @param type Wire protocol number associated with this OFStatisticsType
+     * @param requestClass The Statistics Java class to return when the
+     *                     containing OFType is STATS_REQUEST
+     * @param replyClass   The Statistics Java class to return when the
+     *                     containing OFType is STATS_REPLY
+     */
+    OFStatisticsType(int type, Class<? extends OFStatistics> requestClass,
+            Class<? extends OFStatistics> replyClass,
+            Instantiable<OFStatistics> requestInstantiable,
+            Instantiable<OFStatistics> replyInstantiable) {
+        this.type = (short) type;
+        this.requestClass = requestClass;
+        try {
+            this.requestConstructor = requestClass.getConstructor(new Class[]{});
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Failure getting constructor for class: " + requestClass, e);
+        }
+
+        this.replyClass = replyClass;
+        try {
+            this.replyConstructor = replyClass.getConstructor(new Class[]{});
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Failure getting constructor for class: " + replyClass, e);
+        }
+        this.requestInstantiable = requestInstantiable;
+        this.replyInstantiable = replyInstantiable;
+        OFStatisticsType.addMapping(this.type, OFType.STATS_REQUEST, this);
+        OFStatisticsType.addMapping(this.type, OFType.STATS_REPLY, this);
+    }
+
+    /**
+     * Adds a mapping from type value to OFStatisticsType enum
+     *
+     * @param i OpenFlow wire protocol type
+     * @param t type of containing OFMessage, only accepts STATS_REQUEST or
+     *          STATS_REPLY
+     * @param st type
+     */
+    static public void addMapping(short i, OFType t, OFStatisticsType st) {
+        if (i < 0)
+            i = (short) (16+i);
+        if (t == OFType.STATS_REQUEST) {
+            if (requestMapping == null)
+                requestMapping = new OFStatisticsType[16];
+            OFStatisticsType.requestMapping[i] = st;
+        } else if (t == OFType.STATS_REPLY){
+            if (replyMapping == null)
+                replyMapping = new OFStatisticsType[16];
+            OFStatisticsType.replyMapping[i] = st;
+        } else {
+            throw new RuntimeException(t.toString() + " is an invalid OFType");
+        }
+    }
+
+    /**
+     * Remove a mapping from type value to OFStatisticsType enum
+     *
+     * @param i OpenFlow wire protocol type
+     * @param t type of containing OFMessage, only accepts STATS_REQUEST or
+     *          STATS_REPLY
+     */
+    static public void removeMapping(short i, OFType t) {
+        if (i < 0)
+            i = (short) (16+i);
+        if (t == OFType.STATS_REQUEST) {
+            requestMapping[i] = null;
+        } else if (t == OFType.STATS_REPLY){
+            replyMapping[i] = null;
+        } else {
+            throw new RuntimeException(t.toString() + " is an invalid OFType");
+        }
+    }
+
+    /**
+     * Given a wire protocol OpenFlow type number, return the OFStatisticsType
+     * associated with it
+     *
+     * @param i wire protocol number
+     * @param t type of containing OFMessage, only accepts STATS_REQUEST or
+     *          STATS_REPLY
+     * @return OFStatisticsType enum type
+     */
+    static public OFStatisticsType valueOf(short i, OFType t) {
+        if (i < 0)
+            i = (short) (16+i);
+        if (t == OFType.STATS_REQUEST) {
+            return requestMapping[i];
+        } else if (t == OFType.STATS_REPLY){
+            return replyMapping[i];
+        } else {
+            throw new RuntimeException(t.toString() + " is an invalid OFType");
+        }
+    }
+
+    /**
+     * @return Returns the wire protocol value corresponding to this
+     * OFStatisticsType
+     */
+    public short getTypeValue() {
+        return this.type;
+    }
+
+    /**
+     * @param t type of containing OFMessage, only accepts STATS_REQUEST or
+     *          STATS_REPLY
+     * @return return the OFMessage subclass corresponding to this
+     *                OFStatisticsType
+     */
+    public Class<? extends OFStatistics> toClass(OFType t) {
+        if (t == OFType.STATS_REQUEST) {
+            return requestClass;
+        } else if (t == OFType.STATS_REPLY){
+            return replyClass;
+        } else {
+            throw new RuntimeException(t.toString() + " is an invalid OFType");
+        }
+    }
+
+    /**
+     * Returns the no-argument Constructor of the implementation class for
+     * this OFStatisticsType, either request or reply based on the supplied
+     * OFType
+     *
+     * @param t
+     * @return
+     */
+    public Constructor<? extends OFStatistics> getConstructor(OFType t) {
+        if (t == OFType.STATS_REQUEST) {
+            return requestConstructor;
+        } else if (t == OFType.STATS_REPLY) {
+            return replyConstructor;
+        } else {
+            throw new RuntimeException(t.toString() + " is an invalid OFType");
+        }
+    }
+
+    /**
+     * @return the requestInstantiable
+     */
+    public Instantiable<OFStatistics> getRequestInstantiable() {
+        return requestInstantiable;
+    }
+
+    /**
+     * @param requestInstantiable the requestInstantiable to set
+     */
+    public void setRequestInstantiable(
+            Instantiable<OFStatistics> requestInstantiable) {
+        this.requestInstantiable = requestInstantiable;
+    }
+
+    /**
+     * @return the replyInstantiable
+     */
+    public Instantiable<OFStatistics> getReplyInstantiable() {
+        return replyInstantiable;
+    }
+
+    /**
+     * @param replyInstantiable the replyInstantiable to set
+     */
+    public void setReplyInstantiable(Instantiable<OFStatistics> replyInstantiable) {
+        this.replyInstantiable = replyInstantiable;
+    }
+
+    /**
+     * Returns a new instance of the implementation class for
+     * this OFStatisticsType, either request or reply based on the supplied
+     * OFType
+     *
+     * @param t
+     * @return
+     */
+    public OFStatistics newInstance(OFType t) {
+        if (t == OFType.STATS_REQUEST) {
+            return requestInstantiable.instantiate();
+        } else if (t == OFType.STATS_REPLY) {
+            return replyInstantiable.instantiate();
+        } else {
+            throw new RuntimeException(t.toString() + " is an invalid OFType");
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFTableStatistics.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFTableStatistics.java
new file mode 100644
index 0000000..9e6d34e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFTableStatistics.java
@@ -0,0 +1,223 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.StringByteSerializer;
+
+/**
+ * Represents an ofp_table_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFTableStatistics implements OFStatistics {
+    public static int MAX_TABLE_NAME_LEN = 32;
+
+    protected byte tableId;
+    protected String name;
+    protected int wildcards;
+    protected int maximumEntries;
+    protected int activeCount;
+    protected long lookupCount;
+    protected long matchedCount;
+
+    /**
+     * @return the tableId
+     */
+    public byte getTableId() {
+        return tableId;
+    }
+
+    /**
+     * @param tableId the tableId to set
+     */
+    public void setTableId(byte tableId) {
+        this.tableId = tableId;
+    }
+
+    /**
+     * @return the name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return the wildcards
+     */
+    public int getWildcards() {
+        return wildcards;
+    }
+
+    /**
+     * @param wildcards the wildcards to set
+     */
+    public void setWildcards(int wildcards) {
+        this.wildcards = wildcards;
+    }
+
+    /**
+     * @return the maximumEntries
+     */
+    public int getMaximumEntries() {
+        return maximumEntries;
+    }
+
+    /**
+     * @param maximumEntries the maximumEntries to set
+     */
+    public void setMaximumEntries(int maximumEntries) {
+        this.maximumEntries = maximumEntries;
+    }
+
+    /**
+     * @return the activeCount
+     */
+    public int getActiveCount() {
+        return activeCount;
+    }
+
+    /**
+     * @param activeCount the activeCount to set
+     */
+    public void setActiveCount(int activeCount) {
+        this.activeCount = activeCount;
+    }
+
+    /**
+     * @return the lookupCount
+     */
+    public long getLookupCount() {
+        return lookupCount;
+    }
+
+    /**
+     * @param lookupCount the lookupCount to set
+     */
+    public void setLookupCount(long lookupCount) {
+        this.lookupCount = lookupCount;
+    }
+
+    /**
+     * @return the matchedCount
+     */
+    public long getMatchedCount() {
+        return matchedCount;
+    }
+
+    /**
+     * @param matchedCount the matchedCount to set
+     */
+    public void setMatchedCount(long matchedCount) {
+        this.matchedCount = matchedCount;
+    }
+
+    @Override
+    public int getLength() {
+        return 64;
+    }
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        this.tableId = data.readByte();
+        data.readByte(); // pad
+        data.readByte(); // pad
+        data.readByte(); // pad
+        this.name = StringByteSerializer.readFrom(data, MAX_TABLE_NAME_LEN);
+        this.wildcards = data.readInt();
+        this.maximumEntries = data.readInt();
+        this.activeCount = data.readInt();
+        this.lookupCount = data.readLong();
+        this.matchedCount = data.readLong();
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        data.writeByte(this.tableId);
+        data.writeByte((byte) 0); // pad
+        data.writeByte((byte) 0); // pad
+        data.writeByte((byte) 0); // pad
+        StringByteSerializer.writeTo(data, MAX_TABLE_NAME_LEN, this.name);
+        data.writeInt(this.wildcards);
+        data.writeInt(this.maximumEntries);
+        data.writeInt(this.activeCount);
+        data.writeLong(this.lookupCount);
+        data.writeLong(this.matchedCount);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 449;
+        int result = 1;
+        result = prime * result + activeCount;
+        result = prime * result + (int) (lookupCount ^ (lookupCount >>> 32));
+        result = prime * result + (int) (matchedCount ^ (matchedCount >>> 32));
+        result = prime * result + maximumEntries;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + tableId;
+        result = prime * result + wildcards;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFTableStatistics)) {
+            return false;
+        }
+        OFTableStatistics other = (OFTableStatistics) obj;
+        if (activeCount != other.activeCount) {
+            return false;
+        }
+        if (lookupCount != other.lookupCount) {
+            return false;
+        }
+        if (matchedCount != other.matchedCount) {
+            return false;
+        }
+        if (maximumEntries != other.maximumEntries) {
+            return false;
+        }
+        if (name == null) {
+            if (other.name != null) {
+                return false;
+            }
+        } else if (!name.equals(other.name)) {
+            return false;
+        }
+        if (tableId != other.tableId) {
+            return false;
+        }
+        if (wildcards != other.wildcards) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFVendorStatistics.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFVendorStatistics.java
new file mode 100644
index 0000000..0257a6a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFVendorStatistics.java
@@ -0,0 +1,83 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * The base class for vendor implemented statistics
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFVendorStatistics implements OFStatistics {
+    protected int vendor;
+    protected byte[] body;
+
+    // non-message fields
+    protected int length = 0;
+
+    @Override
+    public void readFrom(ChannelBuffer data) {
+        this.vendor = data.readInt();
+        if (body == null)
+            body = new byte[length - 4];
+        data.readBytes(body);
+    }
+
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        data.writeInt(this.vendor);
+        if (body != null)
+            data.writeBytes(body);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 457;
+        int result = 1;
+        result = prime * result + vendor;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OFVendorStatistics)) {
+            return false;
+        }
+        OFVendorStatistics other = (OFVendorStatistics) obj;
+        if (vendor != other.vendor) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int getLength() {
+        return length;
+    }
+
+    public void setLength(int length) {
+        this.length = length;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorDataType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorDataType.java
new file mode 100644
index 0000000..1f0e14b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorDataType.java
@@ -0,0 +1,71 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.vendor;
+
+import org.openflow.protocol.Instantiable;
+
+/**
+ * Subclass of OFVendorDataType that works with any vendor data format that
+ * begins with a integral value to indicate the format of the remaining data.
+ * It maps from the per-vendor-id integral data type code to the object
+ * used to instantiate the class associated with that vendor data type.
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFBasicVendorDataType extends OFVendorDataType {
+    
+    /**
+     * The data type value at the beginning of the vendor data.
+     */
+    protected long type;
+    
+    /**
+     * Construct an empty (i.e. no specified data type value) vendor data type.
+     */
+    public OFBasicVendorDataType() {
+        super();
+        this.type = 0;
+    }
+    
+    /**
+     * Store some information about the vendor data type, including wire protocol
+     * type number, derived class and instantiator.
+     *
+     * @param type Wire protocol number associated with this vendor data type
+     * @param instantiator An Instantiator<OFVendorData> implementation that
+     *              creates an instance of an appropriate subclass of OFVendorData.
+     */
+    public OFBasicVendorDataType(long type, Instantiable<OFVendorData> instantiator) {
+        super(instantiator);
+        this.type = type;
+    }
+
+    /**
+     * @return Returns the wire protocol value corresponding to this OFVendorDataType
+     */
+    public long getTypeValue() {
+        return this.type;
+    }
+    
+    /**
+     * @param type the wire protocol value for this data type
+     */
+    public void setTypeValue(long type) {
+        this.type = type;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorId.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorId.java
new file mode 100644
index 0000000..09365d7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorId.java
@@ -0,0 +1,162 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.vendor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.Instantiable;
+
+/**
+ * Basic subclass of OFVendorId that works with any vendor data format where
+ * the data begins with an integral data type value.
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFBasicVendorId extends OFVendorId {
+    
+    /**
+     * The size of the data type value at the beginning of all vendor
+     * data associated with this vendor id. The data type size must be
+     * either 1, 2, 4 or 8.
+     */
+    protected int dataTypeSize;
+    
+    /**
+     * Map of the vendor data types that have been registered for this
+     * vendor id.
+     */
+    protected Map<Long, OFBasicVendorDataType> dataTypeMap =
+            new HashMap<Long, OFBasicVendorDataType>();
+    
+    /**
+     * Construct an OFVendorId that where the vendor data begins
+     * with a data type value whose size is dataTypeSize.
+     * @param id the id of the vendor, typically the OUI of a vendor
+     *     prefixed with 0.
+     * @param dataTypeSize the size of the integral data type value
+     *     at the beginning of the vendor data. The value must be the
+     *     size of an integeral data type (i.e. either 1,2,4 or 8).
+     */
+    public OFBasicVendorId(int id, int dataTypeSize) {
+        super(id);
+        assert (dataTypeSize == 1) || (dataTypeSize == 2) ||
+               (dataTypeSize == 4) || (dataTypeSize == 8);
+        this.dataTypeSize = dataTypeSize;
+    }
+
+    /**
+     * Get the size of the data type value at the beginning of the vendor
+     * data. OFBasicVendorId assumes that this value is common across all of
+     * the vendor data formats associated with a given vendor id.
+     * @return
+     */
+    public int getDataTypeSize() {
+        return dataTypeSize;
+    }
+    
+    /**
+     * Register a vendor data type with this vendor id.
+     * @param vendorDataType
+     */
+    public void registerVendorDataType(OFBasicVendorDataType vendorDataType) {
+        dataTypeMap.put(vendorDataType.getTypeValue(), vendorDataType);
+    }
+    
+    /**
+     * Lookup the OFVendorDataType instance that has been registered with
+     * this vendor id.
+     * 
+     * @param vendorDataType the integer code that was parsed from the 
+     * @return
+     */
+    public OFVendorDataType lookupVendorDataType(int vendorDataType) {
+        return dataTypeMap.get(vendorDataType);
+    }
+
+    /**
+     * This function parses enough of the data from the buffer to be able
+     * to determine the appropriate OFVendorDataType for the data. It is meant
+     * to be a reasonably generic implementation that will work for most
+     * formats of vendor extensions. If the vendor data doesn't fit the
+     * assumptions listed below, then this method will need to be overridden
+     * to implement custom parsing.
+     * 
+     * This implementation assumes that the vendor data begins with a data
+     * type code that is used to distinguish different formats of vendor
+     * data associated with a particular vendor ID.
+     * The exact format of the data is vendor-defined, so we don't know how
+     * how big the code is (or really even if there is a code). This code
+     * assumes that the common case will be that the data does include
+     * an initial type code (i.e. so that the vendor can have multiple
+     * message/data types) and that the size is either 1, 2 or 4 bytes.
+     * The size of the initial type code is configured by the subclass of
+     * OFVendorId.
+     * 
+     * @param data the channel buffer containing the vendor data.
+     * @param length the length to the end of the enclosing message
+     * @return the OFVendorDataType that can be used to instantiate the
+     *         appropriate subclass of OFVendorData.
+     */
+    public OFVendorDataType parseVendorDataType(ChannelBuffer data, int length) {
+        OFVendorDataType vendorDataType = null;
+        
+        // Parse out the type code from the vendor data.
+        long dataTypeValue = 0;
+        if ((length == 0) || (length >= dataTypeSize)) {
+            switch (dataTypeSize) {
+                case 1:
+                    dataTypeValue = data.readByte();
+                    break;
+                case 2:
+                    dataTypeValue = data.readShort();
+                    break;
+                case 4:
+                    dataTypeValue = data.readInt();
+                    break;
+                case 8:
+                    dataTypeValue = data.readLong();
+                    break;
+                default:
+                    // This would be indicative of a coding error where the
+                    // dataTypeSize was specified incorrectly. This should have been
+                    // caught in the constructor for OFVendorId.
+                    assert false;
+            }
+            
+            vendorDataType = dataTypeMap.get(dataTypeValue);
+        }
+        
+        // If we weren't able to parse/map the data to a known OFVendorDataType,
+        // then map it to a generic vendor data type.
+        if (vendorDataType == null) {
+            vendorDataType = new OFBasicVendorDataType(dataTypeValue,
+                new Instantiable<OFVendorData>() {
+                    @Override
+                    public OFVendorData instantiate() {
+                        return new OFByteArrayVendorData();
+                    }
+                }
+            );
+        }
+        
+        return vendorDataType;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFByteArrayVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFByteArrayVendorData.java
new file mode 100644
index 0000000..08fa003
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFByteArrayVendorData.java
@@ -0,0 +1,94 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.vendor;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Basic implementation of OFVendorData that just treats the data as a
+ * byte array. This is used if there's an OFVendor message where there's
+ * no registered OFVendorId or no specific OFVendorDataType that can be
+ * determined from the data.
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFByteArrayVendorData implements OFVendorData {
+
+    protected byte[] bytes;
+    
+    /**
+     * Construct vendor data with an empty byte array.
+     */
+    public OFByteArrayVendorData() {
+    }
+    
+    /**
+     * Construct vendor data with the specified byte array.
+     * @param bytes
+     */
+    public OFByteArrayVendorData(byte[] bytes) {
+        this.bytes = bytes;
+    }
+    
+    /**
+     * Get the associated byte array for this vendor data.
+     * @return the byte array containing the raw vendor data.
+     */
+    public byte[] getBytes() {
+        return bytes;
+    }
+    
+    /**
+     * Set the byte array for the vendor data.
+     * @param bytes the raw byte array containing the vendor data.
+     */
+    public void setBytes(byte[] bytes) {
+        this.bytes = bytes;
+    }
+    
+    /**
+     * Get the length of the vendor data. In this case it's just then length
+     * of the underlying byte array.
+     * @return the length of the vendor data
+     */
+    @Override
+    public int getLength() {
+        return (bytes != null) ? bytes.length : 0;
+    }
+
+    /**
+     * Read the vendor data from the ChannelBuffer into the byte array.
+     * @param data the channel buffer from which we're deserializing
+     * @param length the length to the end of the enclosing message
+     */
+    @Override
+    public void readFrom(ChannelBuffer data, int length) {
+        bytes = new byte[length];
+        data.readBytes(bytes);
+    }
+
+    /**
+     * Write the vendor data bytes to the ChannelBuffer
+     * @param data the channel buffer to which we're serializing
+     */
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        if (bytes != null)
+            data.writeBytes(bytes);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorData.java
new file mode 100644
index 0000000..6dfb4e6
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorData.java
@@ -0,0 +1,44 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.vendor;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * The base class for all vendor data.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public interface OFVendorData {
+    /**
+     * @return length of the data
+     */
+    public int getLength();
+    
+    /**
+     * Read the vendor data from the specified ChannelBuffer
+     * @param data
+     */
+    public void readFrom(ChannelBuffer data, int length);
+
+    /**
+     * Write the vendor data to the specified ChannelBuffer
+     * @param data
+     */
+    public void writeTo(ChannelBuffer data);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorDataType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorDataType.java
new file mode 100644
index 0000000..ecae482
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorDataType.java
@@ -0,0 +1,79 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.vendor;
+
+import org.openflow.protocol.Instantiable;
+
+/**
+ * Class that represents a specific vendor data type format in an
+ * OFVendor message. Typically the vendor data will begin with an integer
+ * code that determines the format of the rest of the data, but this
+ * class does not assume that. It's basically just a holder for an
+ * instantiator of the appropriate subclass of OFVendorData.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFVendorDataType {
+
+    /**
+     * Object that instantiates the subclass of OFVendorData 
+     * associated with this data type.
+     */
+    protected Instantiable<OFVendorData> instantiable;
+
+    /**
+     * Construct an empty vendor data type.
+     */
+    public OFVendorDataType() {
+        super();
+    }
+
+    /**
+     * Construct a vendor data type with the specified instantiable.
+     * @param instantiable object that creates the subclass of OFVendorData
+     *     associated with this data type.
+     */
+    public OFVendorDataType(Instantiable<OFVendorData> instantiable) {
+        this.instantiable = instantiable;
+    }
+    
+    /**
+     * Returns a new instance of a subclass of OFVendorData associated with
+     * this OFVendorDataType.
+     * 
+     * @return the new object
+     */
+    public OFVendorData newInstance() {
+        return instantiable.instantiate();
+    }
+
+    /**
+     * @return the instantiable
+     */
+    public Instantiable<OFVendorData> getInstantiable() {
+        return instantiable;
+    }
+
+    /**
+     * @param instantiable the instantiable to set
+     */
+    public void setInstantiable(Instantiable<OFVendorData> instantiable) {
+        this.instantiable = instantiable;
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorId.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorId.java
new file mode 100644
index 0000000..f0af8a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorId.java
@@ -0,0 +1,85 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol.vendor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Base class for the vendor ID corresponding to vendor extensions from a
+ * given vendor. It is responsible for knowing how to parse out some sort of
+ * data type value from the vendor data in an OFVendor message so that we can
+ * dispatch to the different subclasses of OFVendorData corresponding to the
+ * different formats of data for the vendor extensions.
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public abstract class OFVendorId {
+    static Map<Integer, OFVendorId> mapping = new HashMap<Integer, OFVendorId>();
+
+    /**
+     * The vendor id value, typically the OUI of the vendor prefixed with 0.
+     */
+    protected int id;
+    
+    /**
+     * Register a new vendor id.
+     * @param vendorId the vendor id to register
+     */
+    public static void registerVendorId(OFVendorId vendorId) {
+        mapping.put(vendorId.getId(), vendorId);
+    }
+    
+    /**
+     * Lookup the OFVendorId instance corresponding to the given id value.
+     * @param id the integer vendor id value
+     * @return the corresponding OFVendorId that's been registered for the
+     *     given value, or null if there id has not been registered.
+     */
+    public static OFVendorId lookupVendorId(int id) {
+        return mapping.get(id);
+    }
+    
+    /**
+     * Create an OFVendorId with the give vendor id value
+     * @param id
+     */
+    public OFVendorId(int id) {
+        this.id = id;
+    }
+    
+    /**
+     * @return the vendor id value
+     */
+    public int getId() {
+        return id;
+    }
+    
+    /**
+     * This function parses enough of the data from the channel buffer to be
+     * able to determine the appropriate OFVendorDataType for the data.
+     * 
+     * @param data the channel buffer containing the vendor data.
+     * @param length the length to the end of the enclosing message
+     * @return the OFVendorDataType that can be used to instantiate the
+     *         appropriate subclass of OFVendorData.
+     */
+    public abstract OFVendorDataType parseVendorDataType(ChannelBuffer data, int length);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/HexString.java b/src/ext/floodlight/src/main/java/org/openflow/util/HexString.java
new file mode 100644
index 0000000..5777612
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/HexString.java
@@ -0,0 +1,92 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import java.math.BigInteger;
+
+public class HexString {
+    /**
+     * Convert a string of bytes to a ':' separated hex string
+     * @param bytes
+     * @return "0f:ca:fe:de:ad:be:ef"
+     */
+    public static String toHexString(byte[] bytes) {
+        int i;
+        String ret = "";
+        String tmp;
+        for(i=0; i< bytes.length; i++) {
+            if(i> 0)
+                ret += ":";
+            tmp = Integer.toHexString(U8.f(bytes[i]));
+            if (tmp.length() == 1)
+                ret += "0";
+            ret += tmp; 
+        }
+        return ret;
+    }
+    
+    public static String toHexString(long val, int padTo) {
+        char arr[] = Long.toHexString(val).toCharArray();
+        String ret = "";
+        // prepend the right number of leading zeros
+        int i = 0;
+        for (; i < (padTo * 2 - arr.length); i++) {
+            ret += "0";
+            if ((i % 2) == 1)
+                ret += ":";
+        }
+        for (int j = 0; j < arr.length; j++) {
+            ret += arr[j];
+            if ((((i + j) % 2) == 1) && (j < (arr.length - 1)))
+                ret += ":";
+        }
+        return ret;        
+    }
+   
+    public static String toHexString(long val) {
+        return toHexString(val, 8);
+    }
+    
+    
+    /**
+     * Convert a string of hex values into a string of bytes
+     * @param values "0f:ca:fe:de:ad:be:ef"
+     * @return [15, 5 ,2, 5, 17] 
+     * @throws NumberFormatException If the string can not be parsed
+     */ 
+    public static byte[] fromHexString(String values) throws NumberFormatException {
+        String[] octets = values.split(":");
+        byte[] ret = new byte[octets.length];
+        
+        for(int i = 0; i < octets.length; i++) {
+            if (octets[i].length() > 2)
+                throw new NumberFormatException("Invalid octet length");
+            ret[i] = Integer.valueOf(octets[i], 16).byteValue();
+        }
+        return ret;
+    }
+    
+    public static long toLong(String values) throws NumberFormatException {
+        // Long.parseLong() can't handle HexStrings with MSB set. Sigh. 
+        BigInteger bi = new BigInteger(values.replaceAll(":", ""),16);
+        if (bi.bitLength() > 64) 
+            throw new NumberFormatException("Input string too big to fit in long: " + values);
+        return bi.longValue();
+    }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/LRULinkedHashMap.java b/src/ext/floodlight/src/main/java/org/openflow/util/LRULinkedHashMap.java
new file mode 100644
index 0000000..7f05381
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/LRULinkedHashMap.java
@@ -0,0 +1,42 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import java.util.LinkedHashMap;
+
+public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {
+    private static final long serialVersionUID = -2964986094089626647L;
+    protected int maximumCapacity;
+
+    public LRULinkedHashMap(int initialCapacity, int maximumCapacity) {
+        super(initialCapacity, 0.75f, true);
+        this.maximumCapacity = maximumCapacity;
+    }
+
+    public LRULinkedHashMap(int maximumCapacity) {
+        super(16, 0.75f, true);
+        this.maximumCapacity = maximumCapacity;
+    }
+
+    @Override
+    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
+        if (this.size() > maximumCapacity)
+            return true;
+        return false;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/StringByteSerializer.java b/src/ext/floodlight/src/main/java/org/openflow/util/StringByteSerializer.java
new file mode 100644
index 0000000..9287fd2
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/StringByteSerializer.java
@@ -0,0 +1,58 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+public class StringByteSerializer {
+    public static String readFrom(ChannelBuffer data, int length) {
+        byte[] stringBytes = new byte[length];
+        data.readBytes(stringBytes);
+        // find the first index of 0
+        int index = 0;
+        for (byte b : stringBytes) {
+            if (0 == b)
+                break;
+            ++index;
+        }
+        return new String(Arrays.copyOf(stringBytes, index),
+                Charset.forName("ascii"));
+    }
+
+    public static void writeTo(ChannelBuffer data, int length, String value) {
+        try {
+            byte[] name = value.getBytes("ASCII");
+            if (name.length < length) {
+                data.writeBytes(name);
+                for (int i = name.length; i < length; ++i) {
+                    data.writeByte((byte) 0);
+                }
+            } else {
+                data.writeBytes(name, 0, length-1);
+                data.writeByte((byte) 0);
+            }
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/U16.java b/src/ext/floodlight/src/main/java/org/openflow/util/U16.java
new file mode 100644
index 0000000..0d8917d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/U16.java
@@ -0,0 +1,28 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+public class U16 {
+    public static int f(short i) {
+        return (int)i & 0xffff;
+    }
+
+    public static short t(int l) {
+        return (short) l;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/U32.java b/src/ext/floodlight/src/main/java/org/openflow/util/U32.java
new file mode 100644
index 0000000..3aab400
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/U32.java
@@ -0,0 +1,28 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+public class U32 {
+    public static long f(int i) {
+        return (long)i & 0xffffffffL;
+    }
+
+    public static int t(long l) {
+        return (int) l;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/U64.java b/src/ext/floodlight/src/main/java/org/openflow/util/U64.java
new file mode 100644
index 0000000..c6ae0f7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/U64.java
@@ -0,0 +1,30 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import java.math.BigInteger;
+
+public class U64 {
+    public static BigInteger f(long i) {
+        return new BigInteger(Long.toBinaryString(i), 2);
+    }
+
+    public static long t(BigInteger l) {
+        return l.longValue();
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/U8.java b/src/ext/floodlight/src/main/java/org/openflow/util/U8.java
new file mode 100644
index 0000000..0b575ad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/U8.java
@@ -0,0 +1,28 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+public class U8 {
+    public static short f(byte i) {
+        return (short) ((short)i & 0xff);
+    }
+
+    public static byte t(short l) {
+        return (byte) l;
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/Unsigned.java b/src/ext/floodlight/src/main/java/org/openflow/util/Unsigned.java
new file mode 100644
index 0000000..0754a3f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/Unsigned.java
@@ -0,0 +1,212 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+/*****
+ * A util library class for dealing with the lack of unsigned datatypes in Java
+ *
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+
+public class Unsigned {
+    /**
+     * Get an unsigned byte from the current position of the ByteBuffer
+     *
+     * @param bb ByteBuffer to get the byte from
+     * @return an unsigned byte contained in a short
+     */
+    public static short getUnsignedByte(ByteBuffer bb) {
+        return ((short) (bb.get() & (short) 0xff));
+    }
+
+    /**
+     * Get an unsigned byte from the specified offset in the ByteBuffer
+     *
+     * @param bb ByteBuffer to get the byte from
+     * @param offset the offset to get the byte from
+     * @return an unsigned byte contained in a short
+     */
+    public static short getUnsignedByte(ByteBuffer bb, int offset) {
+        return ((short) (bb.get(offset) & (short) 0xff));
+    }
+
+    /**
+     * Put an unsigned byte into the specified ByteBuffer at the current
+     * position
+     *
+     * @param bb ByteBuffer to put the byte into
+     * @param v the short containing the unsigned byte
+     */
+    public static void putUnsignedByte(ByteBuffer bb, short v) {
+        bb.put((byte) (v & 0xff));
+    }
+
+    /**
+     * Put an unsigned byte into the specified ByteBuffer at the specified
+     * offset
+     *
+     * @param bb ByteBuffer to put the byte into
+     * @param v the short containing the unsigned byte
+     * @param offset the offset to insert the unsigned byte at
+     */
+    public static void putUnsignedByte(ByteBuffer bb, short v, int offset) {
+        bb.put(offset, (byte) (v & 0xff));
+    }
+
+    /**
+     * Get an unsigned short from the current position of the ByteBuffer
+     *
+     * @param bb ByteBuffer to get the byte from
+     * @return an unsigned short contained in a int
+     */
+    public static int getUnsignedShort(ByteBuffer bb) {
+        return (bb.getShort() & 0xffff);
+    }
+
+    /**
+     * Get an unsigned short from the specified offset in the ByteBuffer
+     *
+     * @param bb ByteBuffer to get the short from
+     * @param offset the offset to get the short from
+     * @return an unsigned short contained in a int
+     */
+    public static int getUnsignedShort(ByteBuffer bb, int offset) {
+        return (bb.getShort(offset) & 0xffff);
+    }
+
+    /**
+     * Put an unsigned short into the specified ByteBuffer at the current
+     * position
+     *
+     * @param bb ByteBuffer to put the short into
+     * @param v the int containing the unsigned short
+     */
+    public static void putUnsignedShort(ByteBuffer bb, int v) {
+        bb.putShort((short) (v & 0xffff));
+    }
+
+    /**
+     * Put an unsigned short into the specified ByteBuffer at the specified
+     * offset
+     *
+     * @param bb ByteBuffer to put the short into
+     * @param v the int containing the unsigned short
+     * @param offset the offset to insert the unsigned short at
+     */
+    public static void putUnsignedShort(ByteBuffer bb, int v, int offset) {
+        bb.putShort(offset, (short) (v & 0xffff));
+    }
+
+    /**
+     * Get an unsigned int from the current position of the ByteBuffer
+     *
+     * @param bb ByteBuffer to get the int from
+     * @return an unsigned int contained in a long
+     */
+    public static long getUnsignedInt(ByteBuffer bb) {
+        return ((long) bb.getInt() & 0xffffffffL);
+    }
+
+    /**
+     * Get an unsigned int from the specified offset in the ByteBuffer
+     *
+     * @param bb ByteBuffer to get the int from
+     * @param offset the offset to get the int from
+     * @return an unsigned int contained in a long
+     */
+    public static long getUnsignedInt(ByteBuffer bb, int offset) {
+        return ((long) bb.getInt(offset) & 0xffffffffL);
+    }
+
+    /**
+     * Put an unsigned int into the specified ByteBuffer at the current position
+     *
+     * @param bb ByteBuffer to put the int into
+     * @param v the long containing the unsigned int
+     */
+    public static void putUnsignedInt(ByteBuffer bb, long v) {
+        bb.putInt((int) (v & 0xffffffffL));
+    }
+
+    /**
+     * Put an unsigned int into the specified ByteBuffer at the specified offset
+     *
+     * @param bb ByteBuffer to put the int into
+     * @param v the long containing the unsigned int
+     * @param offset the offset to insert the unsigned int at
+     */
+    public static void putUnsignedInt(ByteBuffer bb, long v, int offset) {
+        bb.putInt(offset, (int) (v & 0xffffffffL));
+    }
+
+    /**
+     * Get an unsigned long from the current position of the ByteBuffer
+     *
+     * @param bb ByteBuffer to get the long from
+     * @return an unsigned long contained in a BigInteger
+     */
+    public static BigInteger getUnsignedLong(ByteBuffer bb) {
+        byte[] v = new byte[8];
+        for (int i = 0; i < 8; ++i) {
+            v[i] = bb.get(i);
+        }
+        return new BigInteger(1, v);
+    }
+
+    /**
+     * Get an unsigned long from the specified offset in the ByteBuffer
+     *
+     * @param bb ByteBuffer to get the long from
+     * @param offset the offset to get the long from
+     * @return an unsigned long contained in a BigInteger
+     */
+    public static BigInteger getUnsignedLong(ByteBuffer bb, int offset) {
+        byte[] v = new byte[8];
+        for (int i = 0; i < 8; ++i) {
+            v[i] = bb.get(offset+i);
+        }
+        return new BigInteger(1, v);
+    }
+
+    /**
+     * Put an unsigned long into the specified ByteBuffer at the current
+     * position
+     *
+     * @param bb ByteBuffer to put the long into
+     * @param v the BigInteger containing the unsigned long
+     */
+    public static void putUnsignedLong(ByteBuffer bb, BigInteger v) {
+        bb.putLong(v.longValue());
+    }
+
+    /**
+     * Put an unsigned long into the specified ByteBuffer at the specified
+     * offset
+     *
+     * @param bb  ByteBuffer to put the long into
+     * @param v the BigInteger containing the unsigned long
+     * @param offset the offset to insert the unsigned long at
+     */
+    public static void putUnsignedLong(ByteBuffer bb, BigInteger v, int offset) {
+        bb.putLong(offset, v.longValue());
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFNiciraVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFNiciraVendorData.java
new file mode 100644
index 0000000..687d544
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFNiciraVendorData.java
@@ -0,0 +1,98 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.vendor.nicira;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.vendor.OFVendorData;
+
+/**
+ * Base class for vendor data corresponding to a Nicira vendor extension.
+ * Nicira vendor data always starts with a 4-byte integer data type value.
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFNiciraVendorData implements OFVendorData {
+
+    public static final int NX_VENDOR_ID = 0x00002320;
+    /**
+     * The value of the integer data type at the beginning of the vendor data
+     */
+    protected int dataType;
+    
+    /**
+     * Construct empty (i.e. unspecified data type) Nicira vendor data.
+     */
+    public OFNiciraVendorData() {
+    }
+    
+    /**
+     * Contruct Nicira vendor data with the specified data type
+     * @param dataType the data type value at the beginning of the vendor data.
+     */
+    public OFNiciraVendorData(int dataType) {
+        this.dataType = dataType;
+    }
+    
+    /**
+     * Get the data type value at the beginning of the vendor data
+     * @return the integer data type value
+     */
+    public int getDataType() {
+        return dataType;
+    }
+    
+    /**
+     * Set the data type value
+     * @param dataType the integer data type value at the beginning of the
+     *     vendor data.
+     */
+    public void setDataType(int dataType) {
+        this.dataType = dataType;
+    }
+    
+    /**
+     * Get the length of the vendor data. This implementation will normally
+     * be the superclass for another class that will override this to return
+     * the overall vendor data length. This implementation just returns the 
+     * length of the part that includes the 4-byte integer data type value
+     * at the beginning of the vendor data.
+     */
+    @Override
+    public int getLength() {
+        return 4;
+    }
+
+    /**
+     * Read the vendor data from the ChannelBuffer
+     * @param data the channel buffer from which we're deserializing
+     * @param length the length to the end of the enclosing message
+     */
+    @Override
+    public void readFrom(ChannelBuffer data, int length) {
+        dataType = data.readInt();
+    }
+
+    /**
+     * Write the vendor data to the ChannelBuffer
+     * @param data the channel buffer to which we're serializing
+     */
+    @Override
+    public void writeTo(ChannelBuffer data) {
+        data.writeInt(dataType);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleReplyVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleReplyVendorData.java
new file mode 100644
index 0000000..fa28c71
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleReplyVendorData.java
@@ -0,0 +1,66 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.vendor.nicira;
+
+import org.openflow.protocol.Instantiable;
+import org.openflow.protocol.vendor.OFVendorData;
+
+/**
+ * Subclass of OFVendorData representing the vendor data associated with
+ * a role reply vendor extension.
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFRoleReplyVendorData extends OFRoleVendorData {
+
+    protected static Instantiable<OFVendorData> instantiable =
+            new Instantiable<OFVendorData>() {
+                public OFVendorData instantiate() {
+                    return new OFRoleReplyVendorData();
+                }
+            };
+
+    /**
+     * @return a subclass of Instantiable<OFVendorData> that instantiates
+     *         an instance of OFRoleReplyVendorData.
+     */
+    public static Instantiable<OFVendorData> getInstantiable() {
+        return instantiable;
+    }
+
+    /**
+     * The data type value for a role reply
+     */
+    public static final int NXT_ROLE_REPLY = 11;
+
+    /**
+     * Construct a role reply vendor data with an unspecified role value.
+     */
+    public OFRoleReplyVendorData() {
+        super(NXT_ROLE_REPLY);
+    }
+    
+    /**
+     * Construct a role reply vendor data with the specified role value.
+     * @param role the role value for the role reply. Should be one of
+     *      NX_ROLE_OTHER, NX_ROLE_MASTER or NX_ROLE_SLAVE.
+     */
+    public OFRoleReplyVendorData(int role) {
+        super(NXT_ROLE_REPLY, role);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleRequestVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleRequestVendorData.java
new file mode 100644
index 0000000..e7dbe71
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleRequestVendorData.java
@@ -0,0 +1,66 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.vendor.nicira;
+
+import org.openflow.protocol.Instantiable;
+import org.openflow.protocol.vendor.OFVendorData;
+
+/**
+ * Subclass of OFVendorData representing the vendor data associated with
+ * a role request vendor extension.
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFRoleRequestVendorData extends OFRoleVendorData {
+
+    protected static Instantiable<OFVendorData> instantiable =
+            new Instantiable<OFVendorData>() {
+                public OFVendorData instantiate() {
+                    return new OFRoleRequestVendorData();
+                }
+            };
+
+    /**
+     * @return a subclass of Instantiable<OFVendorData> that instantiates
+     *         an instance of OFRoleRequestVendorData.
+     */
+    public static Instantiable<OFVendorData> getInstantiable() {
+        return instantiable;
+    }
+
+    /**
+     * The data type value for a role request
+     */
+    public static final int NXT_ROLE_REQUEST = 10;
+
+    /**
+     * Construct a role request vendor data with an unspecified role value.
+     */
+    public OFRoleRequestVendorData() {
+        super(NXT_ROLE_REQUEST);
+    }
+    
+    /**
+     * Construct a role request vendor data with the specified role value.
+     * @param role the role value for the role request. Should be one of
+     *      NX_ROLE_OTHER, NX_ROLE_MASTER or NX_ROLE_SLAVE.
+     */
+    public OFRoleRequestVendorData(int role) {
+        super(NXT_ROLE_REQUEST, role);
+    }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleVendorData.java
new file mode 100644
index 0000000..e7c8bf2
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleVendorData.java
@@ -0,0 +1,113 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson & Rob Sherwood, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.vendor.nicira;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Class that represents the vendor data in the role request
+ * extension implemented by Open vSwitch to support high availability.
+ * 
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFRoleVendorData extends OFNiciraVendorData {
+    
+    /**
+     * Role value indicating that the controller is in the OTHER role.
+     */
+    public static final int NX_ROLE_OTHER = 0;
+    
+    /**
+     * Role value indicating that the controller is in the MASTER role.
+     */
+    public static final int NX_ROLE_MASTER = 1;
+    
+    /**
+     * Role value indicating that the controller is in the SLAVE role.
+     */
+    public static final int NX_ROLE_SLAVE = 2;
+
+    protected int role;
+
+    /** 
+     * Construct an uninitialized OFRoleVendorData
+     */
+    public OFRoleVendorData() {
+        super();
+    }
+    
+    /**
+     * Construct an OFRoleVendorData with the specified data type
+     * (i.e. either request or reply) and an unspecified role.
+     * @param dataType
+     */
+    public OFRoleVendorData(int dataType) {
+        super(dataType);
+    }
+    
+    /**
+     * Construct an OFRoleVendorData with the specified data type
+     * (i.e. either request or reply) and role (i.e. one of of
+     * master, slave, or other).
+     * @param dataType either role request or role reply data type
+     */
+    public OFRoleVendorData(int dataType, int role) {
+        super(dataType);
+        this.role = role;
+    }
+    /**
+     * @return the role value of the role vendor data
+     */
+    public int getRole() {
+        return role;
+    }
+    
+    /**
+     * @param role the role value of the role vendor data
+     */
+    public void setRole(int role) {
+        this.role = role;
+    }
+
+    /**
+     * @return the total length of the role vendor data
+     */
+    @Override
+    public int getLength() {
+        return super.getLength() + 4;
+    }
+    
+    /**
+     * Read the role vendor data from the ChannelBuffer
+     * @param data the channel buffer from which we're deserializing
+     * @param length the length to the end of the enclosing message
+     */
+    public void readFrom(ChannelBuffer data, int length) {
+        super.readFrom(data, length);
+        role = data.readInt();
+    }
+
+    /**
+     * Write the role vendor data to the ChannelBuffer
+     * @param data the channel buffer to which we're serializing
+     */
+    public void writeTo(ChannelBuffer data) {
+        super.writeTo(data);
+        data.writeInt(role);
+    }
+}
diff --git a/src/ext/floodlight/src/main/python/PythonClient.py b/src/ext/floodlight/src/main/python/PythonClient.py
new file mode 100644
index 0000000..5c9890e
--- /dev/null
+++ b/src/ext/floodlight/src/main/python/PythonClient.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+import sys
+sys.path.append('../../../target/gen-py')
+
+from packetstreamer import PacketStreamer
+from packetstreamer.ttypes import *
+
+from thrift import Thrift
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+
+try:
+
+    # Make socket
+    transport = TSocket.TSocket('localhost', 9090)
+
+    # Buffering is critical. Raw sockets are very slow
+    transport = TTransport.TFramedTransport(transport)
+
+    # Wrap in a protocol
+    protocol = TBinaryProtocol.TBinaryProtocol(transport)
+
+    # Create a client to use the protocol encoder
+    client = PacketStreamer.Client(protocol)
+
+    # Connect!
+    transport.open()
+
+    while 1:
+        packets = client.getPackets("session1")
+        print 'session1 packets num: %d' % (len(packets))
+        count = 1
+        for packet in packets:
+            print "Packet %d: %s"% (count, packet)
+            if "FilterTimeout" in packet:
+                sys.exit()
+            count += 1 
+
+    # Close!
+    transport.close()
+
+except Thrift.TException, tx:
+    print '%s' % (tx.message)
+
+except KeyboardInterrupt, e:
+    print 'Bye-bye'
diff --git a/src/ext/floodlight/src/main/python/PythonServer.py b/src/ext/floodlight/src/main/python/PythonServer.py
new file mode 100644
index 0000000..c3c844e
--- /dev/null
+++ b/src/ext/floodlight/src/main/python/PythonServer.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+import sys
+import logging
+sys.path.append('../../../target/gen-py')
+
+from packetstreamer import PacketStreamer
+from packetstreamer.ttypes import *
+
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+from thrift.server import TServer
+
+class PacketStreamerHandler:
+  def __init__(self):
+    logging.handlers.codecs = None
+    self.log = logging.getLogger("packetstreamer")
+    self.log.setLevel(logging.DEBUG)
+    handler = logging.handlers.SysLogHandler("/dev/log")
+    handler.setFormatter(logging.Formatter("%(name)s: %(levelname)s %(message)s"))
+    self.log.addHandler(handler)
+
+  def ping(self):
+    self.log.debug('ping()')
+    return true
+
+  def pushPacketSync(self, packet):
+    self.log.debug('receive a packet synchronously: %s' %(packet))
+    return 0
+
+  def pushPacketAsync(self, packet):
+    self.log.debug('receive a packet Asynchronously: %s' %(packet))
+
+handler = PacketStreamerHandler()
+processor = PacketStreamer.Processor(handler)
+transport = TSocket.TServerSocket(9090)
+tfactory = TTransport.TBufferedTransportFactory()
+pfactory = TBinaryProtocol.TBinaryProtocolFactory()
+
+server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
+
+# You could do one of these for a multithreaded server
+#server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
+#server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
+
+print 'Starting the server...'
+server.serve()
+print 'done.'
diff --git a/src/ext/floodlight/src/main/python/compileall.py b/src/ext/floodlight/src/main/python/compileall.py
new file mode 100644
index 0000000..b21d95f
--- /dev/null
+++ b/src/ext/floodlight/src/main/python/compileall.py
@@ -0,0 +1,157 @@
+"""Module/script to "compile" all .py files to .pyc (or .pyo) file.
+
+When called as a script with arguments, this compiles the directories
+given as arguments recursively; the -l option prevents it from
+recursing into directories.
+
+Without arguments, if compiles all modules on sys.path, without
+recursing into subdirectories.  (Even though it should do so for
+packages -- for now, you'll have to deal with packages separately.)
+
+See module py_compile for details of the actual byte-compilation.
+
+"""
+
+import os
+import sys
+import py_compile
+
+__all__ = ["compile_dir","compile_path"]
+
+def compile_dir(dir, maxlevels=10, ddir=None,
+                force=0, rx=None, quiet=0):
+    """Byte-compile all modules in the given directory tree.
+
+    Arguments (only dir is required):
+
+    dir:       the directory to byte-compile
+    maxlevels: maximum recursion level (default 10)
+    ddir:      if given, purported directory name (this is the
+               directory name that will show up in error messages)
+    force:     if 1, force compilation, even if timestamps are up-to-date
+    quiet:     if 1, be quiet during compilation
+
+    """
+    if not quiet:
+        print 'Listing', dir, '...'
+    try:
+        names = os.listdir(dir)
+    except os.error:
+        print "Can't list", dir
+        names = []
+    names.sort()
+    success = 1
+    for name in names:
+        fullname = os.path.join(dir, name)
+        if ddir is not None:
+            dfile = os.path.join(ddir, name)
+        else:
+            dfile = None
+        if rx is not None:
+            mo = rx.search(fullname)
+            if mo:
+                continue
+        if os.path.isfile(fullname):
+            head, tail = name[:-3], name[-3:]
+            if tail == '.py':
+                cfile = fullname + (__debug__ and 'c' or 'o')
+                ftime = os.stat(fullname).st_mtime
+                try: ctime = os.stat(cfile).st_mtime
+                except os.error: ctime = 0
+                if (ctime > ftime) and not force: continue
+                if not quiet:
+                    print 'Compiling', fullname, '...'
+                try:
+                    ok = py_compile.compile(fullname, None, dfile, True)
+                except KeyboardInterrupt:
+                    raise KeyboardInterrupt
+                except py_compile.PyCompileError,err:
+                    if quiet:
+                        print 'Compiling', fullname, '...'
+                    print err.msg
+                    success = 0
+                except IOError, e:
+                    print "Sorry", e
+                    success = 0
+                else:
+                    if ok == 0:
+                        success = 0
+        elif maxlevels > 0 and \
+             name != os.curdir and name != os.pardir and \
+             os.path.isdir(fullname) and \
+             not os.path.islink(fullname):
+            if not compile_dir(fullname, maxlevels - 1, dfile, force, rx, quiet):
+                success = 0
+    return success
+
+def compile_path(skip_curdir=1, maxlevels=0, force=0, quiet=0):
+    """Byte-compile all module on sys.path.
+
+    Arguments (all optional):
+
+    skip_curdir: if true, skip current directory (default true)
+    maxlevels:   max recursion level (default 0)
+    force: as for compile_dir() (default 0)
+    quiet: as for compile_dir() (default 0)
+
+    """
+    success = 1
+    for dir in sys.path:
+        if (not dir or dir == os.curdir) and skip_curdir:
+            print 'Skipping current directory'
+        else:
+            success = success and compile_dir(dir, maxlevels, None,
+                                              force, quiet=quiet)
+    return success
+
+def main():
+    """Script main program."""
+    import getopt
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], 'lfqd:x:')
+    except getopt.error, msg:
+        print msg
+        print "usage: python compileall.py [-l] [-f] [-q] [-d destdir] " \
+              "[-x regexp] [directory ...]"
+        print "-l: don't recurse down"
+        print "-f: force rebuild even if timestamps are up-to-date"
+        print "-q: quiet operation"
+        print "-d destdir: purported directory name for error messages"
+        print "   if no directory arguments, -l sys.path is assumed"
+        print "-x regexp: skip files matching the regular expression regexp"
+        print "   the regexp is search for in the full path of the file"
+        sys.exit(2)
+    maxlevels = 10
+    ddir = None
+    force = 0
+    quiet = 0
+    rx = None
+    for o, a in opts:
+        if o == '-l': maxlevels = 0
+        if o == '-d': ddir = a
+        if o == '-f': force = 1
+        if o == '-q': quiet = 1
+        if o == '-x':
+            import re
+            rx = re.compile(a)
+    if ddir:
+        if len(args) != 1:
+            print "-d destdir require exactly one directory argument"
+            sys.exit(2)
+    success = 1
+    try:
+        if args:
+            for dir in args:
+                if not compile_dir(dir, maxlevels, ddir,
+                                   force, rx, quiet):
+                    success = 0
+        else:
+            success = compile_path()
+    except KeyboardInterrupt:
+        print "\n[interrupt]"
+        success = 0
+    return success
+
+if __name__ == '__main__':
+    exit_status = int(not main())
+    sys.exit(exit_status)
diff --git a/src/ext/floodlight/src/main/python/debugserver.py b/src/ext/floodlight/src/main/python/debugserver.py
new file mode 100644
index 0000000..d8c81f9
--- /dev/null
+++ b/src/ext/floodlight/src/main/python/debugserver.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+
+import sys
+from threading import currentThread
+from SocketServer import BaseRequestHandler, TCPServer
+from code import InteractiveConsole
+
+_locals = None
+
+class DebugLogger(object):
+    def do_print(self, *args):
+        for i in args:
+            print i,
+        print
+    info = do_print
+    warn = do_print
+    debug = do_print
+_log = DebugLogger()
+
+
+class DebugConsole(InteractiveConsole):
+    def __init__(self, request):
+        self.request = request
+        InteractiveConsole.__init__(self, _locals)
+
+    def raw_input(self, prompt):
+        self.request.send(prompt)
+        data = self.request.recv(10000).rstrip()
+        if len(data) == 1 and ord(data[0]) == 4:
+            sys.exit()
+        return data
+
+    def write(self, data):
+        self.request.send(str(data))
+
+    def write_nl(self, data):
+        self.write(str(data)+"\r\n")
+
+class DebugServerHandler(BaseRequestHandler):
+    def __init__(self, request, client_address, server):
+        currentThread()._thread.setName("debugserver-%s:%d" % client_address)
+        _log.debug('Open connection to DebugServer from %s:%d' % client_address)
+        BaseRequestHandler.__init__(self, request, client_address, server)
+
+    def handle(self):
+        console = DebugConsole(self.request)
+        sys.displayhook = console.write_nl
+        console.interact('DebugServer')
+        self.request.close()
+
+class DebugServer(TCPServer):
+    daemon_threads = True
+    allow_reuse_address = True
+
+    def handle_error(self, request, client_address):
+        _log.debug('Closing connection to DebugServer from %s:%d' % client_address)
+        request.close()
+
+def run_server(port=6655, host='0.0.0.0', locals=locals()):
+    currentThread()._thread.setName("debugserver-main")
+
+    global _locals
+    _locals = locals
+    if "log" in locals.keys():
+        global _log
+        _log = locals["log"]
+
+    _log.info("Starting DebugServer on port %d" % port)
+    server = DebugServer(('', port), DebugServerHandler)
+    try:
+        server.serve_forever()
+    except KeyboardInterrupt:
+        pass
+
+if __name__ == "__main__":
+    run_server()
diff --git a/src/ext/floodlight/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/ext/floodlight/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
new file mode 100644
index 0000000..0225a2b
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
@@ -0,0 +1,25 @@
+net.floodlightcontroller.core.FloodlightProvider
+net.floodlightcontroller.storage.memory.MemoryStorageSource
+net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl
+net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager
+net.floodlightcontroller.topology.TopologyManager
+net.floodlightcontroller.forwarding.Forwarding
+net.floodlightcontroller.flowcache.FlowReconcileManager
+net.floodlightcontroller.core.OFMessageFilterManager
+net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher
+net.floodlightcontroller.perfmon.PktInProcessingTime
+net.floodlightcontroller.perfmon.NullPktInProcessingTime
+net.floodlightcontroller.restserver.RestApiServer
+net.floodlightcontroller.learningswitch.LearningSwitch
+net.floodlightcontroller.hub.Hub
+net.floodlightcontroller.jython.JythonDebugInterface
+net.floodlightcontroller.counter.CounterStore
+net.floodlightcontroller.counter.NullCounterStore
+net.floodlightcontroller.threadpool.ThreadPool
+net.floodlightcontroller.ui.web.StaticWebRoutable
+net.floodlightcontroller.virtualnetwork.VirtualNetworkFilter
+net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier
+net.floodlightcontroller.devicemanager.test.MockDeviceManager
+net.floodlightcontroller.core.test.MockFloodlightProvider
+net.floodlightcontroller.core.test.MockThreadPoolService
+net.floodlightcontroller.firewall.Firewall
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/floodlightdefault.properties b/src/ext/floodlight/src/main/resources/floodlightdefault.properties
new file mode 100644
index 0000000..56d42eb
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/floodlightdefault.properties
@@ -0,0 +1,16 @@
+floodlight.modules = net.floodlightcontroller.storage.memory.MemoryStorageSource,\
+net.floodlightcontroller.core.FloodlightProvider,\
+net.floodlightcontroller.threadpool.ThreadPool,\
+net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl,\
+net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher,\
+net.floodlightcontroller.firewall.Firewall,\
+net.floodlightcontroller.forwarding.Forwarding,\
+net.floodlightcontroller.jython.JythonDebugInterface,\
+net.floodlightcontroller.counter.CounterStore,\
+net.floodlightcontroller.perfmon.PktInProcessingTime,\
+net.floodlightcontroller.ui.web.StaticWebRoutable
+net.floodlightcontroller.restserver.RestApiServer.port = 8080
+net.floodlightcontroller.core.FloodlightProvider.openflowport = 6633
+net.floodlightcontroller.jython.JythonDebugInterface.port = 6655
+net.floodlightcontroller.forwarding.Forwarding.idletimeout = 5
+net.floodlightcontroller.forwarding.Forwarding.hardtimeout = 0
diff --git a/src/ext/floodlight/src/main/resources/quantum.properties b/src/ext/floodlight/src/main/resources/quantum.properties
new file mode 100644
index 0000000..89c7543
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/quantum.properties
@@ -0,0 +1,15 @@
+# The default configuration for openstack
+floodlight.modules = net.floodlightcontroller.storage.memory.MemoryStorageSource,\
+net.floodlightcontroller.core.FloodlightProvider,\
+net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl,\
+net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher,\
+net.floodlightcontroller.forwarding.Forwarding,\
+net.floodlightcontroller.jython.JythonDebugInterface,\
+net.floodlightcontroller.counter.CounterStore,\
+net.floodlightcontroller.perfmon.PktInProcessingTime,\
+net.floodlightcontroller.ui.web.StaticWebRoutable,\
+net.floodlightcontroller.virtualnetwork.VirtualNetworkFilter,\
+net.floodlightcontroller.threadpool.ThreadPool
+net.floodlightcontroller.restserver.RestApiServer.port = 8080
+net.floodlightcontroller.core.FloodlightProvider.openflowport = 6633
+net.floodlightcontroller.jython.JythonDebugInterface.port = 6655
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/css/bootstrap.css b/src/ext/floodlight/src/main/resources/web/css/bootstrap.css
new file mode 100644
index 0000000..98f31f3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/css/bootstrap.css
@@ -0,0 +1,4000 @@
+/*!
+ * Bootstrap v2.0.2
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+  display: block;
+}
+audio,
+canvas,
+video {
+  display: inline-block;
+  *display: inline;
+  *zoom: 1;
+}
+audio:not([controls]) {
+  display: none;
+}
+html {
+  font-size: 100%;
+  -webkit-text-size-adjust: 100%;
+  -ms-text-size-adjust: 100%;
+}
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+a:hover,
+a:active {
+  outline: 0;
+}
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+sup {
+  top: -0.5em;
+}
+sub {
+  bottom: -0.25em;
+}
+img {
+  height: auto;
+  border: 0;
+  -ms-interpolation-mode: bicubic;
+  vertical-align: middle;
+}
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-size: 100%;
+  vertical-align: middle;
+}
+button,
+input {
+  *overflow: visible;
+  line-height: normal;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  cursor: pointer;
+  -webkit-appearance: button;
+}
+input[type="search"] {
+  -webkit-appearance: textfield;
+  -webkit-box-sizing: content-box;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+  -webkit-appearance: none;
+}
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+.clearfix {
+  *zoom: 1;
+}
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+.clearfix:after {
+  clear: both;
+}
+.hide-text {
+  overflow: hidden;
+  text-indent: 100%;
+  white-space: nowrap;
+}
+.input-block-level {
+  display: block;
+  width: 100%;
+  min-height: 28px;
+  /* Make inputs at least the height of their button counterpart */
+
+  /* Makes inputs behave like true block-level elements */
+
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  -ms-box-sizing: border-box;
+  box-sizing: border-box;
+}
+body {
+  margin: 0;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 13px;
+  line-height: 18px;
+  color: #0c028; /* #333333 */
+  background-color: white; /*  #ffffff; */
+}
+a {
+  color: #0088cc;
+  text-decoration: none;
+}
+a:hover {
+  color: #005580;
+  text-decoration: underline;
+}
+.row {
+  margin-left: -20px;
+  margin-top: 5px;
+  *zoom: 1;
+}
+.row:before,
+.row:after {
+  display: table;
+  content: "";
+}
+.row:after {
+  clear: both;
+}
+[class*="span"] {
+  float: left;
+  margin-left: 20px;
+}
+.container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+  width: 940px;
+}
+.span12 {
+  width: 940px;
+}
+.span11 {
+  width: 860px;
+}
+.span10 {
+  width: 780px;
+}
+.span9 {
+  width: 700px;
+}
+.span8 {
+  width: 620px;
+}
+.span7 {
+  width: 540px;
+}
+.span6 {
+  width: 460px;
+}
+.span5 {
+  width: 380px;
+}
+.span4 {
+  width: 300px;
+}
+.span3 {
+  width: 220px;
+}
+.span2 {
+  width: 140px;
+}
+.span1 {
+  width: 60px;
+}
+.offset12 {
+  margin-left: 980px;
+}
+.offset11 {
+  margin-left: 900px;
+}
+.offset10 {
+  margin-left: 820px;
+}
+.offset9 {
+  margin-left: 740px;
+}
+.offset8 {
+  margin-left: 660px;
+}
+.offset7 {
+  margin-left: 580px;
+}
+.offset6 {
+  margin-left: 500px;
+}
+.offset5 {
+  margin-left: 420px;
+}
+.offset4 {
+  margin-left: 340px;
+}
+.offset3 {
+  margin-left: 260px;
+}
+.offset2 {
+  margin-left: 180px;
+}
+.offset1 {
+  margin-left: 100px;
+}
+.row-fluid {
+  width: 100%;
+  *zoom: 1;
+}
+.row-fluid:before,
+.row-fluid:after {
+  display: table;
+  content: "";
+}
+.row-fluid:after {
+  clear: both;
+}
+.row-fluid > [class*="span"] {
+  float: left;
+  margin-left: 2.127659574%;
+}
+.row-fluid > [class*="span"]:first-child {
+  margin-left: 0;
+}
+.row-fluid > .span12 {
+  width: 99.99999998999999%;
+}
+.row-fluid > .span11 {
+  width: 91.489361693%;
+}
+.row-fluid > .span10 {
+  width: 82.97872339599999%;
+}
+.row-fluid > .span9 {
+  width: 74.468085099%;
+}
+.row-fluid > .span8 {
+  width: 65.95744680199999%;
+}
+.row-fluid > .span7 {
+  width: 57.446808505%;
+}
+.row-fluid > .span6 {
+  width: 48.93617020799999%;
+}
+.row-fluid > .span5 {
+  width: 40.425531911%;
+}
+.row-fluid > .span4 {
+  width: 31.914893614%;
+}
+.row-fluid > .span3 {
+  width: 23.404255317%;
+}
+.row-fluid > .span2 {
+  width: 14.89361702%;
+}
+.row-fluid > .span1 {
+  width: 6.382978723%;
+}
+.container {
+  margin-left: auto;
+  margin-right: auto;
+  *zoom: 1;
+}
+.container:before,
+.container:after {
+  display: table;
+  content: "";
+}
+.container:after {
+  clear: both;
+}
+.container-fluid {
+  padding-left: 20px;
+  padding-right: 20px;
+  *zoom: 1;
+}
+.container-fluid:before,
+.container-fluid:after {
+  display: table;
+  content: "";
+}
+.container-fluid:after {
+  clear: both;
+}
+p {
+  margin: 0 0 9px;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 13px;
+  line-height: 18px;
+}
+p small {
+  font-size: 11px;
+  color: #999999;
+}
+.lead {
+  margin-bottom: 18px;
+  font-size: 20px;
+  font-weight: 200;
+  line-height: 27px;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  margin: 0;
+  font-family: inherit;
+  font-weight: bold;
+  color: inherit;
+  text-rendering: optimizelegibility;
+}
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small {
+  font-weight: normal;
+  color: #999999;
+}
+h1 {
+  font-size: 23px;
+  line-height: 25px;
+}
+h1 small {
+  font-size: 18px;
+}
+h2 {
+  font-size: 18px;
+  line-height: 22px;
+}
+h2 small {
+  font-size: 18px;
+}
+h3 {
+  line-height: 18px;
+  font-size: 18px;
+}
+h3 small {
+  font-size: 14px;
+}
+h4,
+h5,
+h6 {
+  line-height: 16px;
+}
+h4 {
+  font-size: 14px;
+}
+h4 small {
+  font-size: 12px;
+}
+h5 {
+  font-size: 12px;
+}
+h6 {
+  font-size: 11px;
+  color: #999999;
+  text-transform: uppercase;
+}
+.page-header {
+  padding-bottom: 17px;
+  margin: 18px 0;
+  border-bottom: 1px solid #eeeeee;
+}
+.page-header h1 {
+  line-height: 1;
+}
+ul,
+ol {
+  padding: 0;
+  margin: 0 0 9px 25px;
+}
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+  margin-bottom: 0;
+}
+ul {
+  list-style: disc;
+}
+ol {
+  list-style: decimal;
+}
+li {
+  line-height: 18px;
+}
+ul.unstyled,
+ol.unstyled {
+  margin-left: 0;
+  list-style: none;
+}
+dl {
+  margin-bottom: 18px;
+}
+dt,
+dd {
+  line-height: 18px;
+}
+dt {
+  font-weight: bold;
+  line-height: 17px;
+}
+dd {
+  margin-left: 9px;
+}
+.dl-horizontal dt {
+  float: left;
+  clear: left;
+  width: 120px;
+  text-align: right;
+}
+.dl-horizontal dd {
+  margin-left: 130px;
+}
+hr {
+  margin: 18px 0;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+  border-bottom: 1px solid #ffffff;
+}
+strong {
+  font-weight: bold;
+}
+em {
+  font-style: italic;
+}
+.muted {
+  color: #999999;
+}
+abbr[title] {
+  border-bottom: 1px dotted #ddd;
+  cursor: help;
+}
+abbr.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+blockquote {
+  padding: 0 0 0 15px;
+  margin: 0 0 18px;
+  border-left: 5px solid #eeeeee;
+}
+blockquote p {
+  margin-bottom: 0;
+  font-size: 16px;
+  font-weight: 300;
+  line-height: 22.5px;
+}
+blockquote small {
+  display: block;
+  line-height: 18px;
+  color: #999999;
+}
+blockquote small:before {
+  content: '\2014 \00A0';
+}
+blockquote.pull-right {
+  float: right;
+  padding-left: 0;
+  padding-right: 15px;
+  border-left: 0;
+  border-right: 5px solid #eeeeee;
+}
+blockquote.pull-right p,
+blockquote.pull-right small {
+  text-align: right;
+}
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+  content: "";
+}
+address {
+  display: block;
+  margin-bottom: 18px;
+  line-height: 18px;
+  font-style: normal;
+}
+small {
+  font-size: 100%;
+}
+cite {
+  font-style: normal;
+}
+code,
+pre {
+  padding: 0 3px 2px;
+  font-family: Menlo, Monaco, "Courier New", monospace;
+  font-size: 12px;
+  color: #333333;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+code {
+  padding: 2px 4px;
+  color: #d14;
+  background-color: #f7f7f9;
+  border: 1px solid #e1e1e8;
+}
+pre {
+  display: block;
+  padding: 8.5px;
+  margin: 0 0 9px;
+  font-size: 12.025px;
+  line-height: 18px;
+  background-color: #f5f5f5;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  white-space: pre;
+  white-space: pre-wrap;
+  word-break: break-all;
+  word-wrap: break-word;
+}
+pre.prettyprint {
+  margin-bottom: 18px;
+}
+pre code {
+  padding: 0;
+  color: inherit;
+  background-color: transparent;
+  border: 0;
+}
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+form {
+  margin: 0 0 18px;
+}
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 27px;
+  font-size: 19.5px;
+  line-height: 36px;
+  color: #333333;
+  border: 0;
+  border-bottom: 1px solid #eee;
+}
+legend small {
+  font-size: 13.5px;
+  color: #999999;
+}
+label,
+input,
+button,
+select,
+textarea {
+  font-size: 13px;
+  font-weight: normal;
+  line-height: 18px;
+}
+input,
+button,
+select,
+textarea {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+label {
+  display: block;
+  margin-bottom: 5px;
+  color: #333333;
+}
+input,
+textarea,
+select,
+.uneditable-input {
+  display: inline-block;
+  width: 210px;
+  height: 18px;
+  padding: 4px;
+  margin-bottom: 9px;
+  font-size: 13px;
+  line-height: 18px;
+  color: #555555;
+  border: 1px solid #cccccc;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.uneditable-textarea {
+  width: auto;
+  height: auto;
+}
+label input,
+label textarea,
+label select {
+  display: block;
+}
+input[type="image"],
+input[type="checkbox"],
+input[type="radio"] {
+  width: auto;
+  height: auto;
+  padding: 0;
+  margin: 3px 0;
+  *margin-top: 0;
+  /* IE7 */
+
+  line-height: normal;
+  cursor: pointer;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+  border: 0 \9;
+  /* IE9 and down */
+
+}
+input[type="image"] {
+  border: 0;
+}
+input[type="file"] {
+  width: auto;
+  padding: initial;
+  line-height: initial;
+  border: initial;
+  background-color: #ffffff;
+  background-color: initial;
+  -webkit-box-shadow: none;
+  -moz-box-shadow: none;
+  box-shadow: none;
+}
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  width: auto;
+  height: auto;
+}
+select,
+input[type="file"] {
+  height: 28px;
+  /* In IE7, the height of the select element cannot be changed by height, only font-size */
+
+  *margin-top: 4px;
+  /* For IE7, add top margin to align select with labels */
+
+  line-height: 28px;
+}
+input[type="file"] {
+  line-height: 18px \9;
+}
+select {
+  width: 220px;
+  background-color: #ffffff;
+}
+select[multiple],
+select[size] {
+  height: auto;
+}
+input[type="image"] {
+  -webkit-box-shadow: none;
+  -moz-box-shadow: none;
+  box-shadow: none;
+}
+textarea {
+  height: auto;
+}
+input[type="hidden"] {
+  display: none;
+}
+.radio,
+.checkbox {
+  padding-left: 18px;
+}
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+  float: left;
+  margin-left: -18px;
+}
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+  padding-top: 5px;
+}
+.radio.inline,
+.checkbox.inline {
+  display: inline-block;
+  padding-top: 5px;
+  margin-bottom: 0;
+  vertical-align: middle;
+}
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+  margin-left: 10px;
+}
+input,
+textarea {
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+  -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+  -ms-transition: border linear 0.2s, box-shadow linear 0.2s;
+  -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+  transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+input:focus,
+textarea:focus {
+  border-color: rgba(82, 168, 236, 0.8);
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+  outline: 0;
+  outline: thin dotted \9;
+  /* IE6-9 */
+
+}
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus,
+select:focus {
+  -webkit-box-shadow: none;
+  -moz-box-shadow: none;
+  box-shadow: none;
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+.input-mini {
+  width: 60px;
+}
+.input-small {
+  width: 90px;
+}
+.input-medium {
+  width: 150px;
+}
+.input-large {
+  width: 210px;
+}
+.input-xlarge {
+  width: 270px;
+}
+.input-xxlarge {
+  width: 530px;
+}
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input {
+  float: none;
+  margin-left: 0;
+}
+input,
+textarea,
+.uneditable-input {
+  margin-left: 0;
+}
+input.span12, textarea.span12, .uneditable-input.span12 {
+  width: 930px;
+}
+input.span11, textarea.span11, .uneditable-input.span11 {
+  width: 850px;
+}
+input.span10, textarea.span10, .uneditable-input.span10 {
+  width: 770px;
+}
+input.span9, textarea.span9, .uneditable-input.span9 {
+  width: 690px;
+}
+input.span8, textarea.span8, .uneditable-input.span8 {
+  width: 610px;
+}
+input.span7, textarea.span7, .uneditable-input.span7 {
+  width: 530px;
+}
+input.span6, textarea.span6, .uneditable-input.span6 {
+  width: 450px;
+}
+input.span5, textarea.span5, .uneditable-input.span5 {
+  width: 370px;
+}
+input.span4, textarea.span4, .uneditable-input.span4 {
+  width: 290px;
+}
+input.span3, textarea.span3, .uneditable-input.span3 {
+  width: 210px;
+}
+input.span2, textarea.span2, .uneditable-input.span2 {
+  width: 130px;
+}
+input.span1, textarea.span1, .uneditable-input.span1 {
+  width: 50px;
+}
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+  background-color: #eeeeee;
+  border-color: #ddd;
+  cursor: not-allowed;
+}
+.control-group.warning > label,
+.control-group.warning .help-block,
+.control-group.warning .help-inline {
+  color: #c09853;
+}
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+  color: #c09853;
+  border-color: #c09853;
+}
+.control-group.warning input:focus,
+.control-group.warning select:focus,
+.control-group.warning textarea:focus {
+  border-color: #a47e3c;
+  -webkit-box-shadow: 0 0 6px #dbc59e;
+  -moz-box-shadow: 0 0 6px #dbc59e;
+  box-shadow: 0 0 6px #dbc59e;
+}
+.control-group.warning .input-prepend .add-on,
+.control-group.warning .input-append .add-on {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #c09853;
+}
+.control-group.error > label,
+.control-group.error .help-block,
+.control-group.error .help-inline {
+  color: #b94a48;
+}
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+  color: #b94a48;
+  border-color: #b94a48;
+}
+.control-group.error input:focus,
+.control-group.error select:focus,
+.control-group.error textarea:focus {
+  border-color: #953b39;
+  -webkit-box-shadow: 0 0 6px #d59392;
+  -moz-box-shadow: 0 0 6px #d59392;
+  box-shadow: 0 0 6px #d59392;
+}
+.control-group.error .input-prepend .add-on,
+.control-group.error .input-append .add-on {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #b94a48;
+}
+.control-group.success > label,
+.control-group.success .help-block,
+.control-group.success .help-inline {
+  color: #468847;
+}
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+  color: #468847;
+  border-color: #468847;
+}
+.control-group.success input:focus,
+.control-group.success select:focus,
+.control-group.success textarea:focus {
+  border-color: #356635;
+  -webkit-box-shadow: 0 0 6px #7aba7b;
+  -moz-box-shadow: 0 0 6px #7aba7b;
+  box-shadow: 0 0 6px #7aba7b;
+}
+.control-group.success .input-prepend .add-on,
+.control-group.success .input-append .add-on {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #468847;
+}
+input:focus:required:invalid,
+textarea:focus:required:invalid,
+select:focus:required:invalid {
+  color: #b94a48;
+  border-color: #ee5f5b;
+}
+input:focus:required:invalid:focus,
+textarea:focus:required:invalid:focus,
+select:focus:required:invalid:focus {
+  border-color: #e9322d;
+  -webkit-box-shadow: 0 0 6px #f8b9b7;
+  -moz-box-shadow: 0 0 6px #f8b9b7;
+  box-shadow: 0 0 6px #f8b9b7;
+}
+.form-actions {
+  padding: 17px 20px 18px;
+  margin-top: 18px;
+  margin-bottom: 18px;
+  background-color: #eeeeee;
+  border-top: 1px solid #ddd;
+  *zoom: 1;
+}
+.form-actions:before,
+.form-actions:after {
+  display: table;
+  content: "";
+}
+.form-actions:after {
+  clear: both;
+}
+.uneditable-input {
+  display: block;
+  background-color: #ffffff;
+  border-color: #eee;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+  -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+  cursor: not-allowed;
+}
+:-moz-placeholder {
+  color: #999999;
+}
+::-webkit-input-placeholder {
+  color: #999999;
+}
+.help-block,
+.help-inline {
+  color: #555555;
+}
+.help-block {
+  display: block;
+  margin-bottom: 9px;
+}
+.help-inline {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  vertical-align: middle;
+  padding-left: 5px;
+}
+.input-prepend,
+.input-append {
+  margin-bottom: 5px;
+}
+.input-prepend input,
+.input-append input,
+.input-prepend select,
+.input-append select,
+.input-prepend .uneditable-input,
+.input-append .uneditable-input {
+  *margin-left: 0;
+  -webkit-border-radius: 0 3px 3px 0;
+  -moz-border-radius: 0 3px 3px 0;
+  border-radius: 0 3px 3px 0;
+}
+.input-prepend input:focus,
+.input-append input:focus,
+.input-prepend select:focus,
+.input-append select:focus,
+.input-prepend .uneditable-input:focus,
+.input-append .uneditable-input:focus {
+  position: relative;
+  z-index: 2;
+}
+.input-prepend .uneditable-input,
+.input-append .uneditable-input {
+  border-left-color: #ccc;
+}
+.input-prepend .add-on,
+.input-append .add-on {
+  display: inline-block;
+  width: auto;
+  min-width: 16px;
+  height: 18px;
+  padding: 4px 5px;
+  font-weight: normal;
+  line-height: 18px;
+  text-align: center;
+  text-shadow: 0 1px 0 #ffffff;
+  vertical-align: middle;
+  background-color: #eeeeee;
+  border: 1px solid #ccc;
+}
+.input-prepend .add-on,
+.input-append .add-on,
+.input-prepend .btn,
+.input-append .btn {
+  -webkit-border-radius: 3px 0 0 3px;
+  -moz-border-radius: 3px 0 0 3px;
+  border-radius: 3px 0 0 3px;
+}
+.input-prepend .active,
+.input-append .active {
+  background-color: #a9dba9;
+  border-color: #46a546;
+}
+.input-prepend .add-on,
+.input-prepend .btn {
+  margin-right: -1px;
+}
+.input-append input,
+.input-append select .uneditable-input {
+  -webkit-border-radius: 3px 0 0 3px;
+  -moz-border-radius: 3px 0 0 3px;
+  border-radius: 3px 0 0 3px;
+}
+.input-append .uneditable-input {
+  border-left-color: #eee;
+  border-right-color: #ccc;
+}
+.input-append .add-on,
+.input-append .btn {
+  margin-left: -1px;
+  -webkit-border-radius: 0 3px 3px 0;
+  -moz-border-radius: 0 3px 3px 0;
+  border-radius: 0 3px 3px 0;
+}
+.input-prepend.input-append input,
+.input-prepend.input-append select,
+.input-prepend.input-append .uneditable-input {
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.input-prepend.input-append .add-on:first-child,
+.input-prepend.input-append .btn:first-child {
+  margin-right: -1px;
+  -webkit-border-radius: 3px 0 0 3px;
+  -moz-border-radius: 3px 0 0 3px;
+  border-radius: 3px 0 0 3px;
+}
+.input-prepend.input-append .add-on:last-child,
+.input-prepend.input-append .btn:last-child {
+  margin-left: -1px;
+  -webkit-border-radius: 0 3px 3px 0;
+  -moz-border-radius: 0 3px 3px 0;
+  border-radius: 0 3px 3px 0;
+}
+.search-query {
+  padding-left: 14px;
+  padding-right: 14px;
+  margin-bottom: 0;
+  -webkit-border-radius: 14px;
+  -moz-border-radius: 14px;
+  border-radius: 14px;
+}
+.form-search input,
+.form-inline input,
+.form-horizontal input,
+.form-search textarea,
+.form-inline textarea,
+.form-horizontal textarea,
+.form-search select,
+.form-inline select,
+.form-horizontal select,
+.form-search .help-inline,
+.form-inline .help-inline,
+.form-horizontal .help-inline,
+.form-search .uneditable-input,
+.form-inline .uneditable-input,
+.form-horizontal .uneditable-input,
+.form-search .input-prepend,
+.form-inline .input-prepend,
+.form-horizontal .input-prepend,
+.form-search .input-append,
+.form-inline .input-append,
+.form-horizontal .input-append {
+  display: inline-block;
+  margin-bottom: 0;
+}
+.form-search .hide,
+.form-inline .hide,
+.form-horizontal .hide {
+  display: none;
+}
+.form-search label,
+.form-inline label {
+  display: inline-block;
+}
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+  margin-bottom: 0;
+}
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+  padding-left: 0;
+  margin-bottom: 0;
+  vertical-align: middle;
+}
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+  float: left;
+  margin-left: 0;
+  margin-right: 3px;
+}
+.control-group {
+  margin-bottom: 9px;
+}
+legend + .control-group {
+  margin-top: 18px;
+  -webkit-margin-top-collapse: separate;
+}
+.form-horizontal .control-group {
+  margin-bottom: 18px;
+  *zoom: 1;
+}
+.form-horizontal .control-group:before,
+.form-horizontal .control-group:after {
+  display: table;
+  content: "";
+}
+.form-horizontal .control-group:after {
+  clear: both;
+}
+.form-horizontal .control-label {
+  float: left;
+  width: 140px;
+  padding-top: 5px;
+  text-align: right;
+}
+.form-horizontal .controls {
+  margin-left: 160px;
+  /* Super jank IE7 fix to ensure the inputs in .input-append and input-prepend don't inherit the margin of the parent, in this case .controls */
+
+  *display: inline-block;
+  *margin-left: 0;
+  *padding-left: 20px;
+}
+.form-horizontal .help-block {
+  margin-top: 9px;
+  margin-bottom: 0;
+}
+.form-horizontal .form-actions {
+  padding-left: 160px;
+}
+table {
+  max-width: 100%;
+  border-collapse: collapse;
+  border-spacing: 0;
+  background-color: transparent;
+}
+.table {
+  width: 100%;
+  margin-bottom: 18px;
+}
+.table th,
+.table td {
+  padding: 8px;
+  line-height: 18px;
+  text-align: left;
+  vertical-align: top;
+  border-top: 1px solid #dddddd;
+}
+.table th {
+  font-weight: bold;
+}
+.table thead th {
+  vertical-align: bottom;
+}
+.table colgroup + thead tr:first-child th,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child th,
+.table thead:first-child tr:first-child td {
+  border-top: 0;
+}
+.table tbody + tbody {
+  border-top: 2px solid #dddddd;
+}
+.table-condensed th,
+.table-condensed td {
+  padding: 4px 5px;
+}
+.table-bordered {
+  border: 1px solid #dddddd;
+  border-left: 0;
+  border-collapse: separate;
+  *border-collapse: collapsed;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.table-bordered th,
+.table-bordered td {
+  border-left: 1px solid #dddddd;
+}
+.table-bordered thead:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child td {
+  border-top: 0;
+}
+.table-bordered thead:first-child tr:first-child th:first-child,
+.table-bordered tbody:first-child tr:first-child td:first-child {
+  -webkit-border-radius: 4px 0 0 0;
+  -moz-border-radius: 4px 0 0 0;
+  border-radius: 4px 0 0 0;
+}
+.table-bordered thead:first-child tr:first-child th:last-child,
+.table-bordered tbody:first-child tr:first-child td:last-child {
+  -webkit-border-radius: 0 4px 0 0;
+  -moz-border-radius: 0 4px 0 0;
+  border-radius: 0 4px 0 0;
+}
+.table-bordered thead:last-child tr:last-child th:first-child,
+.table-bordered tbody:last-child tr:last-child td:first-child {
+  -webkit-border-radius: 0 0 0 4px;
+  -moz-border-radius: 0 0 0 4px;
+  border-radius: 0 0 0 4px;
+}
+.table-bordered thead:last-child tr:last-child th:last-child,
+.table-bordered tbody:last-child tr:last-child td:last-child {
+  -webkit-border-radius: 0 0 4px 0;
+  -moz-border-radius: 0 0 4px 0;
+  border-radius: 0 0 4px 0;
+}
+.table-striped tbody tr:nth-child(odd) td,
+.table-striped tbody tr:nth-child(odd) th {
+  background-color: #f9f9f9;
+}
+.table tbody tr:hover td,
+.table tbody tr:hover th {
+  background-color: #f5f5f5;
+}
+table .span1 {
+  float: none;
+  width: 44px;
+  margin-left: 0;
+}
+table .span2 {
+  float: none;
+  width: 124px;
+  margin-left: 0;
+}
+table .span3 {
+  float: none;
+  width: 204px;
+  margin-left: 0;
+}
+table .span4 {
+  float: none;
+  width: 284px;
+  margin-left: 0;
+}
+table .span5 {
+  float: none;
+  width: 364px;
+  margin-left: 0;
+}
+table .span6 {
+  float: none;
+  width: 444px;
+  margin-left: 0;
+}
+table .span7 {
+  float: none;
+  width: 524px;
+  margin-left: 0;
+}
+table .span8 {
+  float: none;
+  width: 604px;
+  margin-left: 0;
+}
+table .span9 {
+  float: none;
+  width: 684px;
+  margin-left: 0;
+}
+table .span10 {
+  float: none;
+  width: 764px;
+  margin-left: 0;
+}
+table .span11 {
+  float: none;
+  width: 844px;
+  margin-left: 0;
+}
+table .span12 {
+  float: none;
+  width: 924px;
+  margin-left: 0;
+}
+table .span13 {
+  float: none;
+  width: 1004px;
+  margin-left: 0;
+}
+table .span14 {
+  float: none;
+  width: 1084px;
+  margin-left: 0;
+}
+table .span15 {
+  float: none;
+  width: 1164px;
+  margin-left: 0;
+}
+table .span16 {
+  float: none;
+  width: 1244px;
+  margin-left: 0;
+}
+table .span17 {
+  float: none;
+  width: 1324px;
+  margin-left: 0;
+}
+table .span18 {
+  float: none;
+  width: 1404px;
+  margin-left: 0;
+}
+table .span19 {
+  float: none;
+  width: 1484px;
+  margin-left: 0;
+}
+table .span20 {
+  float: none;
+  width: 1564px;
+  margin-left: 0;
+}
+table .span21 {
+  float: none;
+  width: 1644px;
+  margin-left: 0;
+}
+table .span22 {
+  float: none;
+  width: 1724px;
+  margin-left: 0;
+}
+table .span23 {
+  float: none;
+  width: 1804px;
+  margin-left: 0;
+}
+table .span24 {
+  float: none;
+  width: 1884px;
+  margin-left: 0;
+}
+[class^="icon-"],
+[class*=" icon-"] {
+  display: inline-block;
+  width: 14px;
+  height: 14px;
+  line-height: 14px;
+  vertical-align: text-top;
+  background-image: url("../img/glyphicons-halflings.png");
+  background-position: 14px 14px;
+  background-repeat: no-repeat;
+  *margin-right: .3em;
+}
+[class^="icon-"]:last-child,
+[class*=" icon-"]:last-child {
+  *margin-left: 0;
+}
+.icon-white {
+  background-image: url("../img/glyphicons-halflings-white.png");
+}
+.icon-glass {
+  background-position: 0      0;
+}
+.icon-music {
+  background-position: -24px 0;
+}
+.icon-search {
+  background-position: -48px 0;
+}
+.icon-envelope {
+  background-position: -72px 0;
+}
+.icon-heart {
+  background-position: -96px 0;
+}
+.icon-star {
+  background-position: -120px 0;
+}
+.icon-star-empty {
+  background-position: -144px 0;
+}
+.icon-user {
+  background-position: -168px 0;
+}
+.icon-film {
+  background-position: -192px 0;
+}
+.icon-th-large {
+  background-position: -216px 0;
+}
+.icon-th {
+  background-position: -240px 0;
+}
+.icon-th-list {
+  background-position: -264px 0;
+}
+.icon-ok {
+  background-position: -288px 0;
+}
+.icon-remove {
+  background-position: -312px 0;
+}
+.icon-zoom-in {
+  background-position: -336px 0;
+}
+.icon-zoom-out {
+  background-position: -360px 0;
+}
+.icon-off {
+  background-position: -384px 0;
+}
+.icon-signal {
+  background-position: -408px 0;
+}
+.icon-cog {
+  background-position: -432px 0;
+}
+.icon-trash {
+  background-position: -456px 0;
+}
+.icon-home {
+  background-position: 0 -24px;
+}
+.icon-file {
+  background-position: -24px -24px;
+}
+.icon-time {
+  background-position: -48px -24px;
+}
+.icon-road {
+  background-position: -72px -24px;
+}
+.icon-download-alt {
+  background-position: -96px -24px;
+}
+.icon-download {
+  background-position: -120px -24px;
+}
+.icon-upload {
+  background-position: -144px -24px;
+}
+.icon-inbox {
+  background-position: -168px -24px;
+}
+.icon-play-circle {
+  background-position: -192px -24px;
+}
+.icon-repeat {
+  background-position: -216px -24px;
+}
+.icon-refresh {
+  background-position: -240px -24px;
+}
+.icon-list-alt {
+  background-position: -264px -24px;
+}
+.icon-lock {
+  background-position: -287px -24px;
+}
+.icon-flag {
+  background-position: -312px -24px;
+}
+.icon-headphones {
+  background-position: -336px -24px;
+}
+.icon-volume-off {
+  background-position: -360px -24px;
+}
+.icon-volume-down {
+  background-position: -384px -24px;
+}
+.icon-volume-up {
+  background-position: -408px -24px;
+}
+.icon-qrcode {
+  background-position: -432px -24px;
+}
+.icon-barcode {
+  background-position: -456px -24px;
+}
+.icon-tag {
+  background-position: 0 -48px;
+}
+.icon-tags {
+  background-position: -25px -48px;
+}
+.icon-book {
+  background-position: -48px -48px;
+}
+.icon-bookmark {
+  background-position: -72px -48px;
+}
+.icon-print {
+  background-position: -96px -48px;
+}
+.icon-camera {
+  background-position: -120px -48px;
+}
+.icon-font {
+  background-position: -144px -48px;
+}
+.icon-bold {
+  background-position: -167px -48px;
+}
+.icon-italic {
+  background-position: -192px -48px;
+}
+.icon-text-height {
+  background-position: -216px -48px;
+}
+.icon-text-width {
+  background-position: -240px -48px;
+}
+.icon-align-left {
+  background-position: -264px -48px;
+}
+.icon-align-center {
+  background-position: -288px -48px;
+}
+.icon-align-right {
+  background-position: -312px -48px;
+}
+.icon-align-justify {
+  background-position: -336px -48px;
+}
+.icon-list {
+  background-position: -360px -48px;
+}
+.icon-indent-left {
+  background-position: -384px -48px;
+}
+.icon-indent-right {
+  background-position: -408px -48px;
+}
+.icon-facetime-video {
+  background-position: -432px -48px;
+}
+.icon-picture {
+  background-position: -456px -48px;
+}
+.icon-pencil {
+  background-position: 0 -72px;
+}
+.icon-map-marker {
+  background-position: -24px -72px;
+}
+.icon-adjust {
+  background-position: -48px -72px;
+}
+.icon-tint {
+  background-position: -72px -72px;
+}
+.icon-edit {
+  background-position: -96px -72px;
+}
+.icon-share {
+  background-position: -120px -72px;
+}
+.icon-check {
+  background-position: -144px -72px;
+}
+.icon-move {
+  background-position: -168px -72px;
+}
+.icon-step-backward {
+  background-position: -192px -72px;
+}
+.icon-fast-backward {
+  background-position: -216px -72px;
+}
+.icon-backward {
+  background-position: -240px -72px;
+}
+.icon-play {
+  background-position: -264px -72px;
+}
+.icon-pause {
+  background-position: -288px -72px;
+}
+.icon-stop {
+  background-position: -312px -72px;
+}
+.icon-forward {
+  background-position: -336px -72px;
+}
+.icon-fast-forward {
+  background-position: -360px -72px;
+}
+.icon-step-forward {
+  background-position: -384px -72px;
+}
+.icon-eject {
+  background-position: -408px -72px;
+}
+.icon-chevron-left {
+  background-position: -432px -72px;
+}
+.icon-chevron-right {
+  background-position: -456px -72px;
+}
+.icon-plus-sign {
+  background-position: 0 -96px;
+}
+.icon-minus-sign {
+  background-position: -24px -96px;
+}
+.icon-remove-sign {
+  background-position: -48px -96px;
+}
+.icon-ok-sign {
+  background-position: -72px -96px;
+}
+.icon-question-sign {
+  background-position: -96px -96px;
+}
+.icon-info-sign {
+  background-position: -120px -96px;
+}
+.icon-screenshot {
+  background-position: -144px -96px;
+}
+.icon-remove-circle {
+  background-position: -168px -96px;
+}
+.icon-ok-circle {
+  background-position: -192px -96px;
+}
+.icon-ban-circle {
+  background-position: -216px -96px;
+}
+.icon-arrow-left {
+  background-position: -240px -96px;
+}
+.icon-arrow-right {
+  background-position: -264px -96px;
+}
+.icon-arrow-up {
+  background-position: -289px -96px;
+}
+.icon-arrow-down {
+  background-position: -312px -96px;
+}
+.icon-share-alt {
+  background-position: -336px -96px;
+}
+.icon-resize-full {
+  background-position: -360px -96px;
+}
+.icon-resize-small {
+  background-position: -384px -96px;
+}
+.icon-plus {
+  background-position: -408px -96px;
+}
+.icon-minus {
+  background-position: -433px -96px;
+}
+.icon-asterisk {
+  background-position: -456px -96px;
+}
+.icon-exclamation-sign {
+  background-position: 0 -120px;
+}
+.icon-gift {
+  background-position: -24px -120px;
+}
+.icon-leaf {
+  background-position: -48px -120px;
+}
+.icon-fire {
+  background-position: -72px -120px;
+}
+.icon-eye-open {
+  background-position: -96px -120px;
+}
+.icon-eye-close {
+  background-position: -120px -120px;
+}
+.icon-warning-sign {
+  background-position: -144px -120px;
+}
+.icon-plane {
+  background-position: -168px -120px;
+}
+.icon-calendar {
+  background-position: -192px -120px;
+}
+.icon-random {
+  background-position: -216px -120px;
+}
+.icon-comment {
+  background-position: -240px -120px;
+}
+.icon-magnet {
+  background-position: -264px -120px;
+}
+.icon-chevron-up {
+  background-position: -288px -120px;
+}
+.icon-chevron-down {
+  background-position: -313px -119px;
+}
+.icon-retweet {
+  background-position: -336px -120px;
+}
+.icon-shopping-cart {
+  background-position: -360px -120px;
+}
+.icon-folder-close {
+  background-position: -384px -120px;
+}
+.icon-folder-open {
+  background-position: -408px -120px;
+}
+.icon-resize-vertical {
+  background-position: -432px -119px;
+}
+.icon-resize-horizontal {
+  background-position: -456px -118px;
+}
+.dropdown {
+  position: relative;
+}
+.dropdown-toggle {
+  *margin-bottom: -3px;
+}
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+  outline: 0;
+}
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  vertical-align: top;
+  border-left: 4px solid transparent;
+  border-right: 4px solid transparent;
+  border-top: 4px solid #000000;
+  opacity: 0.3;
+  filter: alpha(opacity=30);
+  content: "";
+}
+.dropdown .caret {
+  margin-top: 8px;
+  margin-left: 2px;
+}
+.dropdown:hover .caret,
+.open.dropdown .caret {
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  float: left;
+  display: none;
+  min-width: 160px;
+  padding: 4px 0;
+  margin: 0;
+  list-style: none;
+  background-color: #ffffff;
+  border-color: #ccc;
+  border-color: rgba(0, 0, 0, 0.2);
+  border-style: solid;
+  border-width: 1px;
+  -webkit-border-radius: 0 0 5px 5px;
+  -moz-border-radius: 0 0 5px 5px;
+  border-radius: 0 0 5px 5px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -webkit-background-clip: padding-box;
+  -moz-background-clip: padding;
+  background-clip: padding-box;
+  *border-right-width: 2px;
+  *border-bottom-width: 2px;
+}
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+.dropdown-menu .divider {
+  height: 1px;
+  margin: 8px 1px;
+  overflow: hidden;
+  background-color: #e5e5e5;
+  border-bottom: 1px solid #ffffff;
+  *width: 100%;
+  *margin: -5px 0 5px;
+}
+.dropdown-menu a {
+  display: block;
+  padding: 3px 15px;
+  clear: both;
+  font-weight: normal;
+  line-height: 18px;
+  color: #333333;
+  white-space: nowrap;
+}
+.dropdown-menu li > a:hover,
+.dropdown-menu .active > a,
+.dropdown-menu .active > a:hover {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #0088cc;
+}
+.dropdown.open {
+  *z-index: 1000;
+}
+.dropdown.open .dropdown-toggle {
+  color: #ffffff;
+  background: #ccc;
+  background: rgba(0, 0, 0, 0.3);
+}
+.dropdown.open .dropdown-menu {
+  display: block;
+}
+.pull-right .dropdown-menu {
+  left: auto;
+  right: 0;
+}
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  border-top: 0;
+  border-bottom: 4px solid #000000;
+  content: "\2191";
+}
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 1px;
+}
+.typeahead {
+  margin-top: 2px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #eee;
+  border: 1px solid rgba(0, 0, 0, 0.05);
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, 0.15);
+}
+.well-large {
+  padding: 24px;
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+}
+.well-small {
+  padding: 9px;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.fade {
+  -webkit-transition: opacity 0.15s linear;
+  -moz-transition: opacity 0.15s linear;
+  -ms-transition: opacity 0.15s linear;
+  -o-transition: opacity 0.15s linear;
+  transition: opacity 0.15s linear;
+  opacity: 0;
+}
+.fade.in {
+  opacity: 1;
+}
+.collapse {
+  -webkit-transition: height 0.35s ease;
+  -moz-transition: height 0.35s ease;
+  -ms-transition: height 0.35s ease;
+  -o-transition: height 0.35s ease;
+  transition: height 0.35s ease;
+  position: relative;
+  overflow: hidden;
+  height: 0;
+}
+.collapse.in {
+  height: auto;
+}
+.close {
+  float: right;
+  font-size: 20px;
+  font-weight: bold;
+  line-height: 18px;
+  color: #000000;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.2;
+  filter: alpha(opacity=20);
+}
+.close:hover {
+  color: #000000;
+  text-decoration: none;
+  opacity: 0.4;
+  filter: alpha(opacity=40);
+  cursor: pointer;
+}
+.btn {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  padding: 4px 10px 4px;
+  margin-bottom: 0;
+  font-size: 13px;
+  line-height: 18px;
+  color: #333333;
+  text-align: center;
+  text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+  vertical-align: middle;
+  background-color: #f5f5f5;
+  background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+  background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: linear-gradient(top, #ffffff, #e6e6e6);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
+  border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+  border: 1px solid #cccccc;
+  border-bottom-color: #b3b3b3;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+  -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+  cursor: pointer;
+  *margin-left: .3em;
+}
+.btn:hover,
+.btn:active,
+.btn.active,
+.btn.disabled,
+.btn[disabled] {
+  background-color: #e6e6e6;
+}
+.btn:active,
+.btn.active {
+  background-color: #cccccc \9;
+}
+.btn:first-child {
+  *margin-left: 0;
+}
+.btn:hover {
+  color: #333333;
+  text-decoration: none;
+  background-color: #e6e6e6;
+  background-position: 0 -15px;
+  -webkit-transition: background-position 0.1s linear;
+  -moz-transition: background-position 0.1s linear;
+  -ms-transition: background-position 0.1s linear;
+  -o-transition: background-position 0.1s linear;
+  transition: background-position 0.1s linear;
+}
+.btn:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+.btn.active,
+.btn:active {
+  background-image: none;
+  -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+  -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+  background-color: #e6e6e6;
+  background-color: #d9d9d9 \9;
+  outline: 0;
+}
+.btn.disabled,
+.btn[disabled] {
+  cursor: default;
+  background-image: none;
+  background-color: #e6e6e6;
+  opacity: 0.65;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+  -moz-box-shadow: none;
+  box-shadow: none;
+}
+.btn-large {
+  padding: 9px 14px;
+  font-size: 15px;
+  line-height: normal;
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+}
+.btn-large [class^="icon-"] {
+  margin-top: 1px;
+}
+.btn-small {
+  padding: 5px 9px;
+  font-size: 11px;
+  line-height: 16px;
+}
+.btn-small [class^="icon-"] {
+  margin-top: -1px;
+}
+.btn-mini {
+  padding: 2px 6px;
+  font-size: 11px;
+  line-height: 14px;
+}
+.btn-primary,
+.btn-primary:hover,
+.btn-warning,
+.btn-warning:hover,
+.btn-danger,
+.btn-danger:hover,
+.btn-success,
+.btn-success:hover,
+.btn-info,
+.btn-info:hover,
+.btn-inverse,
+.btn-inverse:hover {
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  color: #ffffff;
+}
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+  color: rgba(255, 255, 255, 0.75);
+}
+.btn-primary {
+  background-color: #0074cc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0055cc);
+  background-image: -ms-linear-gradient(top, #0088cc, #0055cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0055cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0055cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0055cc);
+  background-image: linear-gradient(top, #0088cc, #0055cc);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0055cc', GradientType=0);
+  border-color: #0055cc #0055cc #003580;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-primary:hover,
+.btn-primary:active,
+.btn-primary.active,
+.btn-primary.disabled,
+.btn-primary[disabled] {
+  background-color: #0055cc;
+}
+.btn-primary:active,
+.btn-primary.active {
+  background-color: #004099 \9;
+}
+.btn-warning {
+  background-color: #faa732;
+  background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+  background-image: -ms-linear-gradient(top, #fbb450, #f89406);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+  background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+  background-image: -o-linear-gradient(top, #fbb450, #f89406);
+  background-image: linear-gradient(top, #fbb450, #f89406);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);
+  border-color: #f89406 #f89406 #ad6704;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-warning:hover,
+.btn-warning:active,
+.btn-warning.active,
+.btn-warning.disabled,
+.btn-warning[disabled] {
+  background-color: #f89406;
+}
+.btn-warning:active,
+.btn-warning.active {
+  background-color: #c67605 \9;
+}
+.btn-danger {
+  background-color: #da4f49;
+  background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -ms-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+  background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: linear-gradient(top, #ee5f5b, #bd362f);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);
+  border-color: #bd362f #bd362f #802420;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-danger:hover,
+.btn-danger:active,
+.btn-danger.active,
+.btn-danger.disabled,
+.btn-danger[disabled] {
+  background-color: #bd362f;
+}
+.btn-danger:active,
+.btn-danger.active {
+  background-color: #942a25 \9;
+}
+.btn-success {
+  background-color: #5bb75b;
+  background-image: -moz-linear-gradient(top, #62c462, #51a351);
+  background-image: -ms-linear-gradient(top, #62c462, #51a351);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+  background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+  background-image: -o-linear-gradient(top, #62c462, #51a351);
+  background-image: linear-gradient(top, #62c462, #51a351);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);
+  border-color: #51a351 #51a351 #387038;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-success:hover,
+.btn-success:active,
+.btn-success.active,
+.btn-success.disabled,
+.btn-success[disabled] {
+  background-color: #51a351;
+}
+.btn-success:active,
+.btn-success.active {
+  background-color: #408140 \9;
+}
+.btn-info {
+  background-color: #49afcd;
+  background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: -ms-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+  background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: linear-gradient(top, #5bc0de, #2f96b4);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);
+  border-color: #2f96b4 #2f96b4 #1f6377;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-info:hover,
+.btn-info:active,
+.btn-info.active,
+.btn-info.disabled,
+.btn-info[disabled] {
+  background-color: #2f96b4;
+}
+.btn-info:active,
+.btn-info.active {
+  background-color: #24748c \9;
+}
+.btn-inverse {
+  background-color: #414141;
+  background-image: -moz-linear-gradient(top, #555555, #222222);
+  background-image: -ms-linear-gradient(top, #555555, #222222);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222));
+  background-image: -webkit-linear-gradient(top, #555555, #222222);
+  background-image: -o-linear-gradient(top, #555555, #222222);
+  background-image: linear-gradient(top, #555555, #222222);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0);
+  border-color: #222222 #222222 #000000;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-inverse:hover,
+.btn-inverse:active,
+.btn-inverse.active,
+.btn-inverse.disabled,
+.btn-inverse[disabled] {
+  background-color: #222222;
+}
+.btn-inverse:active,
+.btn-inverse.active {
+  background-color: #080808 \9;
+}
+button.btn,
+input[type="submit"].btn {
+  *padding-top: 2px;
+  *padding-bottom: 2px;
+}
+button.btn::-moz-focus-inner,
+input[type="submit"].btn::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+button.btn.btn-large,
+input[type="submit"].btn.btn-large {
+  *padding-top: 7px;
+  *padding-bottom: 7px;
+}
+button.btn.btn-small,
+input[type="submit"].btn.btn-small {
+  *padding-top: 3px;
+  *padding-bottom: 3px;
+}
+button.btn.btn-mini,
+input[type="submit"].btn.btn-mini {
+  *padding-top: 1px;
+  *padding-bottom: 1px;
+}
+.btn-group {
+  position: relative;
+  *zoom: 1;
+  *margin-left: .3em;
+}
+.btn-group:before,
+.btn-group:after {
+  display: table;
+  content: "";
+}
+.btn-group:after {
+  clear: both;
+}
+.btn-group:first-child {
+  *margin-left: 0;
+}
+.btn-group + .btn-group {
+  margin-left: 5px;
+}
+.btn-toolbar {
+  margin-top: 9px;
+  margin-bottom: 9px;
+}
+.btn-toolbar .btn-group {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+}
+.btn-group .btn {
+  position: relative;
+  float: left;
+  margin-left: -1px;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.btn-group .btn:first-child {
+  margin-left: 0;
+  -webkit-border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+  border-top-left-radius: 4px;
+  -webkit-border-bottom-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  border-bottom-left-radius: 4px;
+}
+.btn-group .btn:last-child,
+.btn-group .dropdown-toggle {
+  -webkit-border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  border-top-right-radius: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+  border-bottom-right-radius: 4px;
+}
+.btn-group .btn.large:first-child {
+  margin-left: 0;
+  -webkit-border-top-left-radius: 6px;
+  -moz-border-radius-topleft: 6px;
+  border-top-left-radius: 6px;
+  -webkit-border-bottom-left-radius: 6px;
+  -moz-border-radius-bottomleft: 6px;
+  border-bottom-left-radius: 6px;
+}
+.btn-group .btn.large:last-child,
+.btn-group .large.dropdown-toggle {
+  -webkit-border-top-right-radius: 6px;
+  -moz-border-radius-topright: 6px;
+  border-top-right-radius: 6px;
+  -webkit-border-bottom-right-radius: 6px;
+  -moz-border-radius-bottomright: 6px;
+  border-bottom-right-radius: 6px;
+}
+.btn-group .btn:hover,
+.btn-group .btn:focus,
+.btn-group .btn:active,
+.btn-group .btn.active {
+  z-index: 2;
+}
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+.btn-group .dropdown-toggle {
+  padding-left: 8px;
+  padding-right: 8px;
+  -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+  -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+  *padding-top: 3px;
+  *padding-bottom: 3px;
+}
+.btn-group .btn-mini.dropdown-toggle {
+  padding-left: 5px;
+  padding-right: 5px;
+  *padding-top: 1px;
+  *padding-bottom: 1px;
+}
+.btn-group .btn-small.dropdown-toggle {
+  *padding-top: 4px;
+  *padding-bottom: 4px;
+}
+.btn-group .btn-large.dropdown-toggle {
+  padding-left: 12px;
+  padding-right: 12px;
+}
+.btn-group.open {
+  *z-index: 1000;
+}
+.btn-group.open .dropdown-menu {
+  display: block;
+  margin-top: 1px;
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+}
+.btn-group.open .dropdown-toggle {
+  background-image: none;
+  -webkit-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+  -moz-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+  box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.btn .caret {
+  margin-top: 7px;
+  margin-left: 0;
+}
+.btn:hover .caret,
+.open.btn-group .caret {
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+.btn-mini .caret {
+  margin-top: 5px;
+}
+.btn-small .caret {
+  margin-top: 6px;
+}
+.btn-large .caret {
+  margin-top: 6px;
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+  border-top: 5px solid #000000;
+}
+.btn-primary .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret,
+.btn-success .caret,
+.btn-inverse .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+  opacity: 0.75;
+  filter: alpha(opacity=75);
+}
+.alert {
+  padding: 8px 35px 8px 14px;
+  margin-bottom: 18px;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+  background-color: #fcf8e3;
+  border: 1px solid #fbeed5;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  color: #c09853;
+}
+.alert-heading {
+  color: inherit;
+}
+.alert .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  line-height: 18px;
+}
+.alert-success {
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+  color: #468847;
+}
+.alert-danger,
+.alert-error {
+  background-color: #f2dede;
+  border-color: #eed3d7;
+  color: #b94a48;
+}
+.alert-info {
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+  color: #3a87ad;
+}
+.alert-block {
+  padding-top: 14px;
+  padding-bottom: 14px;
+}
+.alert-block > p,
+.alert-block > ul {
+  margin-bottom: 0;
+}
+.alert-block p + p {
+  margin-top: 5px;
+}
+.nav {
+  margin-left: 0;
+  margin-bottom: 18px;
+  list-style: none;
+}
+.nav > li > a {
+  display: block;
+}
+.nav > li > a:hover {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+.nav .nav-header {
+  display: block;
+  padding: 3px 15px;
+  font-size: 11px;
+  font-weight: bold;
+  line-height: 18px;
+  color: white;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+  text-transform: uppercase;
+}
+.nav li + .nav-header {
+  margin-top: 9px;
+}
+.nav-list {
+  padding-left: 15px;
+  padding-right: 15px;
+  margin-bottom: 0;
+}
+.nav-list > li > a,
+.nav-list .nav-header {
+  margin-left: -15px;
+  margin-right: -15px;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+.nav-list > li > a {
+  padding: 3px 15px;
+}
+.nav-list > .active > a,
+.nav-list > .active > a:hover {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+  background-color: #0088cc;
+}
+.nav-list [class^="icon-"] {
+  margin-right: 2px;
+}
+.nav-list .divider {
+  height: 1px;
+  margin: 8px 1px;
+  overflow: hidden;
+  background-color: #e5e5e5;
+  border-bottom: 1px solid #ffffff;
+  *width: 100%;
+  *margin: -5px 0 5px;
+}
+.nav-tabs,
+.nav-pills {
+  *zoom: 1;
+}
+.nav-tabs:before,
+.nav-pills:before,
+.nav-tabs:after,
+.nav-pills:after {
+  display: table;
+  content: "";
+}
+.nav-tabs:after,
+.nav-pills:after {
+  clear: both;
+}
+.nav-tabs > li,
+.nav-pills > li {
+  float: left;
+}
+.nav-tabs > li > a,
+.nav-pills > li > a {
+  padding-right: 12px;
+  padding-left: 12px;
+  margin-right: 2px;
+  line-height: 14px;
+}
+.nav-tabs {
+  border-bottom: 1px solid #ddd;
+}
+.nav-tabs > li {
+  margin-bottom: -1px;
+}
+.nav-tabs > li > a {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  line-height: 18px;
+  border: 1px solid transparent;
+  -webkit-border-radius: 4px 4px 0 0;
+  -moz-border-radius: 4px 4px 0 0;
+  border-radius: 4px 4px 0 0;
+}
+.nav-tabs > li > a:hover {
+  border-color: #eeeeee #eeeeee #dddddd;
+}
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover {
+  color: #555555;
+  background-color: #ffffff;
+  border: 1px solid #ddd;
+  border-bottom-color: transparent;
+  cursor: default;
+}
+.nav-pills > li > a {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  margin-top: 2px;
+  margin-bottom: 2px;
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+}
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover {
+  color: #ffffff;
+  background-color: #0088cc;
+}
+.nav-stacked > li {
+  float: none;
+}
+.nav-stacked > li > a {
+  margin-right: 0;
+}
+.nav-tabs.nav-stacked {
+  border-bottom: 0;
+}
+.nav-tabs.nav-stacked > li > a {
+  border: 1px solid #ddd;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.nav-tabs.nav-stacked > li:first-child > a {
+  -webkit-border-radius: 4px 4px 0 0;
+  -moz-border-radius: 4px 4px 0 0;
+  border-radius: 4px 4px 0 0;
+}
+.nav-tabs.nav-stacked > li:last-child > a {
+  -webkit-border-radius: 0 0 4px 4px;
+  -moz-border-radius: 0 0 4px 4px;
+  border-radius: 0 0 4px 4px;
+}
+.nav-tabs.nav-stacked > li > a:hover {
+  border-color: #ddd;
+  z-index: 2;
+}
+.nav-pills.nav-stacked > li > a {
+  margin-bottom: 3px;
+}
+.nav-pills.nav-stacked > li:last-child > a {
+  margin-bottom: 1px;
+}
+.nav-tabs .dropdown-menu,
+.nav-pills .dropdown-menu {
+  margin-top: 1px;
+  border-width: 1px;
+}
+.nav-pills .dropdown-menu {
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.nav-tabs .dropdown-toggle .caret,
+.nav-pills .dropdown-toggle .caret {
+  border-top-color: #0088cc;
+  border-bottom-color: #0088cc;
+  margin-top: 6px;
+}
+.nav-tabs .dropdown-toggle:hover .caret,
+.nav-pills .dropdown-toggle:hover .caret {
+  border-top-color: #005580;
+  border-bottom-color: #005580;
+}
+.nav-tabs .active .dropdown-toggle .caret,
+.nav-pills .active .dropdown-toggle .caret {
+  border-top-color: #333333;
+  border-bottom-color: #333333;
+}
+.nav > .dropdown.active > a:hover {
+  color: #000000;
+  cursor: pointer;
+}
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > .open.active > a:hover {
+  color: #ffffff;
+  background-color: #999999;
+  border-color: #999999;
+}
+.nav .open .caret,
+.nav .open.active .caret,
+.nav .open a:hover .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+.tabs-stacked .open > a:hover {
+  border-color: #999999;
+}
+.tabbable {
+  *zoom: 1;
+}
+.tabbable:before,
+.tabbable:after {
+  display: table;
+  content: "";
+}
+.tabbable:after {
+  clear: both;
+}
+.tab-content {
+  display: table;
+  width: 100%;
+}
+.tabs-below .nav-tabs,
+.tabs-right .nav-tabs,
+.tabs-left .nav-tabs {
+  border-bottom: 0;
+}
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+  display: none;
+}
+.tab-content > .active,
+.pill-content > .active {
+  display: block;
+}
+.tabs-below .nav-tabs {
+  border-top: 1px solid #ddd;
+}
+.tabs-below .nav-tabs > li {
+  margin-top: -1px;
+  margin-bottom: 0;
+}
+.tabs-below .nav-tabs > li > a {
+  -webkit-border-radius: 0 0 4px 4px;
+  -moz-border-radius: 0 0 4px 4px;
+  border-radius: 0 0 4px 4px;
+}
+.tabs-below .nav-tabs > li > a:hover {
+  border-bottom-color: transparent;
+  border-top-color: #ddd;
+}
+.tabs-below .nav-tabs .active > a,
+.tabs-below .nav-tabs .active > a:hover {
+  border-color: transparent #ddd #ddd #ddd;
+}
+.tabs-left .nav-tabs > li,
+.tabs-right .nav-tabs > li {
+  float: none;
+}
+.tabs-left .nav-tabs > li > a,
+.tabs-right .nav-tabs > li > a {
+  min-width: 74px;
+  margin-right: 0;
+  margin-bottom: 3px;
+}
+.tabs-left .nav-tabs {
+  float: left;
+  margin-right: 19px;
+  border-right: 1px solid #ddd;
+}
+.tabs-left .nav-tabs > li > a {
+  margin-right: -1px;
+  -webkit-border-radius: 4px 0 0 4px;
+  -moz-border-radius: 4px 0 0 4px;
+  border-radius: 4px 0 0 4px;
+}
+.tabs-left .nav-tabs > li > a:hover {
+  border-color: #eeeeee #dddddd #eeeeee #eeeeee;
+}
+.tabs-left .nav-tabs .active > a,
+.tabs-left .nav-tabs .active > a:hover {
+  border-color: #ddd transparent #ddd #ddd;
+  *border-right-color: #ffffff;
+}
+.tabs-right .nav-tabs {
+  float: right;
+  margin-left: 19px;
+  border-left: 1px solid #ddd;
+}
+.tabs-right .nav-tabs > li > a {
+  margin-left: -1px;
+  -webkit-border-radius: 0 4px 4px 0;
+  -moz-border-radius: 0 4px 4px 0;
+  border-radius: 0 4px 4px 0;
+}
+.tabs-right .nav-tabs > li > a:hover {
+  border-color: #eeeeee #eeeeee #eeeeee #dddddd;
+}
+.tabs-right .nav-tabs .active > a,
+.tabs-right .nav-tabs .active > a:hover {
+  border-color: #ddd #ddd #ddd transparent;
+  *border-left-color: #ffffff;
+}
+.navbar {
+  *position: relative;
+  *z-index: 2;
+  overflow: visible;
+  margin-bottom: 18px;
+}
+.navbar-inner {
+  padding-left: 20px;
+  padding-right: 20px;
+  background-color: white;
+  border-bottom: 2px solid #221e50;
+/*  background-image: -moz-linear-gradient(top, #333333, #222222);
+  background-image: -ms-linear-gradient(top, #333333, #222222);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));
+  background-image: -webkit-linear-gradient(top, #333333, #222222);
+  background-image: -o-linear-gradient(top, #333333, #222222);
+  background-image: linear-gradient(top, #333333, #222222);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);*/
+}
+.navbar .container {
+  width: auto;
+}
+.btn-navbar {
+  display: none;
+  float: right;
+  padding: 7px 10px;
+  margin-left: 5px;
+  margin-right: 5px;
+  background-color: #2c2c2c;
+  background-image: -moz-linear-gradient(top, #333333, #222222);
+  background-image: -ms-linear-gradient(top, #333333, #222222);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));
+  background-image: -webkit-linear-gradient(top, #333333, #222222);
+  background-image: -o-linear-gradient(top, #333333, #222222);
+  background-image: linear-gradient(top, #333333, #222222);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);
+  border-color: #222222 #222222 #000000;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+  -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+}
+.btn-navbar:hover,
+.btn-navbar:active,
+.btn-navbar.active,
+.btn-navbar.disabled,
+.btn-navbar[disabled] {
+  background-color: #222222;
+}
+.btn-navbar:active,
+.btn-navbar.active {
+  background-color: #080808 \9;
+}
+.btn-navbar .icon-bar {
+  display: block;
+  width: 18px;
+  height: 2px;
+  background-color: #f5f5f5;
+  -webkit-border-radius: 1px;
+  -moz-border-radius: 1px;
+  border-radius: 1px;
+  -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+  -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+.btn-navbar .icon-bar + .icon-bar {
+  margin-top: 3px;
+}
+.nav-collapse.collapse {
+  height: auto;
+}
+.navbar {
+  color: #999999;
+}
+.navbar .brand:hover {
+  text-decoration: none;
+}
+.navbar .brand {
+  float: left;
+  display: block;
+  padding: 8px 20px 12px;
+  margin-left: -20px;
+  font-size: 20px;
+  font-weight: 200;
+  line-height: 1;
+  color: #ffffff;
+}
+.navbar .navbar-text {
+  margin-bottom: 0;
+  line-height: 40px;
+}
+.navbar .btn,
+.navbar .btn-group {
+  margin-top: 5px;
+}
+.navbar .btn-group .btn {
+  margin-top: 0;
+}
+.navbar-form {
+  margin-bottom: 0;
+  *zoom: 1;
+}
+.navbar-form:before,
+.navbar-form:after {
+  display: table;
+  content: "";
+}
+.navbar-form:after {
+  clear: both;
+}
+.navbar-form input,
+.navbar-form select,
+.navbar-form .radio,
+.navbar-form .checkbox {
+  margin-top: 5px;
+}
+.navbar-form input,
+.navbar-form select {
+  display: inline-block;
+  margin-bottom: 0;
+}
+.navbar-form input[type="image"],
+.navbar-form input[type="checkbox"],
+.navbar-form input[type="radio"] {
+  margin-top: 3px;
+}
+.navbar-form .input-append,
+.navbar-form .input-prepend {
+  margin-top: 6px;
+  white-space: nowrap;
+}
+.navbar-form .input-append input,
+.navbar-form .input-prepend input {
+  margin-top: 0;
+}
+.navbar-search {
+  position: relative;
+  float: left;
+  margin-top: 17px;
+  margin-bottom: 0;
+}
+.navbar-search .search-query {
+  padding: 4px 9px;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 13px;
+  font-weight: normal;
+  line-height: 1;
+  color: #ffffff;
+  background-color: #626262;
+  border: 1px solid #151515;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.15);
+  -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.15);
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.15);
+  -webkit-transition: none;
+  -moz-transition: none;
+  -ms-transition: none;
+  -o-transition: none;
+  transition: none;
+}
+.navbar-search .search-query:-moz-placeholder {
+  color: #cccccc;
+}
+.navbar-search .search-query::-webkit-input-placeholder {
+  color: #cccccc;
+}
+.navbar-search .search-query:focus,
+.navbar-search .search-query.focused {
+  padding: 5px 10px;
+  color: #333333;
+  text-shadow: 0 1px 0 #ffffff;
+  background-color: #ffffff;
+  border: 0;
+  -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+  -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+  box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+  outline: 0;
+}
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+  margin-bottom: 0;
+}
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+  padding-left: 0;
+  padding-right: 0;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+  width: 940px;
+}
+.navbar-fixed-top {
+  top: 0;
+}
+.navbar-fixed-bottom {
+  bottom: 0;
+}
+.navbar .nav {
+  position: relative;
+  left: 0;
+  display: block;
+  float: left;
+  margin: 0 10px 0px 0;
+}
+.navbar .nav.pull-right {
+  float: right;
+}
+.navbar .nav > li {
+  display: block;
+  margin-top: 10px;
+  font-size: 130%;
+  float: left;
+}
+.navbar .nav > li > a {
+  float: none;
+  padding: 10px 10px 11px;
+  line-height: 25px;
+  color: #221e50;
+  text-decoration: none;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.navbar .nav > li > a:hover {
+  background-color: transparent;
+  color: #221e50;
+  text-decoration: underline;
+}
+.navbar .nav .active > a,
+.navbar .nav .active > a:hover {
+  color: #221e50;
+  text-decoration: underline;
+  background-color: white;
+}
+.navbar .divider-vertical {
+  height: 40px;
+  width: 1px;
+  margin: 0 9px;
+  overflow: hidden;
+  background-color: white;
+  border-right: 1px solid #333333;
+}
+.navbar .nav.pull-right {
+  margin-left: 10px;
+  margin-right: 0;
+}
+.navbar .dropdown-menu {
+  margin-top: 1px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.navbar .dropdown-menu:before {
+  content: '';
+  display: inline-block;
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  position: absolute;
+  top: -7px;
+  left: 9px;
+}
+.navbar .dropdown-menu:after {
+  content: '';
+  display: inline-block;
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid #ffffff;
+  position: absolute;
+  top: -6px;
+  left: 10px;
+}
+.navbar-fixed-bottom .dropdown-menu:before {
+  border-top: 7px solid #ccc;
+  border-top-color: rgba(0, 0, 0, 0.2);
+  border-bottom: 0;
+  bottom: -7px;
+  top: auto;
+}
+.navbar-fixed-bottom .dropdown-menu:after {
+  border-top: 6px solid #ffffff;
+  border-bottom: 0;
+  bottom: -6px;
+  top: auto;
+}
+.navbar .nav .dropdown-toggle .caret,
+.navbar .nav .open.dropdown .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+.navbar .nav .active .caret {
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+.navbar .nav .open > .dropdown-toggle,
+.navbar .nav .active > .dropdown-toggle,
+.navbar .nav .open.active > .dropdown-toggle {
+  background-color: transparent;
+}
+.navbar .nav .active > .dropdown-toggle:hover {
+  color: #ffffff;
+}
+.navbar .nav.pull-right .dropdown-menu,
+.navbar .nav .dropdown-menu.pull-right {
+  left: auto;
+  right: 0;
+}
+.navbar .nav.pull-right .dropdown-menu:before,
+.navbar .nav .dropdown-menu.pull-right:before {
+  left: auto;
+  right: 12px;
+}
+.navbar .nav.pull-right .dropdown-menu:after,
+.navbar .nav .dropdown-menu.pull-right:after {
+  left: auto;
+  right: 13px;
+}
+.breadcrumb {
+  padding: 7px 14px;
+  margin: 0 0 18px;
+  list-style: none;
+  background-color: #fbfbfb;
+  background-image: -moz-linear-gradient(top, #ffffff, #f5f5f5);
+  background-image: -ms-linear-gradient(top, #ffffff, #f5f5f5);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));
+  background-image: -webkit-linear-gradient(top, #ffffff, #f5f5f5);
+  background-image: -o-linear-gradient(top, #ffffff, #f5f5f5);
+  background-image: linear-gradient(top, #ffffff, #f5f5f5);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);
+  border: 1px solid #ddd;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  -webkit-box-shadow: inset 0 1px 0 #ffffff;
+  -moz-box-shadow: inset 0 1px 0 #ffffff;
+  box-shadow: inset 0 1px 0 #ffffff;
+}
+.breadcrumb li {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  text-shadow: 0 1px 0 #ffffff;
+}
+.breadcrumb .divider {
+  padding: 0 5px;
+  color: #999999;
+}
+.breadcrumb .active a {
+  color: #333333;
+}
+.pagination {
+  height: 36px;
+  margin: 18px 0;
+}
+.pagination ul {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  margin-left: 0;
+  margin-bottom: 0;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+  -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.pagination li {
+  display: inline;
+}
+.pagination a {
+  float: left;
+  padding: 0 14px;
+  line-height: 34px;
+  text-decoration: none;
+  border: 1px solid #ddd;
+  border-left-width: 0;
+}
+.pagination a:hover,
+.pagination .active a {
+  background-color: #f5f5f5;
+}
+.pagination .active a {
+  color: #999999;
+  cursor: default;
+}
+.pagination .disabled span,
+.pagination .disabled a,
+.pagination .disabled a:hover {
+  color: #999999;
+  background-color: transparent;
+  cursor: default;
+}
+.pagination li:first-child a {
+  border-left-width: 1px;
+  -webkit-border-radius: 3px 0 0 3px;
+  -moz-border-radius: 3px 0 0 3px;
+  border-radius: 3px 0 0 3px;
+}
+.pagination li:last-child a {
+  -webkit-border-radius: 0 3px 3px 0;
+  -moz-border-radius: 0 3px 3px 0;
+  border-radius: 0 3px 3px 0;
+}
+.pagination-centered {
+  text-align: center;
+}
+.pagination-right {
+  text-align: right;
+}
+.pager {
+  margin-left: 0;
+  margin-bottom: 18px;
+  list-style: none;
+  text-align: center;
+  *zoom: 1;
+}
+.pager:before,
+.pager:after {
+  display: table;
+  content: "";
+}
+.pager:after {
+  clear: both;
+}
+.pager li {
+  display: inline;
+}
+.pager a {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  -webkit-border-radius: 15px;
+  -moz-border-radius: 15px;
+  border-radius: 15px;
+}
+.pager a:hover {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+.pager .next a {
+  float: right;
+}
+.pager .previous a {
+  float: left;
+}
+.pager .disabled a,
+.pager .disabled a:hover {
+  color: #999999;
+  background-color: #fff;
+  cursor: default;
+}
+.modal-open .dropdown-menu {
+  z-index: 2050;
+}
+.modal-open .dropdown.open {
+  *z-index: 2050;
+}
+.modal-open .popover {
+  z-index: 2060;
+}
+.modal-open .tooltip {
+  z-index: 2070;
+}
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  background-color: #000000;
+}
+.modal-backdrop.fade {
+  opacity: 0;
+}
+.modal-backdrop,
+.modal-backdrop.fade.in {
+  opacity: 0.8;
+  filter: alpha(opacity=80);
+}
+.modal {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  z-index: 1050;
+  overflow: auto;
+  width: 560px;
+  margin: -250px 0 0 -280px;
+  background-color: #ffffff;
+  border: 1px solid #999;
+  border: 1px solid rgba(0, 0, 0, 0.3);
+  *border: 1px solid #999;
+  /* IE6-7 */
+
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+  -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  -webkit-background-clip: padding-box;
+  -moz-background-clip: padding-box;
+  background-clip: padding-box;
+}
+.modal.fade {
+  -webkit-transition: opacity .3s linear, top .3s ease-out;
+  -moz-transition: opacity .3s linear, top .3s ease-out;
+  -ms-transition: opacity .3s linear, top .3s ease-out;
+  -o-transition: opacity .3s linear, top .3s ease-out;
+  transition: opacity .3s linear, top .3s ease-out;
+  top: -25%;
+}
+.modal.fade.in {
+  top: 50%;
+}
+.modal-header {
+  padding: 9px 15px;
+  border-bottom: 1px solid #eee;
+}
+.modal-header .close {
+  margin-top: 2px;
+}
+.modal-body {
+  overflow-y: auto;
+  max-height: 400px;
+  padding: 15px;
+}
+.modal-form {
+  margin-bottom: 0;
+}
+.modal-footer {
+  padding: 14px 15px 15px;
+  margin-bottom: 0;
+  text-align: right;
+  background-color: #f5f5f5;
+  border-top: 1px solid #ddd;
+  -webkit-border-radius: 0 0 6px 6px;
+  -moz-border-radius: 0 0 6px 6px;
+  border-radius: 0 0 6px 6px;
+  -webkit-box-shadow: inset 0 1px 0 #ffffff;
+  -moz-box-shadow: inset 0 1px 0 #ffffff;
+  box-shadow: inset 0 1px 0 #ffffff;
+  *zoom: 1;
+}
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: "";
+}
+.modal-footer:after {
+  clear: both;
+}
+.modal-footer .btn + .btn {
+  margin-left: 5px;
+  margin-bottom: 0;
+}
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+.tooltip {
+  position: absolute;
+  z-index: 1020;
+  display: block;
+  visibility: visible;
+  padding: 5px;
+  font-size: 11px;
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+.tooltip.in {
+  opacity: 0.8;
+  filter: alpha(opacity=80);
+}
+.tooltip.top {
+  margin-top: -2px;
+}
+.tooltip.right {
+  margin-left: 2px;
+}
+.tooltip.bottom {
+  margin-top: 2px;
+}
+.tooltip.left {
+  margin-left: -2px;
+}
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+  border-top: 5px solid #000000;
+}
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-top: 5px solid transparent;
+  border-bottom: 5px solid transparent;
+  border-left: 5px solid #000000;
+}
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+  border-bottom: 5px solid #000000;
+}
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-top: 5px solid transparent;
+  border-bottom: 5px solid transparent;
+  border-right: 5px solid #000000;
+}
+.tooltip-inner {
+  max-width: 200px;
+  padding: 3px 8px;
+  color: #ffffff;
+  text-align: center;
+  text-decoration: none;
+  background-color: #000000;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+}
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1010;
+  display: none;
+  padding: 5px;
+}
+.popover.top {
+  margin-top: -5px;
+}
+.popover.right {
+  margin-left: 5px;
+}
+.popover.bottom {
+  margin-top: 5px;
+}
+.popover.left {
+  margin-left: -5px;
+}
+.popover.top .arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+  border-top: 5px solid #000000;
+}
+.popover.right .arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-top: 5px solid transparent;
+  border-bottom: 5px solid transparent;
+  border-right: 5px solid #000000;
+}
+.popover.bottom .arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+  border-bottom: 5px solid #000000;
+}
+.popover.left .arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-top: 5px solid transparent;
+  border-bottom: 5px solid transparent;
+  border-left: 5px solid #000000;
+}
+.popover .arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+}
+.popover-inner {
+  padding: 3px;
+  width: 280px;
+  overflow: hidden;
+  background: #000000;
+  background: rgba(0, 0, 0, 0.8);
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+  -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+}
+.popover-title {
+  padding: 9px 15px;
+  line-height: 1;
+  background-color: #f5f5f5;
+  border-bottom: 1px solid #eee;
+  -webkit-border-radius: 3px 3px 0 0;
+  -moz-border-radius: 3px 3px 0 0;
+  border-radius: 3px 3px 0 0;
+}
+.popover-content {
+  padding: 14px;
+  background-color: #ffffff;
+  -webkit-border-radius: 0 0 3px 3px;
+  -moz-border-radius: 0 0 3px 3px;
+  border-radius: 0 0 3px 3px;
+  -webkit-background-clip: padding-box;
+  -moz-background-clip: padding-box;
+  background-clip: padding-box;
+}
+.popover-content p,
+.popover-content ul,
+.popover-content ol {
+  margin-bottom: 0;
+}
+.thumbnails {
+  margin-left: -20px;
+  list-style: none;
+  *zoom: 1;
+}
+.thumbnails:before,
+.thumbnails:after {
+  display: table;
+  content: "";
+}
+.thumbnails:after {
+  clear: both;
+}
+.thumbnails > li {
+  float: left;
+  margin: 0 0 18px 20px;
+}
+.thumbnail {
+  display: block;
+  padding: 4px;
+  line-height: 1;
+  border: 1px solid #ddd;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
+  -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+a.thumbnail:hover {
+  border-color: #0088cc;
+  -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+  -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+  box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+.thumbnail > img {
+  display: block;
+  max-width: 100%;
+  margin-left: auto;
+  margin-right: auto;
+}
+.thumbnail .caption {
+  padding: 9px;
+}
+.label {
+  padding: 1px 4px 2px;
+  font-size: 10.998px;
+  font-weight: bold;
+  line-height: 13px;
+  color: #ffffff;
+  vertical-align: middle;
+  white-space: nowrap;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #999999;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.label:hover {
+  color: #ffffff;
+  text-decoration: none;
+}
+.label-important {
+  background-color: #b94a48;
+}
+.label-important:hover {
+  background-color: #953b39;
+}
+.label-warning {
+  background-color: #f89406;
+}
+.label-warning:hover {
+  background-color: #c67605;
+}
+.label-success {
+  background-color: #468847;
+}
+.label-success:hover {
+  background-color: #356635;
+}
+.label-info {
+  background-color: #3a87ad;
+}
+.label-info:hover {
+  background-color: #2d6987;
+}
+.label-inverse {
+  background-color: #333333;
+}
+.label-inverse:hover {
+  background-color: #1a1a1a;
+}
+.badge {
+  padding: 1px 9px 2px;
+  font-size: 12.025px;
+  font-weight: bold;
+  white-space: nowrap;
+  color: #ffffff;
+  background-color: #999999;
+  -webkit-border-radius: 9px;
+  -moz-border-radius: 9px;
+  border-radius: 9px;
+}
+.badge:hover {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+.badge-error {
+  background-color: #b94a48;
+}
+.badge-error:hover {
+  background-color: #953b39;
+}
+.badge-warning {
+  background-color: #f89406;
+}
+.badge-warning:hover {
+  background-color: #c67605;
+}
+.badge-success {
+  background-color: #468847;
+}
+.badge-success:hover {
+  background-color: #356635;
+}
+.badge-info {
+  background-color: #3a87ad;
+}
+.badge-info:hover {
+  background-color: #2d6987;
+}
+.badge-inverse {
+  background-color: #333333;
+}
+.badge-inverse:hover {
+  background-color: #1a1a1a;
+}
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+@-moz-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+@-ms-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+.progress {
+  overflow: hidden;
+  height: 18px;
+  margin-bottom: 18px;
+  background-color: #f7f7f7;
+  background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -ms-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+  background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.progress .bar {
+  width: 0%;
+  height: 18px;
+  color: #ffffff;
+  font-size: 12px;
+  text-align: center;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #0e90d2;
+  background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+  background-image: -ms-linear-gradient(top, #149bdf, #0480be);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+  background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+  background-image: -o-linear-gradient(top, #149bdf, #0480be);
+  background-image: linear-gradient(top, #149bdf, #0480be);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  -ms-box-sizing: border-box;
+  box-sizing: border-box;
+  -webkit-transition: width 0.6s ease;
+  -moz-transition: width 0.6s ease;
+  -ms-transition: width 0.6s ease;
+  -o-transition: width 0.6s ease;
+  transition: width 0.6s ease;
+}
+.progress-striped .bar {
+  background-color: #149bdf;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  -webkit-background-size: 40px 40px;
+  -moz-background-size: 40px 40px;
+  -o-background-size: 40px 40px;
+  background-size: 40px 40px;
+}
+.progress.active .bar {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+  -moz-animation: progress-bar-stripes 2s linear infinite;
+  animation: progress-bar-stripes 2s linear infinite;
+}
+.progress-danger .bar {
+  background-color: #dd514c;
+  background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+  background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: linear-gradient(top, #ee5f5b, #c43c35);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
+}
+.progress-danger.progress-striped .bar {
+  background-color: #ee5f5b;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.progress-success .bar {
+  background-color: #5eb95e;
+  background-image: -moz-linear-gradient(top, #62c462, #57a957);
+  background-image: -ms-linear-gradient(top, #62c462, #57a957);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+  background-image: -webkit-linear-gradient(top, #62c462, #57a957);
+  background-image: -o-linear-gradient(top, #62c462, #57a957);
+  background-image: linear-gradient(top, #62c462, #57a957);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);
+}
+.progress-success.progress-striped .bar {
+  background-color: #62c462;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.progress-info .bar {
+  background-color: #4bb1cf;
+  background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+  background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: linear-gradient(top, #5bc0de, #339bb9);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);
+}
+.progress-info.progress-striped .bar {
+  background-color: #5bc0de;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.progress-warning .bar {
+  background-color: #faa732;
+  background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+  background-image: -ms-linear-gradient(top, #fbb450, #f89406);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+  background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+  background-image: -o-linear-gradient(top, #fbb450, #f89406);
+  background-image: linear-gradient(top, #fbb450, #f89406);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);
+}
+.progress-warning.progress-striped .bar {
+  background-color: #fbb450;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.accordion {
+  margin-bottom: 18px;
+}
+.accordion-group {
+  margin-bottom: 2px;
+  border: 1px solid #e5e5e5;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.accordion-heading {
+  border-bottom: 0;
+}
+.accordion-heading .accordion-toggle {
+  display: block;
+  padding: 8px 15px;
+}
+.accordion-inner {
+  padding: 9px 15px;
+  border-top: 1px solid #e5e5e5;
+}
+.carousel {
+  position: relative;
+  margin-bottom: 18px;
+  line-height: 1;
+}
+.carousel-inner {
+  overflow: hidden;
+  width: 100%;
+  position: relative;
+}
+.carousel .item {
+  display: none;
+  position: relative;
+  -webkit-transition: 0.6s ease-in-out left;
+  -moz-transition: 0.6s ease-in-out left;
+  -ms-transition: 0.6s ease-in-out left;
+  -o-transition: 0.6s ease-in-out left;
+  transition: 0.6s ease-in-out left;
+}
+.carousel .item > img {
+  display: block;
+  line-height: 1;
+}
+.carousel .active,
+.carousel .next,
+.carousel .prev {
+  display: block;
+}
+.carousel .active {
+  left: 0;
+}
+.carousel .next,
+.carousel .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+.carousel .next {
+  left: 100%;
+}
+.carousel .prev {
+  left: -100%;
+}
+.carousel .next.left,
+.carousel .prev.right {
+  left: 0;
+}
+.carousel .active.left {
+  left: -100%;
+}
+.carousel .active.right {
+  left: 100%;
+}
+.carousel-control {
+  position: absolute;
+  top: 40%;
+  left: 15px;
+  width: 40px;
+  height: 40px;
+  margin-top: -20px;
+  font-size: 60px;
+  font-weight: 100;
+  line-height: 30px;
+  color: #ffffff;
+  text-align: center;
+  background: #222222;
+  border: 3px solid #ffffff;
+  -webkit-border-radius: 23px;
+  -moz-border-radius: 23px;
+  border-radius: 23px;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+.carousel-control.right {
+  left: auto;
+  right: 15px;
+}
+.carousel-control:hover {
+  color: #ffffff;
+  text-decoration: none;
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+.carousel-caption {
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  padding: 10px 15px 5px;
+  background: #333333;
+  background: rgba(0, 0, 0, 0.75);
+}
+.carousel-caption h4,
+.carousel-caption p {
+  color: #ffffff;
+}
+.hero-unit {
+  padding: 60px;
+  margin-bottom: 30px;
+  background-color: #eeeeee;
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+}
+.hero-unit h1 {
+  margin-bottom: 0;
+  font-size: 60px;
+  line-height: 1;
+  color: inherit;
+  letter-spacing: -1px;
+}
+.hero-unit p {
+  font-size: 18px;
+  font-weight: 200;
+  line-height: 27px;
+  color: inherit;
+}
+.pull-right {
+  float: right;
+}
+.pull-left {
+  float: left;
+}
+.hide {
+  display: none;
+}
+.show {
+  display: block;
+}
+.invisible {
+  visibility: hidden;
+}
+
+td.status-head {
+  font-weight:bold;
+  width:150px;
+  padding:7px;
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/css/styles.css b/src/ext/floodlight/src/main/resources/web/css/styles.css
new file mode 100644
index 0000000..1abe024
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/css/styles.css
@@ -0,0 +1,14 @@
+.dropdown-menu {
+    max-height: 400px;
+    overflow-y: scroll;
+    width: 220px;
+}
+
+.list-item {
+    padding-top: 6px;
+    padding-left: 56px;
+}
+
+.no-reports {
+    display: none;
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/img/floodlight.png b/src/ext/floodlight/src/main/resources/web/img/floodlight.png
new file mode 100644
index 0000000..98d0477
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/floodlight.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings-white.png b/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..a20760b
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings.png b/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings.png
new file mode 100644
index 0000000..92d4445
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/logo.jpg b/src/ext/floodlight/src/main/resources/web/img/logo.jpg
new file mode 100644
index 0000000..49795ef
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/logo.jpg
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/openflow-logo-40px.png b/src/ext/floodlight/src/main/resources/web/img/openflow-logo-40px.png
new file mode 100644
index 0000000..b455ace
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/openflow-logo-40px.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/server.png b/src/ext/floodlight/src/main/resources/web/img/server.png
new file mode 100644
index 0000000..341c73d
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/server.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/switch.png b/src/ext/floodlight/src/main/resources/web/img/switch.png
new file mode 100644
index 0000000..f8e6910
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/switch.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/index.html b/src/ext/floodlight/src/main/resources/web/index.html
new file mode 100644
index 0000000..cc2c2e8
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/index.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<!-- template from Christophe Coenraets -->
+<!-- 
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ -->
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>Floodlight</title>
+    <meta name="description" content="">
+    <meta name="author" content="">
+
+    <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
+    <!--[if lt IE 9]>
+    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+    <!-- Place this tag in your head or just before your close body tag -->
+    <![endif]-->
+
+    <!-- Le styles -->
+    <style>
+        body {
+            padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
+        }
+    </style>
+    <link href="css/bootstrap.css" rel="stylesheet">
+    <link href="css/styles.css" rel="stylesheet">
+
+    <!-- Le fav and touch icons -->
+    <!-- TODO create some icons 
+    <link rel="shortcut icon" href="/img/favicon.ico">
+    <link rel="apple-touch-icon" href="/img/apple-touch-icon.png">
+    <link rel="apple-touch-icon" sizes="72x72" href="/img/apple-touch-icon-72x72.png">
+    <link rel="apple-touch-icon" sizes="114x114" href="/img/apple-touch-icon-114x114.png">
+    -->
+</head>
+
+<body>
+
+<div class="header"></div>
+
+<div class="container">
+
+<div id="content"></div>
+
+<hr>
+<footer class="footer">
+    <p><a href="http://floodlight.openflowhub.org/">Floodlight </a> &copy; <a href="http://www.bigswitch.com/">Big Switch Networks</a>, <a href="http://www.research.ibm.com/arl/">IBM</a>, et. al.
+    Powered by <a href="http://documentcloud.github.com/backbone/">Backbone.js</a>, <a href="http://twitter.github.com/bootstrap/">Bootstrap</a>, <a href="http://jquery.com/">jQuery</a>, <a href="http://mbostock.github.com/d3/">D3.js</a>, etc.</p>
+</footer>
+
+</div> <!-- /container -->
+
+<script src="lib/jquery.min.js"></script>
+<script src="lib/underscore-min.js"></script>
+<script src="lib/backbone-min.js"></script>
+<script src="lib/d3.v2.min.js"></script>
+
+<script src="lib/bootstrap-dropdown.js"></script>
+<script src="lib/bootstrap-alert.js"></script>
+
+<script src="js/utils.js"></script>
+
+<script src="js/models/hostmodel.js"></script>
+<script src="js/models/topologymodel.js"></script>
+<script src="js/models/statusmodel.js"></script>
+<script src="js/models/switchmodel.js"></script>
+<script src="js/models/portmodel.js"></script>
+<script src="js/models/flowmodel.js"></script>
+
+<script src="js/views/header.js"></script>
+<script src="js/views/home.js"></script>
+<script src="js/views/status.js"></script>
+<script src="js/views/host.js"></script>
+<script src="js/views/switch.js"></script>
+<script src="js/views/topology.js"></script>
+<script src="js/views/port.js"></script>
+<script src="js/views/flow.js"></script>
+
+<script src="js/main.js"></script>
+
+</body>
+</html>
diff --git a/src/ext/floodlight/src/main/resources/web/js/main.js b/src/ext/floodlight/src/main/resources/web/js/main.js
new file mode 100644
index 0000000..2362757
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/main.js
@@ -0,0 +1,127 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+var hackBase = ""; // put a URL here to access a different REST server
+
+var AppRouter = Backbone.Router.extend({
+
+    routes:{
+        "":"home",
+        "topology":"topology",
+        "switches":"switchList",
+        "switch/:id":"switchDetails",
+        "switch/:id/port/:p":"portDetails", // not clear if needed
+        "hosts":"hostList",
+        "host/:id":"hostDetails",
+        // "vlans":"vlanList" // maybe one day
+        // "vlan/:id":"vlanDetails"
+    },
+
+    initialize:function () {
+        this.headerView = new HeaderView();
+        $('.header').html(this.headerView.render().el);
+
+        // Close the search dropdown on click anywhere in the UI
+        $('body').click(function () {
+            $('.dropdown').removeClass("open");
+        });
+    },
+
+    home:function () {
+        $('#content').html(new HomeView().render().el);
+        $('ul[class="nav"] > li').removeClass('active');
+        $('a[href="/"]').parent().addClass('active');
+    },
+
+    topology:function () {
+        //console.log("switching to topology view");
+        var topo = new Topology();
+        $('#content').html(new TopologyView({model:topo, hosts:hl}).render().el);
+        // TODO factor this code out
+        $('ul.nav > li').removeClass('active');
+        $('li > a[href*="topology"]').parent().addClass('active');
+    },
+    
+    switchDetails:function (id) {
+        //console.log("switching [sic] to single switch view");
+        var sw = swl.get(id);
+        $('#content').html(new SwitchView({model:sw}).render().el);
+        $('ul.nav > li').removeClass('active');
+        $('li > a[href*="/switches"]').parent().addClass('active');
+    },
+    
+    switchList:function () {
+        //console.log("switching [sic] to switch list view");
+        $('#content').html(new SwitchListView({model:swl}).render().el);
+        $('ul.nav > li').removeClass('active');
+        $('li > a[href*="/switches"]').parent().addClass('active');
+    },
+
+    hostDetails:function (id) {
+        //console.log("switching to single host view");
+        var h = hl.get(id);
+        $('#content').html(new HostView({model:h}).render().el);
+        $('ul.nav > li').removeClass('active');
+        $('li > a[href*="/hosts"]').parent().addClass('active');
+    },
+    
+    hostList:function () {
+        //console.log("switching to host list view");
+        $('#content').html(new HostListView({model:hl}).render().el);
+        $('ul.nav > li').removeClass('active');
+        $('li > a[href*="/hosts"]').parent().addClass('active');
+    },
+
+});
+
+// load global models and reuse them
+var swl = new SwitchCollection();
+var hl  = new HostCollection();
+
+tpl.loadTemplates(['home', 'status', 'topology', 'header', 'switch', 'switch-list', 'switch-list-item', 'host', 'host-list', 'host-list-item', 'port-list', 'port-list-item', 'flow-list', 'flow-list-item'],
+    function () {
+        app = new AppRouter();
+        Backbone.history.start({pushState: true});
+        // console.log("started history")
+        
+        $(document).ready(function () {
+            // trigger Backbone routing when clicking on links, thanks to Atinux and pbnv
+            app.navigate("", true);
+
+            window.document.addEventListener('click', function(e) {
+                e = e || window.event
+                var target = e.target || e.srcElement
+                if ( target.nodeName.toLowerCase() === 'a' ) {
+                    e.preventDefault()
+                    var uri = target.getAttribute('href')
+                    app.navigate(uri.substr(1), true)
+                }
+            });
+            window.addEventListener('popstate', function(e) {
+                app.navigate(location.pathname.substr(1), true);
+            });
+
+        });
+    });
+
+setInterval(function () {
+    swl.fetch();
+}, 3000);
+
+setInterval(function () {
+    hl.fetch();
+}, 3000);
+
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/flowmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/flowmodel.js
new file mode 100644
index 0000000..80777c3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/flowmodel.js
@@ -0,0 +1,37 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.Flow = Backbone.Model.extend({
+
+    defaults: {
+        receiveBytes: 0,
+        receivePackets: 0,
+        transmitBytes: 0,
+        transmitPackets: 0,
+    },
+
+    // initialize:function () {}
+
+});
+
+window.FlowCollection = Backbone.Collection.extend({
+
+    model:Flow,
+    
+    // instead of the collection loading its children, the switch will load them
+    // initialize:function () {}
+    
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/hostmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/hostmodel.js
new file mode 100644
index 0000000..47ae420
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/hostmodel.js
@@ -0,0 +1,72 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.Host = Backbone.Model.extend({
+
+    defaults: {
+        // vlan: -1,
+        lastSeen: 'never',
+        ip: ' ',
+        swport: ' ',
+    },
+
+    // initialize:function () {}
+
+});
+
+window.HostCollection = Backbone.Collection.extend({
+
+    model:Host,
+
+    initialize:function () {
+        var self = this;
+        //console.log("fetching host list")
+        $.ajax({
+            url:hackBase + "/wm/device/",
+            dataType:"json",
+            success:function (data) {
+                // console.log("fetched  host list: " + data.length);
+                // console.log(data);
+                // data is a list of device hashes
+                _.each(data, function(h) {
+                    if (h['attachmentPoint'].length > 0) {
+                        h.id = h.mac[0];
+                        h.swport = _.reduce(h['attachmentPoint'], function(memo, ap) {
+                            return memo + ap.switchDPID + "-" + ap.port + " "}, "");
+                        //console.log(h.swport);
+                        h.lastSeen = new Date(h.lastSeen).toLocaleString();
+                        self.add(h, {silent: true});
+                    }
+                });
+                self.trigger('add'); // batch redraws
+            }
+        });
+
+    },
+
+    fetch:function () {
+        this.initialize();
+    }
+
+    /*
+     * findByName:function (key) { // TODO: Modify service to include firstName
+     * in search var url = (key == '') ? '/host/' : "/host/search/" + key;
+     * console.log('findByName: ' + key); var self = this; $.ajax({ url:url,
+     * dataType:"json", success:function (data) { console.log("search success: " +
+     * data.length); self.reset(data); } }); }
+     */
+
+});
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/portmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/portmodel.js
new file mode 100644
index 0000000..563f334
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/portmodel.js
@@ -0,0 +1,42 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.Port = Backbone.Model.extend({
+
+    defaults: {
+        name: '',
+        receiveBytes: 0,
+        receivePackets: 0,
+        transmitBytes: 0,
+        transmitPackets: 0,
+        dropped: 0,
+        errors: 0,
+    },
+
+    initialize:function () {
+        // TODO hook up associated hosts
+    }
+
+});
+
+window.PortCollection = Backbone.Collection.extend({
+
+    model:Port,
+    
+    // instead of the collection loading its children, the switch will load them
+    initialize:function () {}
+    
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/statusmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/statusmodel.js
new file mode 100644
index 0000000..31bdff3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/statusmodel.js
@@ -0,0 +1,64 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.Status = Backbone.Model.extend({    
+    defaults: {
+        host: 'localhost',
+        ofport: 6633,
+        uptime: 'unknown',
+        free: 0,
+        total: 0,
+        healthy: 'unknown',
+        modules: [],
+        moduleText: ''
+    },
+    
+    initialize:function () {
+        var self = this;
+        console.log("fetching controller status");
+        $.ajax({
+            url:hackBase + "/wm/core/health/json",
+            dataType:"json",
+            success:function (data) {
+                console.log("fetched controller status: health");
+                self.set(data);
+                // console.log(self.toJSON());
+            }
+        });
+        $.ajax({
+            url:hackBase + "/wm/core/memory/json",
+            dataType:"json",
+            success:function (data) {
+                console.log("fetched controller status: memory");
+                self.set(data);
+                // console.log(self.toJSON());
+            }
+        });
+        $.ajax({
+            url:hackBase + "/wm/core/module/loaded/json",
+            dataType:"json",
+            success:function (data) {
+                console.log("fetched controller status: modules loaded");
+                // console.log(data);
+                self.set({modules:_.keys(data)});
+                self.set({moduleText:_.reduce(_.keys(data), function(s, m)
+                    {return s+m.replace("net.floodlightcontroller", "n.f")+", "}, '')});
+            }
+        });
+
+    }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/switchmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/switchmodel.js
new file mode 100644
index 0000000..285c912
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/switchmodel.js
@@ -0,0 +1,275 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.Switch = Backbone.Model.extend({
+
+    urlRoot:"/wm/core/switch/",
+    
+    defaults: {
+        datapathDescription: '',
+        hardwareDescription: '',
+        manufacturerDescription: '',
+        serialNumber: '',
+        softwareDescription: '',
+        flowCount: ' ',
+        packetCount: ' ',
+        byteCount: ' ',
+    },
+
+    initialize:function () {
+        var self = this;
+
+        //console.log("fetching switch " + this.id + " desc")
+        $.ajax({
+            url:hackBase + "/wm/core/switch/" + self.id + '/desc/json',
+            dataType:"json",
+            success:function (data) {
+                //console.log("fetched  switch " + self.id + " desc");
+                //console.log(data[self.id][0]);
+                self.set(data[self.id][0]);
+            }
+        });
+
+        //console.log("fetching switch " + this.id + " aggregate")
+        $.ajax({
+            url:hackBase + "/wm/core/switch/" + self.id + '/aggregate/json',
+            dataType:"json",
+            success:function (data) {
+                //console.log("fetched  switch " + self.id + " aggregate");
+                //console.log(data[self.id][0]);
+                self.set(data[self.id][0]);
+            }
+        });
+        self.trigger('add');
+        this.ports = new PortCollection();
+        this.flows = new FlowCollection();
+        //this.loadPorts();
+        //this.loadFlows();
+    },
+
+    fetch:function () {
+        this.initialize()
+    },
+
+    loadPorts:function () {
+        var self = this;
+        //console.log("fetching switch " + this.id + " ports")
+        //console.log("fetching switch " + this.id + " features")
+        $.when($.ajax({
+            url:hackBase + "/wm/core/switch/" + self.id + '/port/json',
+            dataType:"json",
+            success:function (data) {
+                //console.log("fetched  switch " + self.id + " ports");
+                //console.log(data[self.id]);
+                // create port models
+                _.each(data[self.id], function(p) {
+                    p.id = self.id+'-'+p.portNumber;
+                    p.dropped = p.receiveDropped + p.transmitDropped;
+                    p.errors = p.receiveCRCErrors + p.receiveErrors + p.receiveOverrunErrors +
+                        p.receiveFrameErrors + p.transmitErrors;
+                    // this is a knda kludgy way to merge models
+                    var m = self.ports.get(p.id);
+                    if(m) {
+                        m.set(p, {silent: true});
+                    } else {
+                        self.ports.add(p, {silent: true});
+                    }
+                    //console.log(p);
+                });
+            }
+        }),
+        $.ajax({
+            url:hackBase + "/wm/core/switch/" + self.id + '/features/json',
+            dataType:"json",
+            success:function (data) {
+                //console.log("fetched  switch " + self.id + " features");
+                //console.log(data[self.id]);
+                // update port models
+                _.each(data[self.id].ports, function(p) {
+                    p.id = self.id+'-'+p.portNumber;
+                    if(p.name != p.portNumber) {
+                        p.name = p.portNumber + ' (' + p.name + ')';
+                    }
+                    p.status = '';
+                    p.status += (p.state & 1) ? 'DOWN' : 'UP';
+                    switch(p.currentFeatures & 0x7f) {
+                    case 1:
+                        p.status += ' 10 Mbps';
+                        break;
+                    case 2:
+                        p.status += ' 10 Mbps FDX';
+                        break;
+                    case 4:
+                        p.status += ' 100 Mbps';
+                        break;
+                    case 8:
+                        p.status += ' 100 Mbps FDX';
+                        break;
+                    case 16:
+                        p.status += ' 1 Gbps'; // RLY?
+                        break;
+                    case 32:
+                        p.status += ' 1 Gbps FDX';
+                        break;
+                    case 64:
+                        p.status += ' 10 Gbps FDX';
+                        break;
+                    }
+                    // TODO parse copper/fiber, autoneg, pause
+                    
+                    // this is a knda kludgy way to merge models
+                    var m = self.ports.get(p.id);
+                    if(m) {
+                        m.set(p, {silent: true});
+                    } else {
+                        self.ports.add(p, {silent: true});
+                    }
+                    //console.log(p);
+                });
+            }
+        })).done(function() {
+            self.ports.trigger('add'); // batch redraws
+        });
+    },
+    
+    loadFlows:function () {
+        var self = this;
+        //console.log("fetching switch " + this.id + " flows")
+        $.ajax({
+            url:hackBase + "/wm/core/switch/" + self.id + '/flow/json',
+            dataType:"json",
+            success:function (data) {
+                //console.log("fetched  switch " + self.id + " flows");
+                var flows = data[self.id];
+                //console.log(flows);
+
+                // create flow models
+                var i = 0;
+                _.each(flows, function(f) {
+                    f.id = self.id + '-' + i++;
+
+                    // build human-readable match
+                    f.matchHTML = '';
+                    if(!(f.match.wildcards & (1<<0))) { // input port
+                        f.matchHTML += "port=" + f.match.inputPort + ", ";
+                    }
+                    if(!(f.match.wildcards & (1<<1))) { // VLAN ID
+                        f.matchHTML += "VLAN=" + f.match.dataLayerVirtualLan + ", ";
+                    }
+                    if(!(f.match.wildcards & (1<<20))) { // VLAN prio
+                        f.matchHTML += "prio=" + f.match.dataLayerVirtualLanPriorityCodePoint  + ", ";
+                    }
+                    if(!(f.match.wildcards & (1<<2))) { // src MAC
+                        f.matchHTML += "src=<a href='/host/" + f.match.dataLayerSource + "'>" +
+                        f.match.dataLayerSource + "</a>, ";
+                    }
+                    if(!(f.match.wildcards & (1<<3))) { // dest MAC
+                        f.matchHTML =+ "dest=<a href='/host/" + f.match.dataLayerDestination + "'>" +
+                        f.match.dataLayerDestination + "</a>, ";
+                    }
+                    if(!(f.match.wildcards & (1<<4))) { // Ethertype
+                        // TODO print a human-readable name instead of hex
+                        f.matchHTML += "ethertype=" + f.match.dataLayerType + ", ";
+                    }
+                    if(!(f.match.wildcards & (1<<5))) { // IP protocol
+                        // TODO print a human-readable name
+                        f.matchHTML += "proto=" + f.match.networkProtocol + ", ";
+                    }
+                    if(!(f.match.wildcards & (1<<6))) { // TCP/UDP source port
+                        f.matchHTML += "IP src port=" + f.match.transportSource + ", ";
+                    }
+                    if(!(f.match.wildcards & (1<<7))) { // TCP/UDP dest port
+                        f.matchHTML += "IP dest port=" + f.match.transportDestination  + ", ";
+                    }
+                    if(!(f.match.wildcards & (32<<8))) { // src IP
+                        f.matchHTML += "src=" + f.match.networkSource  + ", ";
+                    }
+                    if(!(f.match.wildcards & (32<<14))) { // dest IP
+                        f.matchHTML += "dest=" + f.match.networkDestination  + ", ";
+                    }
+                    if(!(f.match.wildcards & (1<<21))) { // IP TOS
+                        f.matchHTML += "TOS=" + f.match.networkTypeOfService  + ", ";
+                    }
+                    // remove trailing ", "
+                    f.matchHTML = f.matchHTML.substr(0, f.matchHTML.length - 2);
+
+                    // build human-readable action list
+                    f.actionText = _.reduce(f.actions, function (memo, a) {
+                        switch (a.type) {
+                            case "OUTPUT":
+                                return memo + "output " + a.port + ', ';
+                            case "OPAQUE_ENQUEUE":
+                                return memo + "enqueue " + a.port + ':' + a.queueId +  ', ';
+                            case "STRIP_VLAN":
+                                return memo + "strip VLAN, ";
+                            case "SET_VLAN_ID":
+                                return memo + "VLAN=" + a.virtualLanIdentifier + ', ';
+                            case "SET_VLAN_PCP":
+                                return memo + "prio=" + a.virtualLanPriorityCodePoint + ', ';
+                            case "SET_DL_SRC":
+                                return memo + "src=" + a.dataLayerAddress + ', ';
+                            case "SET_DL_DST":
+                                return memo + "dest=" + a.dataLayerAddress + ', ';
+                            case "SET_NW_TOS":
+                                return memo + "TOS=" + a.networkTypeOfService + ', ';
+                            case "SET_NW_SRC":
+                                return memo + "src=" + a.networkAddress + ', ';
+                            case "SET_NW_DST":
+                                return memo + "dest=" + a.networkAddress + ', ';
+                            case "SET_TP_SRC":
+                                return memo + "src port=" + a.transportPort + ', ';
+                            case "SET_TP_DST":
+                                return memo + "dest port=" + a.transportPort + ', ';
+                        }
+                    }, "");
+                    // remove trailing ", "
+                    f.actionText = f.actionText.substr(0, f.actionText.length - 2);
+
+                    //console.log(f);
+                    self.flows.add(f, {silent: true});
+                });
+                self.flows.trigger('add');
+            }
+        });
+    },
+});
+
+window.SwitchCollection = Backbone.Collection.extend({
+
+    model:Switch,
+    
+    initialize:function () {
+        var self = this;
+        //console.log("fetching switch list")
+        $.ajax({
+            url:hackBase + "/wm/core/controller/switches/json",
+            dataType:"json",
+            success:function (data) {
+                //console.log("fetched  switch list: " + data.length);
+                //console.log(data);
+                _.each(data, function(sw) {self.add({id: sw['dpid'],
+                                                     inetAddress: sw.inetAddress,
+                                                     connectedSince: new Date(sw.connectedSince).toLocaleString()})});
+            }
+        });
+    },
+
+  fetch:function () {
+	this.initialize()
+    }
+
+
+});
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/topologymodel.js b/src/ext/floodlight/src/main/resources/web/js/models/topologymodel.js
new file mode 100644
index 0000000..c5d8f9b
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/topologymodel.js
@@ -0,0 +1,65 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.Topology = Backbone.Model.extend({
+
+    url:"/wm/topology/links/json",
+    
+    defaults:{
+        nodes: [],
+        links: [],
+    },
+
+    initialize:function () {
+        var self = this;
+        console.log("fetching topology")
+        $.ajax({
+            url:hackBase + self.url,
+            dataType:"json",
+            success:function (data) {
+                console.log("fetched topology: " + data.length);
+                // console.log(data);
+                self.nodes = {};
+                self.links = [];
+
+                // step 1: build unique array of switch IDs
+                /* this doesn't work if there's only one switch,
+                   because there are no switch-switch links
+                _.each(data, function (l) {
+                    self.nodes[l['src-switch']] = true;
+                    self.nodes[l['dst-switch']] = true;
+                });
+                // console.log(self.nodes);
+                var nl = _.keys(self.nodes);
+                */
+                var nl = swl.pluck('id');
+                self.nodes = _.map(nl, function (n) {return {name:n}});
+
+                // step 2: build array of links in format D3 expects
+                _.each(data, function (l) {
+                    self.links.push({source:nl.indexOf(l['src-switch']),
+                                     target:nl.indexOf(l['dst-switch']),
+                                     value:10});
+                });
+                // console.log(self.nodes);
+                // console.log(self.links);
+                self.trigger('change');
+                //self.set(data);
+            }
+        });
+    }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/utils.js b/src/ext/floodlight/src/main/resources/web/js/utils.js
new file mode 100644
index 0000000..c086e29
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/utils.js
@@ -0,0 +1,36 @@
+// template loader from Christophe Coenraets
+tpl = {
+
+    // Hash of preloaded templates for the app
+    templates:{},
+
+    // Recursively pre-load all the templates for the app.
+    // This implementation should be changed in a production environment. All the template files should be
+    // concatenated in a single file.
+    loadTemplates:function (names, callback) {
+
+        var that = this;
+
+        var loadTemplate = function (index) {
+            var name = names[index];
+            console.log('Loading template: ' + name);
+            $.get('tpl/' + name + '.html', function (data) {
+                that.templates[name] = data;
+                index++;
+                if (index < names.length) {
+                    loadTemplate(index);
+                } else {
+                    callback();
+                }
+            });
+        }
+
+        loadTemplate(0);
+    },
+
+    // Get template by name from hash of preloaded templates
+    get:function (name) {
+        return this.templates[name];
+    }
+
+};
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/flow.js b/src/ext/floodlight/src/main/resources/web/js/views/flow.js
new file mode 100644
index 0000000..65e0b71
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/flow.js
@@ -0,0 +1,69 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+// not used for now
+window.FlowView = Backbone.View.extend({
+
+    initialize:function () {
+        this.template = _.template(tpl.get('flow'));
+        this.model.bind("change", this.render, this);
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template(this.model.toJSON()));
+        return this;
+    }
+
+});
+
+window.FlowListItemView = Backbone.View.extend({
+
+    tagName:"tr",
+
+    initialize:function () {
+        this.template = _.template(tpl.get('flow-list-item'));
+        this.model.bind("change", this.render, this);
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template(this.model.toJSON()));
+        return this;
+    }
+
+});
+
+// TODO throughput (bps) and pps sparklines would be nice here
+// TODO hovering over a MAC address could show a compact view of that host
+window.FlowListView = Backbone.View.extend({
+
+    initialize:function () {
+        this.template = _.template(tpl.get('flow-list'));
+        this.model.bind("change", this.render, this);
+        this.model.bind("add", this.render, this);
+    },
+
+    render:function (eventName) {
+        // console.log("rendering flow list view: " + this.model.models.length);
+        $(this.el).html(this.template({nflows:this.model.length}));
+        _.each(this.model.models, function (f) {
+            $(this.el).find('table.flow-table > tbody')
+                .append(new FlowListItemView({model:f}).render().el);
+        }, this);
+        return this;
+    },
+
+});
+
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/header.js b/src/ext/floodlight/src/main/resources/web/js/views/header.js
new file mode 100644
index 0000000..a5fbe00
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/header.js
@@ -0,0 +1,46 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.HeaderView = Backbone.View.extend({
+
+    initialize:function () {
+        this.template = _.template(tpl.get('header'));
+        // this.searchResults = new HostCollection();
+        // this.searchresultsView = new SearchListView({model:this.searchResults, className:'dropdown-menu'});
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template());
+        // $('.navbar-search', this.el).append(this.searchresultsView.render().el);
+        return this;
+    },
+
+    events:{
+        "keyup .search-query":"search"
+    },
+
+    search:function (event) {
+//        var key = event.target.value;
+        var key = $('#searchText').val();
+        console.log('search ' + key);
+        // TODO search the host and switch lists
+        this.searchResults.findByName(key);
+        setTimeout(function () {
+            $('#searchForm').addClass('open');
+        });
+    }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/home.js b/src/ext/floodlight/src/main/resources/web/js/views/home.js
new file mode 100644
index 0000000..0283002
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/home.js
@@ -0,0 +1,41 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.HomeView = Backbone.View.extend({
+
+    initialize:function () {
+        // console.log('Initializing Home View');
+        this.template = _.template(tpl.get('home'));
+    },
+
+    events:{
+        "click #showMeBtn":"showMeBtnClick"
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template());
+        var stats = new Status();
+        $(this.el).find('#controller-status').html(new StatusView({model:stats}).render().el);
+        $(this.el).find('#switch-list').html(new SwitchListView({model:swl}).render().el);
+        $(this.el).find('#host-list').html(new HostListView({model:hl}).render().el);
+        return this;
+    },
+
+    showMeBtnClick:function () {
+        app.headerView.search();
+    }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/host.js b/src/ext/floodlight/src/main/resources/web/js/views/host.js
new file mode 100644
index 0000000..5bb7b0e
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/host.js
@@ -0,0 +1,66 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.HostView = Backbone.View.extend({
+
+    initialize:function () {
+        this.template = _.template(tpl.get('host'));
+        this.model.bind("change", this.render, this);
+        this.model.bind("add", this.render, this);
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template(this.model.toJSON()));
+        return this;
+    }
+
+});
+
+window.HostListView = Backbone.View.extend({
+
+    initialize:function () {
+        var self = this;
+        this.template = _.template(tpl.get('host-list'));
+        this.model.bind("change", this.render, this);
+        this.model.bind("add", this.render, this);
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template({nhosts:hl.length}));
+        _.each(this.model.models, function (h) {
+            $(this.el).find('table.host-table > tbody')
+                .append(new HostListItemView({model:h}).render().el);
+        }, this);
+        return this;
+    }
+});
+
+window.HostListItemView = Backbone.View.extend({
+
+    tagName:"tr",
+
+    initialize:function () {
+        this.template = _.template(tpl.get('host-list-item'));
+        this.model.bind("change", this.render, this);
+        this.model.bind("destroy", this.close, this);
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template(this.model.toJSON()));
+        return this;
+    }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/port.js b/src/ext/floodlight/src/main/resources/web/js/views/port.js
new file mode 100644
index 0000000..e9aadb9
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/port.js
@@ -0,0 +1,70 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+// not used for now
+window.PortView = Backbone.View.extend({
+
+    initialize:function () {
+        this.template = _.template(tpl.get('port'));
+        this.model.bind("change", this.render, this);
+        //this.model.bind("destroy", this.close, this);
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template(this.model.toJSON()));
+        return this;
+    }
+
+});
+
+window.PortListItemView = Backbone.View.extend({
+
+    tagName:"tr",
+
+    initialize:function () {
+        this.template = _.template(tpl.get('port-list-item'));
+        this.model.bind("change", this.render, this);
+        //this.model.bind("destroy", this.close, this);
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template(this.model.toJSON()));
+        return this;
+    }
+
+});
+
+// TODO throughput sparklines would be nice here
+window.PortListView = Backbone.View.extend({
+
+    initialize:function () {
+        this.template = _.template(tpl.get('port-list'));
+        this.model.bind("change", this.render, this);
+        this.model.bind("add", this.render, this);
+    },
+
+    render:function (eventName) {
+        // console.log("rendering port list view");
+        $(this.el).html(this.template({nports:this.model.length}));
+        _.each(this.model.models, function (p) {
+            $(this.el).find('table.port-table > tbody')
+                .append(new PortListItemView({model:p}).render().el);
+        }, this);
+        return this;
+    },
+
+});
+
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/status.js b/src/ext/floodlight/src/main/resources/web/js/views/status.js
new file mode 100644
index 0000000..52c6c1c
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/status.js
@@ -0,0 +1,30 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.StatusView = Backbone.View.extend({
+
+    initialize:function () {
+        this.template = _.template(tpl.get('status'));
+        this.model.bind("change", this.render, this);
+    },
+
+    render:function (eventName) {
+        // console.log("rendering status");
+        $(this.el).html(this.template(this.model.toJSON()));
+        //$(this.el).html(this.template());
+        return this;
+    }
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/switch.js b/src/ext/floodlight/src/main/resources/web/js/views/switch.js
new file mode 100644
index 0000000..178dd99
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/switch.js
@@ -0,0 +1,72 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.SwitchView = Backbone.View.extend({
+
+    initialize:function () {
+        this.template = _.template(tpl.get('switch'));
+        this.model.bind("change", this.render, this);
+        //this.model.bind("destroy", this.close, this);
+        
+        // some parts of the model are large and are only needed in detail view
+        this.model.loadPorts();
+        this.model.loadFlows();
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template(this.model.toJSON()));
+        $(this.el).find('#port-list').html(new PortListView({model:this.model.ports}).render().el);
+        $(this.el).find('#flow-list').html(new FlowListView({model:this.model.flows}).render().el);
+        return this;
+    }
+
+});
+
+window.SwitchListItemView = Backbone.View.extend({
+
+    tagName:"tr",
+
+    initialize:function () {
+        this.template = _.template(tpl.get('switch-list-item'));
+        this.model.bind("change", this.render, this);
+    //this.model.bind("destroy", this.close, this);
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template(this.model.toJSON()));
+        return this;
+    }
+
+});
+
+window.SwitchListView = Backbone.View.extend({
+
+    initialize:function () {
+        this.template = _.template(tpl.get('switch-list'));
+        this.model.bind("change", this.render, this);
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template({nswitches:swl.length}));
+        _.each(this.model.models, function (sw) {
+            $(this.el).find('table.switch-table > tbody')
+                .append(new SwitchListItemView({model:sw}).render().el);
+        }, this);
+        return this;
+    },
+
+});
+
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/topology.js b/src/ext/floodlight/src/main/resources/web/js/views/topology.js
new file mode 100644
index 0000000..77129aa
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/topology.js
@@ -0,0 +1,111 @@
+/*
+   Copyright 2012 IBM
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+window.TopologyView = Backbone.View.extend({
+    initialize:function () {
+        this.template = _.template(tpl.get('topology'));
+        this.model.bind("change", this.render, this);
+        this.hosts = this.options.hosts.models;
+        this.host_links = [];
+    },
+
+    render:function (eventName) {
+        $(this.el).html(this.template());
+        var width = 900,
+            height = 600;
+        var color = d3.scale.category20();
+        var force = d3.layout.force()
+                             .charge(-500)
+                             .linkDistance(200)
+                             .size([width, height]);
+        var svg = d3.select("#topology-graph").append("svg")
+                    .attr("width", width)
+                    .attr("height", height);
+        if(this.model.nodes) {
+            for (var i = 0; i < this.model.nodes.length; i++) {
+                this.model.nodes[i].group = 1;
+                this.model.nodes[i].id = this.model.nodes[i].name;
+            }
+            
+            for (var i = 0; i < this.hosts.length; i++) {
+                host = this.hosts[i];
+                if (host.attributes['ipv4'].length > 0) {
+                    host.name = host.attributes['ipv4'][0] + "\n" + host.id;
+                } else {
+                    host.name = host.id;
+                }
+                host.group = 2;
+                //console.log(host);
+            }
+            
+            var all_nodes = this.model.nodes.concat(this.hosts);
+            
+            var all_nodes_map = [];
+            
+            _.each(all_nodes, function(n) {
+                all_nodes_map[n.id] = n;
+            });
+            
+            for (var i = 0; i < this.hosts.length; i++) {
+                host = this.hosts[i];
+                //for (var j = 0; j < host.attributes['attachmentPoint'].length; j++) {
+                for (var j = 0; j < 1; j++) { // FIXME hack to ignore multiple APs
+                    var link = {source:all_nodes_map[host.id],
+                                target:all_nodes_map[host.attributes['attachmentPoint'][j]['switchDPID']],
+                                value:10};
+                    //console.log(link);
+                    if ( link.source && link.target) {
+                        this.host_links.push(link);
+                    } else {
+                        console.log("Error: skipping link with undefined stuff!")
+                    }
+                }
+            }
+            
+            var all_links = this.model.links.concat(this.host_links);
+            
+            force.nodes(all_nodes).links(all_links).start();
+            var link = svg.selectAll("line.link").data(all_links).enter()
+                          .append("line").attr("class", "link")
+                          .style("stroke", function (d) { return "black"; });
+            var node = svg.selectAll(".node").data(all_nodes)
+                          .enter().append("g")
+                          .attr("class", "node")
+                          .call(force.drag);
+                          
+            node.append("image")
+                .attr("xlink:href", function (d) {return d.group==1 ? "/ui/img/switch.png" : "/ui/img/server.png"})
+                .attr("x", -16).attr("y", -16)
+                .attr("width", 32).attr("height", 32);
+            node.append("text").attr("dx", 20).attr("dy", ".35em")
+                .text(function(d) { return d.name });
+            node.on("click", function (d) {
+                // TODO we could add some functionality here
+                console.log('clicked '+d.name);
+            });
+            node.append("title").text(function(d) { return d.name; });
+            force.on("tick", function() {
+                link.attr("x1", function(d) { return d.source.x; })
+                    .attr("y1", function(d) { return d.source.y; })
+                    .attr("x2", function(d) { return d.target.x; })
+                    .attr("y2", function(d) { return d.target.y; });
+                node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
+                
+            });
+        }
+        return this;
+    }
+});
diff --git a/src/ext/floodlight/src/main/resources/web/lib/backbone-min.js b/src/ext/floodlight/src/main/resources/web/lib/backbone-min.js
new file mode 100644
index 0000000..c1c0d4f
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/backbone-min.js
@@ -0,0 +1,38 @@
+// Backbone.js 0.9.2
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
+{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
+z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
+{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
+b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
+b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
+a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
+h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
+return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
+{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
+!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
+this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
+l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
+a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
+shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
+this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
+e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
+{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
+arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
+C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
+this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
+""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
+!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
+this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
+stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
+function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
+this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
+f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
+for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
+!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
+e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
+b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);
diff --git a/src/ext/floodlight/src/main/resources/web/lib/bootstrap-alert.js b/src/ext/floodlight/src/main/resources/web/lib/bootstrap-alert.js
new file mode 100644
index 0000000..d17f44e
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/bootstrap-alert.js
@@ -0,0 +1,94 @@
+/* ==========================================================
+ * bootstrap-alert.js v2.0.2
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function( $ ){
+
+  "use strict"
+
+ /* ALERT CLASS DEFINITION
+  * ====================== */
+
+  var dismiss = '[data-dismiss="alert"]'
+    , Alert = function ( el ) {
+        $(el).on('click', dismiss, this.close)
+      }
+
+  Alert.prototype = {
+
+    constructor: Alert
+
+  , close: function ( e ) {
+      var $this = $(this)
+        , selector = $this.attr('data-target')
+        , $parent
+
+      if (!selector) {
+        selector = $this.attr('href')
+        selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+      }
+
+      $parent = $(selector)
+      $parent.trigger('close')
+
+      e && e.preventDefault()
+
+      $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+      $parent
+        .trigger('close')
+        .removeClass('in')
+
+      function removeElement() {
+        $parent
+          .trigger('closed')
+          .remove()
+      }
+
+      $.support.transition && $parent.hasClass('fade') ?
+        $parent.on($.support.transition.end, removeElement) :
+        removeElement()
+    }
+
+  }
+
+
+ /* ALERT PLUGIN DEFINITION
+  * ======================= */
+
+  $.fn.alert = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('alert')
+      if (!data) $this.data('alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.alert.Constructor = Alert
+
+
+ /* ALERT DATA-API
+  * ============== */
+
+  $(function () {
+    $('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
+  })
+
+}( window.jQuery );
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/lib/bootstrap-dropdown.js b/src/ext/floodlight/src/main/resources/web/lib/bootstrap-dropdown.js
new file mode 100755
index 0000000..48d3ce0
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/bootstrap-dropdown.js
@@ -0,0 +1,92 @@
+/* ============================================================
+ * bootstrap-dropdown.js v2.0.0
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function( $ ){
+
+  "use strict"
+
+ /* DROPDOWN CLASS DEFINITION
+  * ========================= */
+
+  var toggle = '[data-toggle="dropdown"]'
+    , Dropdown = function ( element ) {
+        var $el = $(element).on('click.dropdown.data-api', this.toggle)
+        $('html').on('click.dropdown.data-api', function () {
+          $el.parent().removeClass('open')
+        })
+      }
+
+  Dropdown.prototype = {
+
+    constructor: Dropdown
+
+  , toggle: function ( e ) {
+      var $this = $(this)
+        , selector = $this.attr('data-target')
+        , $parent
+        , isActive
+
+      if (!selector) {
+        selector = $this.attr('href')
+        selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+      }
+
+      $parent = $(selector)
+      $parent.length || ($parent = $this.parent())
+
+      isActive = $parent.hasClass('open')
+
+      clearMenus()
+      !isActive && $parent.toggleClass('open')
+
+      return false
+    }
+
+  }
+
+  function clearMenus() {
+    $(toggle).parent().removeClass('open')
+  }
+
+
+  /* DROPDOWN PLUGIN DEFINITION
+   * ========================== */
+
+  $.fn.dropdown = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('dropdown')
+      if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  /* APPLY TO STANDARD DROPDOWN ELEMENTS
+   * =================================== */
+
+  $(function () {
+    $('html').on('click.dropdown.data-api', clearMenus)
+    $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+  })
+
+}( window.jQuery )
diff --git a/src/ext/floodlight/src/main/resources/web/lib/d3.v2.min.js b/src/ext/floodlight/src/main/resources/web/lib/d3.v2.min.js
new file mode 100644
index 0000000..dddd929
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/d3.v2.min.js
@@ -0,0 +1,4 @@
+(function(){function e(a,b){try{for(var c in b)Object.defineProperty(a.prototype,c,{value:b[c],enumerable:!1})}catch(d){a.prototype=b}}function g(a){var b=-1,c=a.length,d=[];while(++b<c)d.push(a[b]);return d}function h(a){return Array.prototype.slice.call(a)}function k(){}function n(){return this}function o(a,b,c){return function(){var d=c.apply(b,arguments);return arguments.length?a:d}}function p(a){return a!=null&&!isNaN(a)}function q(a){return a.length}function s(a){return a==null}function t(a){return a.replace(/(^\s+)|(\s+$)/g,"").replace(/\s+/g," ")}function u(a){var b=1;while(a*b%1)b*=10;return b}function x(){}function y(a){function d(){var c=b,d=-1,e=c.length,f;while(++d<e)(f=c[d].on)&&f.apply(this,arguments);return a}var b=[],c=new k;return d.on=function(d,e){var f=c.get(d),g;return arguments.length<2?f&&f.on:(f&&(f.on=null,b=b.slice(0,g=b.indexOf(f)).concat(b.slice(g+1)),c.remove(d)),e&&b.push(c.set(d,{on:e})),a)},d}function B(a,b){return b-(a?1+Math.floor(Math.log(a+Math.pow(10,1+Math.floor(Math.log(a)/Math.LN10)-b))/Math.LN10):1)}function C(a){return a+""}function D(a){var b=a.lastIndexOf("."),c=b>=0?a.substring(b):(b=a.length,""),d=[];while(b>0)d.push(a.substring(b-=3,b+3));return d.reverse().join(",")+c}function F(a,b){return{scale:Math.pow(10,(8-b)*3),symbol:a}}function L(a){return function(b){return b<=0?0:b>=1?1:a(b)}}function M(a){return function(b){return 1-a(1-b)}}function N(a){return function(b){return.5*(b<.5?a(2*b):2-a(2-2*b))}}function O(a){return a}function P(a){return function(b){return Math.pow(b,a)}}function Q(a){return 1-Math.cos(a*Math.PI/2)}function R(a){return Math.pow(2,10*(a-1))}function S(a){return 1-Math.sqrt(1-a*a)}function T(a,b){var c;return arguments.length<2&&(b=.45),arguments.length<1?(a=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/a),function(d){return 1+a*Math.pow(2,10*-d)*Math.sin((d-c)*2*Math.PI/b)}}function U(a){return a||(a=1.70158),function(b){return b*b*((a+1)*b-a)}}function V(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375}function W(){d3.event.stopPropagation(),d3.event.preventDefault()}function X(){var a=d3.event,b;while(b=a.sourceEvent)a=b;return a}function Y(a){var b=new x,c=0,d=arguments.length;while(++c<d)b[arguments[c]]=y(b);return b.of=function(c,d){return function(e){try{var f=e.sourceEvent=d3.event;e.target=a,d3.event=e,b[e.type].apply(c,d)}finally{d3.event=f}}},b}function $(a){return a=="transform"?d3.interpolateTransform:d3.interpolate}function _(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return(c-a)*b}}function ba(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return Math.max(0,Math.min(1,(c-a)*b))}}function bb(a,b,c){return new bc(a,b,c)}function bc(a,b,c){this.r=a,this.g=b,this.b=c}function bd(a){return a<16?"0"+Math.max(0,a).toString(16):Math.min(255,a).toString(16)}function be(a,b,c){var d=0,e=0,f=0,g,h,i;g=/([a-z]+)\((.*)\)/i.exec(a);if(g){h=g[2].split(",");switch(g[1]){case"hsl":return c(parseFloat(h[0]),parseFloat(h[1])/100,parseFloat(h[2])/100);case"rgb":return b(bg(h[0]),bg(h[1]),bg(h[2]))}}return(i=bh.get(a))?b(i.r,i.g,i.b):(a!=null&&a.charAt(0)==="#"&&(a.length===4?(d=a.charAt(1),d+=d,e=a.charAt(2),e+=e,f=a.charAt(3),f+=f):a.length===7&&(d=a.substring(1,3),e=a.substring(3,5),f=a.substring(5,7)),d=parseInt(d,16),e=parseInt(e,16),f=parseInt(f,16)),b(d,e,f))}function bf(a,b,c){var d=Math.min(a/=255,b/=255,c/=255),e=Math.max(a,b,c),f=e-d,g,h,i=(e+d)/2;return f?(h=i<.5?f/(e+d):f/(2-e-d),a==e?g=(b-c)/f+(b<c?6:0):b==e?g=(c-a)/f+2:g=(a-b)/f+4,g*=60):h=g=0,bi(g,h,i)}function bg(a){var b=parseFloat(a);return a.charAt(a.length-1)==="%"?Math.round(b*2.55):b}function bi(a,b,c){return new bj(a,b,c)}function bj(a,b,c){this.h=a,this.s=b,this.l=c}function bk(a,b,c){function f(a){return a>360?a-=360:a<0&&(a+=360),a<60?d+(e-d)*a/60:a<180?e:a<240?d+(e-d)*(240-a)/60:d}function g(a){return Math.round(f(a)*255)}var d,e;return a%=360,a<0&&(a+=360),b=b<0?0:b>1?1:b,c=c<0?0:c>1?1:c,e=c<=.5?c*(1+b):c+b-c*b,d=2*c-e,bb(g(a+120),g(a),g(a-120))}function bl(a){return j(a,br),a}function bs(a){return function(){return bm(a,this)}}function bt(a){return function(){return bn(a,this)}}function bv(a,b){function f(){if(b=this.classList)return b.add(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;c.lastIndex=0,c.test(e)||(e=t(e+" "+a),d?b.baseVal=e:this.className=e)}function g(){if(b=this.classList)return b.remove(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;e=t(e.replace(c," ")),d?b.baseVal=e:this.className=e}function h(){(b.apply(this,arguments)?f:g).call(this)}var c=new RegExp("(^|\\s+)"+d3.requote(a)+"(\\s+|$)","g");if(arguments.length<2){var d=this.node();if(e=d.classList)return e.contains(a);var e=d.className;return c.lastIndex=0,c.test(e.baseVal!=null?e.baseVal:e)}return this.each(typeof b=="function"?h:b?f:g)}function bw(a){return{__data__:a}}function bx(a){return function(){return bq(this,a)}}function by(a){return arguments.length||(a=d3.ascending),function(b,c){return a(b&&b.__data__,c&&c.__data__)}}function bA(a){return j(a,bB),a}function bC(a,b,c){j(a,bG);var d=new k,e=d3.dispatch("start","end"),f=bO;return a.id=b,a.time=c,a.tween=function(b,c){return arguments.length<2?d.get(b):(c==null?d.remove(b):d.set(b,c),a)},a.ease=function(b){return arguments.length?(f=typeof b=="function"?b:d3.ease.apply(d3,arguments),a):f},a.each=function(b,c){return arguments.length<2?bP.call(a,b):(e.on(b,c),a)},d3.timer(function(g){return a.each(function(h,i,j){function p(a){return o.active>b?r():(o.active=b,d.forEach(function(a,b){(tween=b.call(l,h,i))&&k.push(tween)}),e.start.call(l,h,i),q(a)||d3.timer(q,0,c),1)}function q(a){if(o.active!==b)return r();var c=(a-m)/n,d=f(c),g=k.length;while(g>0)k[--g].call(l,d);if(c>=1)return r(),bI=b,e.end.call(l,h,i),bI=0,1}function r(){return--o.count||delete l.__transition__,1}var k=[],l=this,m=a[j][i].delay,n=a[j][i].duration,o=l.__transition__||(l.__transition__={active:0,count:0});++o.count,m<=g?p(g):d3.timer(p,m,c)}),1},0,c),a}function bE(a,b,c){return c!=""&&bD}function bF(a,b){function d(a,d,e){var f=b.call(this,a,d);return f==null?e!=""&&bD:e!=f&&c(e,f)}function e(a,d,e){return e!=b&&c(e,b)}var c=$(a);return typeof b=="function"?d:b==null?bE:(b+="",e)}function bP(a){var b=bI,c=bO,d=bM,e=bN;bI=this.id,bO=this.ease();for(var f=0,g=this.length;f<g;f++)for(var h=this[f],i=0,j=h.length;i<j;i++){var k=h[i];k&&(bM=this[f][i].delay,bN=this[f][i].duration,a.call(k=k.node,k.__data__,i,f))}return bI=b,bO=c,bM=d,bN=e,this}function bT(){var a,b=Date.now(),c=bQ;while(c)a=b-c.then,a>=c.delay&&(c.flush=c.callback(a)),c=c.next;var d=bU()-b;d>24?(isFinite(d)&&(clearTimeout(bS),bS=setTimeout(bT,d)),bR=0):(bR=1,bV(bT))}function bU(){var a=null,b=bQ,c=Infinity;while(b)b.flush?b=a?a.next=b.next:bQ=b.next:(c=Math.min(c,b.then+b.delay),b=(a=b).next);return c}function bW(a){var b=[a.a,a.b],c=[a.c,a.d],d=bY(b),e=bX(b,c),f=bY(bZ(c,b,-e))||0;b[0]*c[1]<c[0]*b[1]&&(b[0]*=-1,b[1]*=-1,d*=-1,e*=-1),this.rotate=(d?Math.atan2(b[1],b[0]):Math.atan2(-c[0],c[1]))*b$,this.translate=[a.e,a.f],this.scale=[d,f],this.skew=f?Math.atan2(e,f)*b$:0}function bX(a,b){return a[0]*b[0]+a[1]*b[1]}function bY(a){var b=Math.sqrt(bX(a,a));return b&&(a[0]/=b,a[1]/=b),b}function bZ(a,b,c){return a[0]+=c*b[0],a[1]+=c*b[1],a}function ca(a,b){var c=a.ownerSVGElement||a;if(c.createSVGPoint){var d=c.createSVGPoint();if(b_<0&&(window.scrollX||window.scrollY)){c=d3.select(document.body).append("svg").style("position","absolute").style("top",0).style("left",0);var e=c[0][0].getScreenCTM();b_=!e.f&&!e.e,c.remove()}return b_?(d.x=b.pageX,d.y=b.pageY):(d.x=b.clientX,d.y=b.clientY),d=d.matrixTransform(a.getScreenCTM().inverse()),[d.x,d.y]}var f=a.getBoundingClientRect();return[b.clientX-f.left-a.clientLeft,b.clientY-f.top-a.clientTop]}function cb(){}function cc(a){var b=a[0],c=a[a.length-1];return b<c?[b,c]:[c,b]}function cd(a){return a.rangeExtent?a.rangeExtent():cc(a.range())}function ce(a,b){var c=0,d=a.length-1,e=a[c],f=a[d],g;f<e&&(g=c,c=d,d=g,g=e,e=f,f=g);if(g=f-e)b=b(g),a[c]=b.floor(e),a[d]=b.ceil(f);return a}function cf(){return Math}function cg(a,b,c,d){function g(){var g=Math.min(a.length,b.length)>2?cn:cm,i=d?ba:_;return e=g(a,b,i,c),f=g(b,a,i,d3.interpolate),h}function h(a){return e(a)}var e,f;return h.invert=function(a){return f(a)},h.domain=function(b){return arguments.length?(a=b.map(Number),g()):a},h.range=function(a){return arguments.length?(b=a,g()):b},h.rangeRound=function(a){return h.range(a).interpolate(d3.interpolateRound)},h.clamp=function(a){return arguments.length?(d=a,g()):d},h.interpolate=function(a){return arguments.length?(c=a,g()):c},h.ticks=function(b){return ck(a,b)},h.tickFormat=function(b){return cl(a,b)},h.nice=function(){return ce(a,ci),g()},h.copy=function(){return cg(a,b,c,d)},g()}function ch(a,b){return d3.rebind(a,b,"range","rangeRound","interpolate","clamp")}function ci(a){return a=Math.pow(10,Math.round(Math.log(a)/Math.LN10)-1),{floor:function(b){return Math.floor(b/a)*a},ceil:function(b){return Math.ceil(b/a)*a}}}function cj(a,b){var c=cc(a),d=c[1]-c[0],e=Math.pow(10,Math.floor(Math.log(d/b)/Math.LN10)),f=b/d*e;return f<=.15?e*=10:f<=.35?e*=5:f<=.75&&(e*=2),c[0]=Math.ceil(c[0]/e)*e,c[1]=Math.floor(c[1]/e)*e+e*.5,c[2]=e,c}function ck(a,b){return d3.range.apply(d3,cj(a,b))}function cl(a,b){return d3.format(",."+Math.max(0,-Math.floor(Math.log(cj(a,b)[2])/Math.LN10+.01))+"f")}function cm(a,b,c,d){var e=c(a[0],a[1]),f=d(b[0],b[1]);return function(a){return f(e(a))}}function cn(a,b,c,d){var e=[],f=[],g=0,h=Math.min(a.length,b.length)-1;a[h]<a[0]&&(a=a.slice().reverse(),b=b.slice().reverse());while(++g<=h)e.push(c(a[g-1],a[g])),f.push(d(b[g-1],b[g]));return function(b){var c=d3.bisect(a,b,1,h)-1;return f[c](e[c](b))}}function co(a,b){function d(c){return a(b(c))}var c=b.pow;return d.invert=function(b){return c(a.invert(b))},d.domain=function(e){return arguments.length?(b=e[0]<0?cr:cq,c=b.pow,a.domain(e.map(b)),d):a.domain().map(c)},d.nice=function(){return a.domain(ce(a.domain(),cf)),d},d.ticks=function(){var d=cc(a.domain()),e=[];if(d.every(isFinite)){var f=Math.floor(d[0]),g=Math.ceil(d[1]),h=c(d[0]),i=c(d[1]);if(b===cr){e.push(c(f));for(;f++<g;)for(var j=9;j>0;j--)e.push(c(f)*j)}else{for(;f<g;f++)for(var j=1;j<10;j++)e.push(c(f)*j);e.push(c(f))}for(f=0;e[f]<h;f++);for(g=e.length;e[g-1]>i;g--);e=e.slice(f,g)}return e},d.tickFormat=function(a,e){arguments.length<2&&(e=cp);if(arguments.length<1)return e;var f=a/d.ticks().length,g=b===cr?(h=-1e-12,Math.floor):(h=1e-12,Math.ceil),h;return function(a){return a/c(g(b(a)+h))<f?e(a):""}},d.copy=function(){return co(a.copy(),b)},ch(d,a)}function cq(a){return Math.log(a<0?0:a)/Math.LN10}function cr(a){return-Math.log(a>0?0:-a)/Math.LN10}function cs(a,b){function e(b){return a(c(b))}var c=ct(b),d=ct(1/b);return e.invert=function(b){return d(a.invert(b))},e.domain=function(b){return arguments.length?(a.domain(b.map(c)),e):a.domain().map(d)},e.ticks=function(a){return ck(e.domain(),a)},e.tickFormat=function(a){return cl(e.domain(),a)},e.nice=function(){return e.domain(ce(e.domain(),ci))},e.exponent=function(a){if(!arguments.length)return b;var f=e.domain();return c=ct(b=a),d=ct(1/b),e.domain(f)},e.copy=function(){return cs(a.copy(),b)},ch(e,a)}function ct(a){return function(b){return b<0?-Math.pow(-b,a):Math.pow(b,a)}}function cu(a,b){function f(b){return d[((c.get(b)||c.set(b,a.push(b)))-1)%d.length]}function g(b,c){return d3.range(a.length).map(function(a){return b+c*a})}var c,d,e;return f.domain=function(d){if(!arguments.length)return a;a=[],c=new k;var e=-1,g=d.length,h;while(++e<g)c.has(h=d[e])||c.set(h,a.push(h));return f[b.t](b.x,b.p)},f.range=function(a){return arguments.length?(d=a,e=0,b={t:"range",x:a},f):d},f.rangePoints=function(c,h){arguments.length<2&&(h=0);var i=c[0],j=c[1],k=(j-i)/(a.length-1+h);return d=g(a.length<2?(i+j)/2:i+k*h/2,k),e=0,b={t:"rangePoints",x:c,p:h},f},f.rangeBands=function(c,h){arguments.length<2&&(h=0);var i=c[1]<c[0],j=c[i-0],k=c[1-i],l=(k-j)/(a.length+h);return d=g(j+l*h,l),i&&d.reverse(),e=l*(1-h),b={t:"rangeBands",x:c,p:h},f},f.rangeRoundBands=function(c,h){arguments.length<2&&(h=0);var i=c[1]<c[0],j=c[i-0],k=c[1-i],l=Math.floor((k-j)/(a.length+h)),m=k-j-(a.length-h)*l;return d=g(j+Math.round(m/2),l),i&&d.reverse(),e=Math.round(l*(1-h)),b={t:"rangeRoundBands",x:c,p:h},f},f.rangeBand=function(){return e},f.rangeExtent=function(){return cc(b.x)},f.copy=function(){return cu(a,b)},f.domain(a)}function cz(a,b){function d(){var d=0,f=a.length,g=b.length;c=[];while(++d<g)c[d-1]=d3.quantile(a,d/g);return e}function e(a){return isNaN(a=+a)?NaN:b[d3.bisect(c,a)]}var c;return e.domain=function(b){return arguments.length?(a=b.filter(function(a){return!isNaN(a)}).sort(d3.ascending),d()):a},e.range=function(a){return arguments.length?(b=a,d()):b},e.quantiles=function(){return c},e.copy=function(){return cz(a,b)},d()}function cA(a,b,c){function f(b){return c[Math.max(0,Math.min(e,Math.floor(d*(b-a))))]}function g(){return d=c.length/(b-a),e=c.length-1,f}var d,e;return f.domain=function(c){return arguments.length?(a=+c[0],b=+c[c.length-1],g()):[a,b]},f.range=function(a){return arguments.length?(c=a,g()):c},f.copy=function(){return cA(a,b,c)},g()}function cB(a){function b(a){return+a}return b.invert=b,b.domain=b.range=function(c){return arguments.length?(a=c.map(b),b):a},b.ticks=function(b){return ck(a,b)},b.tickFormat=function(b){return cl(a,b)},b.copy=function(){return cB(a)},b}function cE(a){return a.innerRadius}function cF(a){return a.outerRadius}function cG(a){return a.startAngle}function cH(a){return a.endAngle}function cI(a){function g(d){return d.length<1?null:"M"+e(a(cJ(this,d,b,c)),f)}var b=cK,c=cL,d=cM,e=cN.get(d),f=.7;return g.x=function(a){return arguments.length?(b=a,g):b},g.y=function(a){return arguments.length?(c=a,g):c},g.interpolate=function(a){return arguments.length?(cN.has(a+="")||(a=cM),e=cN.get(d=a),g):d},g.tension=function(a){return arguments.length?(f=a,g):f},g}function cJ(a,b,c,d){var e=[],f=-1,g=b.length,h=typeof c=="function",i=typeof d=="function",j;if(h&&i)while(++f<g)e.push([c.call(a,j=b[f],f),d.call(a,j,f)]);else if(h)while(++f<g)e.push([c.call(a,b[f],f),d]);else if(i)while(++f<g)e.push([c,d.call(a,b[f],f)]);else while(++f<g)e.push([c,d]);return e}function cK(a){return a[0]}function cL(a){return a[1]}function cO(a){var b=0,c=a.length,d=a[0],e=[d[0],",",d[1]];while(++b<c)e.push("L",(d=a[b])[0],",",d[1]);return e.join("")}function cP(a){var b=0,c=a.length,d=a[0],e=[d[0],",",d[1]];while(++b<c)e.push("V",(d=a[b])[1],"H",d[0]);return e.join("")}function cQ(a){var b=0,c=a.length,d=a[0],e=[d[0],",",d[1]];while(++b<c)e.push("H",(d=a[b])[0],"V",d[1]);return e.join("")}function cR(a,b){return a.length<4?cO(a):a[1]+cU(a.slice(1,a.length-1),cV(a,b))}function cS(a,b){return a.length<3?cO(a):a[0]+cU((a.push(a[0]),a),cV([a[a.length-2]].concat(a,[a[1]]),b))}function cT(a,b,c){return a.length<3?cO(a):a[0]+cU(a,cV(a,b))}function cU(a,b){if(b.length<1||a.length!=b.length&&a.length!=b.length+2)return cO(a);var c=a.length!=b.length,d="",e=a[0],f=a[1],g=b[0],h=g,i=1;c&&(d+="Q"+(f[0]-g[0]*2/3)+","+(f[1]-g[1]*2/3)+","+f[0]+","+f[1],e=a[1],i=2);if(b.length>1){h=b[1],f=a[i],i++,d+="C"+(e[0]+g[0])+","+(e[1]+g[1])+","+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1];for(var j=2;j<b.length;j++,i++)f=a[i],h=b[j],d+="S"+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1]}if(c){var k=a[i];d+="Q"+(f[0]+h[0]*2/3)+","+(f[1]+h[1]*2/3)+","+k[0]+","+k[1]}return d}function cV(a,b){var c=[],d=(1-b)/2,e,f=a[0],g=a[1],h=1,i=a.length;while(++h<i)e=f,f=g,g=a[h],c.push([d*(g[0]-e[0]),d*(g[1]-e[1])]);return c}function cW(a){if(a.length<3)return cO(a);var b=1,c=a.length,d=a[0],e=d[0],f=d[1],g=[e,e,e,(d=a[1])[0]],h=[f,f,f,d[1]],i=[e,",",f];dc(i,g,h);while(++b<c)d=a[b],g.shift(),g.push(d[0]),h.shift(),h.push(d[1]),dc(i,g,h);b=-1;while(++b<2)g.shift(),g.push(d[0]),h.shift(),h.push(d[1]),dc(i,g,h);return i.join("")}function cX(a){if(a.length<4)return cO(a);var b=[],c=-1,d=a.length,e,f=[0],g=[0];while(++c<3)e=a[c],f.push(e[0]),g.push(e[1]);b.push(c$(db,f)+","+c$(db,g)),--c;while(++c<d)e=a[c],f.shift(),f.push(e[0]),g.shift(),g.push(e[1]),dc(b,f,g);return b.join("")}function cY(a){var b,c=-1,d=a.length,e=d+4,f,g=[],h=[];while(++c<4)f=a[c%d],g.push(f[0]),h.push(f[1]);b=[c$(db,g),",",c$(db,h)],--c;while(++c<e)f=a[c%d],g.shift(),g.push(f[0]),h.shift(),h.push(f[1]),dc(b,g,h);return b.join("")}function cZ(a,b){var c=a.length-1,d=a[0][0],e=a[0][1],f=a[c][0]-d,g=a[c][1]-e,h=-1,i,j;while(++h<=c)i=a[h],j=h/c,i[0]=b*i[0]+(1-b)*(d+j*f),i[1]=b*i[1]+(1-b)*(e+j*g);return cW(a)}function c$(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3]}function dc(a,b,c){a.push("C",c$(c_,b),",",c$(c_,c),",",c$(da,b),",",c$(da,c),",",c$(db,b),",",c$(db,c))}function dd(a,b){return(b[1]-a[1])/(b[0]-a[0])}function de(a){var b=0,c=a.length-1,d=[],e=a[0],f=a[1],g=d[0]=dd(e,f);while(++b<c)d[b]=g+(g=dd(e=f,f=a[b+1]));return d[b]=g,d}function df(a){var b=[],c,d,e,f,g=de(a),h=-1,i=a.length-1;while(++h<i)c=dd(a[h],a[h+1]),Math.abs(c)<1e-6?g[h]=g[h+1]=0:(d=g[h]/c,e=g[h+1]/c,f=d*d+e*e,f>9&&(f=c*3/Math.sqrt(f),g[h]=f*d,g[h+1]=f*e));h=-1;while(++h<=i)f=(a[Math.min(i,h+1)][0]-a[Math.max(0,h-1)][0])/(6*(1+g[h]*g[h])),b.push([f||0,g[h]*f||0]);return b}function dg(a){return a.length<3?cO(a):a[0]+cU(a,df(a))}function dh(a){var b,c=-1,d=a.length,e,f;while(++c<d)b=a[c],e=b[0],f=b[1]+cC,b[0]=e*Math.cos(f),b[1]=e*Math.sin(f);return a}function di(a){function j(f){if(f.length<1)return null;var j=cJ(this,f,b,d),k=cJ(this,f,b===c?dj(j):c,d===e?dk(j):e);return"M"+g(a(k),i)+"L"+h(a(j.reverse()),i)+"Z"}var b=cK,c=cK,d=0,e=cL,f,g,h,i=.7;return j.x=function(a){return arguments.length?(b=c=a,j):c},j.x0=function(a){return arguments.length?(b=a,j):b},j.x1=function(a){return arguments.length?(c=a,j):c},j.y=function(a){return arguments.length?(d=e=a,j):e},j.y0=function(a){return arguments.length?(d=a,j):d},j.y1=function(a){return arguments.length?(e=a,j):e},j.interpolate=function(a){return arguments.length?(cN.has(a+="")||(a=cM),g=cN.get(f=a),h=g.reverse||g,j):f},j.tension=function(a){return arguments.length?(i=a,j):i},j.interpolate("linear")}function dj(a){return function(b,c){return a[c][0]}}function dk(a){return function(b,c){return a[c][1]}}function dl(a){return a.source}function dm(a){return a.target}function dn(a){return a.radius}function dp(a){return a.startAngle}function dq(a){return a.endAngle}function dr(a){return[a.x,a.y]}function ds(a){return function(){var b=a.apply(this,arguments),c=b[0],d=b[1]+cC;return[c*Math.cos(d),c*Math.sin(d)]}}function dt(){return 64}function du(){return"circle"}function dv(a){var b=Math.sqrt(a/Math.PI);return"M0,"+b+"A"+b+","+b+" 0 1,1 0,"+ -b+"A"+b+","+b+" 0 1,1 0,"+b+"Z"}function dz(a,b){a.attr("transform",function(a){return"translate("+b(a)+",0)"})}function dA(a,b){a.attr("transform",function(a){return"translate(0,"+b(a)+")"})}function dB(a,b,c){e=[];if(c&&b.length>1){var d=cc(a.domain()),e,f=-1,g=b.length,h=(b[1]-b[0])/++c,i,j;while(++f<g)for(i=c;--i>0;)(j=+b[f]-i*h)>=d[0]&&e.push(j);for(--f,i=0;++i<c&&(j=+b[f]+i*h)<d[1];)e.push(j)}return e}function dG(){dE||(dE=d3.select("body").append("div").style("visibility","hidden").style("top",0).style("height",0).style("width",0).style("overflow-y","scroll").append("div").style("height","2000px").node().parentNode);var a=d3.event,b;try{dE.scrollTop=1e3,dE.dispatchEvent(a),b=1e3-dE.scrollTop}catch(c){b=a.wheelDelta||-a.detail*5}return b}function dH(a){var b=a.source,c=a.target,d=dJ(b,c),e=[b];while(b!==d)b=b.parent,e.push(b);var f=e.length;while(c!==d)e.splice(f,0,c),c=c.parent;return e}function dI(a){var b=[],c=a.parent;while(c!=null)b.push(a),a=c,c=c.parent;return b.push(a),b}function dJ(a,b){if(a===b)return a;var c=dI(a),d=dI(b),e=c.pop(),f=d.pop(),g=null;while(e===f)g=e,e=c.pop(),f=d.pop();return g}function dM(a){a.fixed|=2}function dN(a){a!==dL&&(a.fixed&=1)}function dO(){dL.fixed&=1,dK=dL=null}function dP(){dL.px=d3.event.x,dL.py=d3.event.y,dK.resume()}function dQ(a,b,c){var d=0,e=0;a.charge=0;if(!a.leaf){var f=a.nodes,g=f.length,h=-1,i;while(++h<g){i=f[h];if(i==null)continue;dQ(i,b,c),a.charge+=i.charge,d+=i.charge*i.cx,e+=i.charge*i.cy}}if(a.point){a.leaf||(a.point.x+=Math.random()-.5,a.point.y+=Math.random()-.5);var j=b*c[a.point.index];a.charge+=a.pointCharge=j,d+=j*a.point.x,e+=j*a.point.y}a.cx=d/a.charge,a.cy=e/a.charge}function dR(a){return 20}function dS(a){return 1}function dU(a){return a.x}function dV(a){return a.y}function dW(a,b,c){a.y0=b,a.y=c}function dZ(a){return d3.range(a.length)}function d$(a){var b=-1,c=a[0].length,d=[];while(++b<c)d[b]=0;return d}function d_(a){var b=1,c=0,d=a[0][1],e,f=a.length;for(;b<f;++b)(e=a[b][1])>d&&(c=b,d=e);return c}function ea(a){return a.reduce(eb,0)}function eb(a,b){return a+b[1]}function ec(a,b){return ed(a,Math.ceil(Math.log(b.length)/Math.LN2+1))}function ed(a,b){var c=-1,d=+a[0],e=(a[1]-d)/b,f=[];while(++c<=b)f[c]=e*c+d;return f}function ee(a){return[d3.min(a),d3.max(a)]}function ef(a,b){return d3.rebind(a,b,"sort","children","value"),a.links=ej,a.nodes=function(b){return ek=!0,(a.nodes=a)(b)},a}function eg(a){return a.children}function eh(a){return a.value}function ei(a,b){return b.value-a.value}function ej(a){return d3.merge(a.map(function(a){return(a.children||[]).map(function(b){return{source:a,target:b}})}))}function el(a,b){return a.value-b.value}function em(a,b){var c=a._pack_next;a._pack_next=b,b._pack_prev=a,b._pack_next=c,c._pack_prev=b}function en(a,b){a._pack_next=b,b._pack_prev=a}function eo(a,b){var c=b.x-a.x,d=b.y-a.y,e=a.r+b.r;return e*e-c*c-d*d>.001}function ep(a){function l(a){b=Math.min(a.x-a.r,b),c=Math.max(a.x+a.r,c),d=Math.min(a.y-a.r,d),e=Math.max(a.y+a.r,e)}var b=Infinity,c=-Infinity,d=Infinity,e=-Infinity,f=a.length,g,h,i,j,k;a.forEach(eq),g=a[0],g.x=-g.r,g.y=0,l(g);if(f>1){h=a[1],h.x=h.r,h.y=0,l(h);if(f>2){i=a[2],eu(g,h,i),l(i),em(g,i),g._pack_prev=i,em(i,h),h=g._pack_next;for(var m=3;m<f;m++){eu(g,h,i=a[m]);var n=0,o=1,p=1;for(j=h._pack_next;j!==h;j=j._pack_next,o++)if(eo(j,i)){n=1;break}if(n==1)for(k=g._pack_prev;k!==j._pack_prev;k=k._pack_prev,p++)if(eo(k,i))break;n?(o<p||o==p&&h.r<g.r?en(g,h=j):en(g=k,h),m--):(em(g,i),h=i,l(i))}}}var q=(b+c)/2,r=(d+e)/2,s=0;for(var m=0;m<f;m++){var t=a[m];t.x-=q,t.y-=r,s=Math.max(s,t.r+Math.sqrt(t.x*t.x+t.y*t.y))}return a.forEach(er),s}function eq(a){a._pack_next=a._pack_prev=a}function er(a){delete a._pack_next,delete a._pack_prev}function es(a){var b=a.children;b&&b.length?(b.forEach(es),a.r=ep(b)):a.r=Math.sqrt(a.value)}function et(a,b,c,d){var e=a.children;a.x=b+=d*a.x,a.y=c+=d*a.y,a.r*=d;if(e){var f=-1,g=e.length;while(++f<g)et(e[f],b,c,d)}}function eu(a,b,c){var d=a.r+c.r,e=b.x-a.x,f=b.y-a.y;if(d&&(e||f)){var g=b.r+c.r,h=Math.sqrt(e*e+f*f),i=Math.max(-1,Math.min(1,(d*d+h*h-g*g)/(2*d*h))),j=Math.acos(i),k=i*(d/=h),l=Math.sin(j)*d;c.x=a.x+k*e+l*f,c.y=a.y+k*f-l*e}else c.x=a.x+d,c.y=a.y}function ev(a){return 1+d3.max(a,function(a){return a.y})}function ew(a){return a.reduce(function(a,b){return a+b.x},0)/a.length}function ex(a){var b=a.children;return b&&b.length?ex(b[0]):a}function ey(a){var b=a.children,c;return b&&(c=b.length)?ey(b[c-1]):a}function ez(a,b){return a.parent==b.parent?1:2}function eA(a){var b=a.children;return b&&b.length?b[0]:a._tree.thread}function eB(a){var b=a.children,c;return b&&(c=b.length)?b[c-1]:a._tree.thread}function eC(a,b){var c=a.children;if(c&&(e=c.length)){var d,e,f=-1;while(++f<e)b(d=eC(c[f],b),a)>0&&(a=d)}return a}function eD(a,b){return a.x-b.x}function eE(a,b){return b.x-a.x}function eF(a,b){return a.depth-b.depth}function eG(a,b){function c(a,d){var e=a.children;if(e&&(i=e.length)){var f,g=null,h=-1,i;while(++h<i)f=e[h],c(f,g),g=f}b(a,d)}c(a,null)}function eH(a){var b=0,c=0,d=a.children,e=d.length,f;while(--e>=0)f=d[e]._tree,f.prelim+=b,f.mod+=b,b+=f.shift+(c+=f.change)}function eI(a,b,c){a=a._tree,b=b._tree;var d=c/(b.number-a.number);a.change+=d,b.change-=d,b.shift+=c,b.prelim+=c,b.mod+=c}function eJ(a,b,c){return a._tree.ancestor.parent==b.parent?a._tree.ancestor:c}function eK(a){return{x:a.x,y:a.y,dx:a.dx,dy:a.dy}}function eL(a,b){var c=a.x+b[3],d=a.y+b[0],e=a.dx-b[1]-b[3],f=a.dy-b[0]-b[2];return e<0&&(c+=e/2,e=0),f<0&&(d+=f/2,f=0),{x:c,y:d,dx:e,dy:f}}function eM(a){return a.map(eN).join(",")}function eN(a){return/[",\n]/.test(a)?'"'+a.replace(/\"/g,'""')+'"':a}function eP(a,b){return function(c){return c&&a.hasOwnProperty(c.type)?a[c.type](c):b}}function eQ(a){return"m0,"+a+"a"+a+","+a+" 0 1,1 0,"+ -2*a+"a"+a+","+a+" 0 1,1 0,"+2*a+"z"}function eR(a,b){eS.hasOwnProperty(a.type)&&eS[a.type](a,b)}function eT(a,b){eR(a.geometry,b)}function eU(a,b){for(var c=a.features,d=0,e=c.length;d<e;d++)eR(c[d].geometry,b)}function eV(a,b){for(var c=a.geometries,d=0,e=c.length;d<e;d++)eR(c[d],b)}function eW(a,b){for(var c=a.coordinates,d=0,e=c.length;d<e;d++)b.apply(null,c[d])}function eX(a,b){for(var c=a.coordinates,d=0,e=c.length;d<e;d++)for(var f=c[d],g=0,h=f.length;g<h;g++)b.apply(null,f[g])}function eY(a,b){for(var c=a.coordinates,d=0,e=c.length;d<e;d++)for(var f=c[d][0],g=0,h=f.length;g<h;g++)b.apply(null,f[g])}function eZ(a,b){b.apply(null,a.coordinates)}function e$(a,b){for(var c=a.coordinates[0],d=0,e=c.length;d<e;d++)b.apply(null,c[d])}function e_(a){return a.source}function fa(a){return a.target}function fb(a,b){function q(a){var b=Math.sin(o-(a*=o))/p,c=Math.sin(a)/p,f=b*g*d+c*m*j,i=b*g*e+c*m*k,l=b*h+c*n;return[Math.atan2(i,f)/eO,Math.atan2(l,Math.sqrt(f*f+i*i))/eO]}var c=a[0]*eO,d=Math.cos(c),e=Math.sin(c),f=a[1]*eO,g=Math.cos(f),h=Math.sin(f),i=b[0]*eO,j=Math.cos(i),k=Math.sin(i),l=b[1]*eO,m=Math.cos(l),n=Math.sin(l),o=q.d=Math.acos(Math.max(-1,Math.min(1,h*n+g*m*Math.cos(i-c)))),p=Math.sin(o);return q}function fe(a){var b=0,c=0;for(;;){if(a(b,c))return[b,c];b===0?(b=c+1,c=0):(b-=1,c+=1)}}function ff(a,b,c,d){var e,f,g,h,i,j,k;return e=d[a],f=e[0],g=e[1],e=d[b],h=e[0],i=e[1],e=d[c],j=e[0],k=e[1],(k-g)*(h-f)-(i-g)*(j-f)>0}function fg(a,b,c){return(c[0]-b[0])*(a[1]-b[1])<(c[1]-b[1])*(a[0]-b[0])}function fh(a,b,c,d){var e=a[0],f=b[0],g=c[0],h=d[0],i=a[1],j=b[1],k=c[1],l=d[1],m=e-g,n=f-e,o=h-g,p=i-k,q=j-i,r=l-k,s=(o*p-r*m)/(r*n-o*q);return[e+s*n,i+s*q]}function fj(a,b){var c={list:a.map(function(a,b){return{index:b,x:a[0],y:a[1]}}).sort(function(a,b){return a.y<b.y?-1:a.y>b.y?1:a.x<b.x?-1:a.x>b.x?1:0}),bottomSite:null},d={list:[],leftEnd:null,rightEnd:null,init:function(){d.leftEnd=d.createHalfEdge(null,"l"),d.rightEnd=d.createHalfEdge(null,"l"),d.leftEnd.r=d.rightEnd,d.rightEnd.l=d.leftEnd,d.list.unshift(d.leftEnd,d.rightEnd)},createHalfEdge:function(a,b){return{edge:a,side:b,vertex:null,l:null,r:null}},insert:function(a,b){b.l=a,b.r=a.r,a.r.l=b,a.r=b},leftBound:function(a){var b=d.leftEnd;do b=b.r;while(b!=d.rightEnd&&e.rightOf(b,a));return b=b.l,b},del:function(a){a.l.r=a.r,a.r.l=a.l,a.edge=null},right:function(a){return a.r},left:function(a){return a.l},leftRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[a.side]},rightRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[fi[a.side]]}},e={bisect:function(a,b){var c={region:{l:a,r:b},ep:{l:null,r:null}},d=b.x-a.x,e=b.y-a.y,f=d>0?d:-d,g=e>0?e:-e;return c.c=a.x*d+a.y*e+(d*d+e*e)*.5,f>g?(c.a=1,c.b=e/d,c.c/=d):(c.b=1,c.a=d/e,c.c/=e),c},intersect:function(a,b){var c=a.edge,d=b.edge;if(!c||!d||c.region.r==d.region.r)return null;var e=c.a*d.b-c.b*d.a;if(Math.abs(e)<1e-10)return null;var f=(c.c*d.b-d.c*c.b)/e,g=(d.c*c.a-c.c*d.a)/e,h=c.region.r,i=d.region.r,j,k;h.y<i.y||h.y==i.y&&h.x<i.x?(j=a,k=c):(j=b,k=d);var l=f>=k.region.r.x;return l&&j.side==="l"||!l&&j.side==="r"?null:{x:f,y:g}},rightOf:function(a,b){var c=a.edge,d=c.region.r,e=b.x>d.x;if(e&&a.side==="l")return 1;if(!e&&a.side==="r")return 0;if(c.a===1){var f=b.y-d.y,g=b.x-d.x,h=0,i=0;!e&&c.b<0||e&&c.b>=0?i=h=f>=c.b*g:(i=b.x+b.y*c.b>c.c,c.b<0&&(i=!i),i||(h=1));if(!h){var j=d.x-c.region.l.x;i=c.b*(g*g-f*f)<j*f*(1+2*g/j+c.b*c.b),c.b<0&&(i=!i)}}else{var k=c.c-c.a*b.x,l=b.y-k,m=b.x-d.x,n=k-d.y;i=l*l>m*m+n*n}return a.side==="l"?i:!i},endPoint:function(a,c,d){a.ep[c]=d;if(!a.ep[fi[c]])return;b(a)},distance:function(a,b){var c=a.x-b.x,d=a.y-b.y;return Math.sqrt(c*c+d*d)}},f={list:[],insert:function(a,b,c){a.vertex=b,a.ystar=b.y+c;for(var d=0,e=f.list,g=e.length;d<g;d++){var h=e[d];if(a.ystar>h.ystar||a.ystar==h.ystar&&b.x>h.vertex.x)continue;break}e.splice(d,0,a)},del:function(a){for(var b=0,c=f.list,d=c.length;b<d&&c[b]!=a;++b);c.splice(b,1)},empty:function(){return f.list.length===0},nextEvent:function(a){for(var b=0,c=f.list,d=c.length;b<d;++b)if(c[b]==a)return c[b+1];return null},min:function(){var a=f.list[0];return{x:a.vertex.x,y:a.ystar}},extractMin:function(){return f.list.shift()}};d.init(),c.bottomSite=c.list.shift();var g=c.list.shift(),h,i,j,k,l,m,n,o,p,q,r,s,t;for(;;){f.empty()||(h=f.min());if(g&&(f.empty()||g.y<h.y||g.y==h.y&&g.x<h.x))i=d.leftBound(g),j=d.right(i),n=d.rightRegion(i),s=e.bisect(n,g),m=d.createHalfEdge(s,"l"),d.insert(i,m),q=e.intersect(i,m),q&&(f.del(i),f.insert(i,q,e.distance(q,g))),i=m,m=d.createHalfEdge(s,"r"),d.insert(i,m),q=e.intersect(m,j),q&&f.insert(m,q,e.distance(q,g)),g=c.list.shift();else if(!f.empty())i=f.extractMin(),k=d.left(i),j=d.right(i),l=d.right(j),n=d.leftRegion(i),o=d.rightRegion(j),r=i.vertex,e.endPoint(i.edge,i.side,r),e.endPoint(j.edge,j.side,r),d.del(i),f.del(j),d.del(j),t="l",n.y>o.y&&(p=n,n=o,o=p,t="r"),s=e.bisect(n,o),m=d.createHalfEdge(s,t),d.insert(k,m),e.endPoint(s,fi[t],r),q=e.intersect(k,m),q&&(f.del(k),f.insert(k,q,e.distance(q,n))),q=e.intersect(m,l),q&&f.insert(m,q,e.distance(q,n));else break}for(i=d.right(d.leftEnd);i!=d.rightEnd;i=d.right(i))b(i.edge)}function fk(){return{leaf:!0,nodes:[],point:null}}function fl(a,b,c,d,e,f){if(!a(b,c,d,e,f)){var g=(c+e)*.5,h=(d+f)*.5,i=b.nodes;i[0]&&fl(a,i[0],c,d,g,h),i[1]&&fl(a,i[1],g,d,e,h),i[2]&&fl(a,i[2],c,h,g,f),i[3]&&fl(a,i[3],g,h,e,f)}}function fm(a){return{x:a[0],y:a[1]}}function fo(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function fq(a,b,c,d){var e,f,g=0,h=b.length,i=c.length;while(g<h){if(d>=i)return-1;e=b.charCodeAt(g++);if(e==37){f=fw[b.charAt(g++)];if(!f||(d=f(a,c,d))<0)return-1}else if(e!=c.charCodeAt(d++))return-1}return d}function fx(a,b,c){return fz.test(b.substring(c,c+=3))?c:-1}function fy(a,b,c){fA.lastIndex=0;var d=fA.exec(b.substring(c,c+10));return d?c+=d[0].length:-1}function fB(a,b,c){var d=fC.get(b.substring(c,c+=3).toLowerCase());return d==null?-1:(a.m=d,c)}function fD(a,b,c){fE.lastIndex=0;var d=fE.exec(b.substring(c,c+12));return d?(a.m=fF.get(d[0].toLowerCase()),c+=d[0].length):-1}function fH(a,b,c){return fq(a,fv.c.toString(),b,c)}function fI(a,b,c){return fq(a,fv.x.toString(),b,c)}function fJ(a,b,c){return fq(a,fv.X.toString(),b,c)}function fK(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+4));return d?(a.y=+d[0],c+=d[0].length):-1}function fL(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.y=fM()+ +d[0],c+=d[0].length):-1}function fM(){return~~((new Date).getFullYear()/1e3)*1e3}function fN(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.m=d[0]-1,c+=d[0].length):-1}function fO(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.d=+d[0],c+=d[0].length):-1}function fP(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.H=+d[0],c+=d[0].length):-1}function fQ(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.M=+d[0],c+=d[0].length):-1}function fR(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.S=+d[0],c+=d[0].length):-1}function fS(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+3));return d?(a.L=+d[0],c+=d[0].length):-1}function fU(a,b,c){var d=fV.get(b.substring(c,c+=2).toLowerCase());return d==null?-1:(a.p=d,c)}function fW(a){var b=a.getTimezoneOffset(),c=b>0?"-":"+",d=~~(Math.abs(b)/60),e=Math.abs(b)%60;return c+fr(d)+fr(e)}function fY(a){return a.toISOString()}function fZ(a,b,c){function d(b){var c=a(b),d=f(c,1);return b-c<d-b?c:d}function e(c){return b(c=a(new fn(c-1)),1),c}function f(a,c){return b(a=new fn(+a),c),a}function g(a,d,f){var g=e(a),h=[];if(f>1)while(g<d)c(g)%f||h.push(new Date(+g)),b(g,1);else while(g<d)h.push(new Date(+g)),b(g,1);return h}function h(a,b,c){try{fn=fo;var d=new fo;return d._=a,g(d,b,c)}finally{fn=Date}}a.floor=a,a.round=d,a.ceil=e,a.offset=f,a.range=g;var i=a.utc=f$(a);return i.floor=i,i.round=f$(d),i.ceil=f$(e),i.offset=f$(f),i.range=h,a}function f$(a){return function(b,c){try{fn=fo;var d=new fo;return d._=b,a(d,c)._}finally{fn=Date}}}function f_(a,b,c){function d(b){return a(b)}return d.invert=function(b){return gb(a.invert(b))},d.domain=function(b){return arguments.length?(a.domain(b),d):a.domain().map(gb)},d.nice=function(a){var b=ga(d.domain());return d.domain([a.floor(b[0]),a.ceil(b[1])])},d.ticks=function(c,e){var f=ga(d.domain());if(typeof 
+c!="function"){var g=f[1]-f[0],h=g/c,i=d3.bisect(gf,h);if(i==gf.length)return b.year(f,c);if(!i)return a.ticks(c).map(gb);Math.log(h/gf[i-1])<Math.log(gf[i]/h)&&--i,c=b[i],e=c[1],c=c[0].range}return c(f[0],new Date(+f[1]+1),e)},d.tickFormat=function(){return c},d.copy=function(){return f_(a.copy(),b,c)},d3.rebind(d,a,"range","rangeRound","interpolate","clamp")}function ga(a){var b=a[0],c=a[a.length-1];return b<c?[b,c]:[c,b]}function gb(a){return new Date(a)}function gc(a){return function(b){var c=a.length-1,d=a[c];while(!d[1](b))d=a[--c];return d[0](b)}}function gd(a){var b=new Date(a,0,1);return b.setFullYear(a),b}function ge(a){var b=a.getFullYear(),c=gd(b),d=gd(b+1);return b+(a-c)/(d-c)}function gn(a){var b=new Date(Date.UTC(a,0,1));return b.setUTCFullYear(a),b}function go(a){var b=a.getUTCFullYear(),c=gn(b),d=gn(b+1);return b+(a-c)/(d-c)}Date.now||(Date.now=function(){return+(new Date)});try{document.createElement("div").style.setProperty("opacity",0,"")}catch(a){var b=CSSStyleDeclaration.prototype,c=b.setProperty;b.setProperty=function(a,b,d){c.call(this,a,b+"",d)}}d3={version:"2.8.1"};var f=h;try{f(document.documentElement.childNodes)[0].nodeType}catch(i){f=g}var j=[].__proto__?function(a,b){a.__proto__=b}:function(a,b){for(var c in b)a[c]=b[c]};d3.map=function(a){var b=new k;for(var c in a)b.set(c,a[c]);return b},e(k,{has:function(a){return l+a in this},get:function(a){return this[l+a]},set:function(a,b){return this[l+a]=b},remove:function(a){return a=l+a,a in this&&delete this[a]},keys:function(){var a=[];return this.forEach(function(b){a.push(b)}),a},values:function(){var a=[];return this.forEach(function(b,c){a.push(c)}),a},entries:function(){var a=[];return this.forEach(function(b,c){a.push({key:b,value:c})}),a},forEach:function(a){for(var b in this)b.charCodeAt(0)===m&&a.call(this,b.substring(1),this[b])}});var l="\0",m=l.charCodeAt(0);d3.functor=function(a){return typeof a=="function"?a:function(){return a}},d3.rebind=function(a,b){var c=1,d=arguments.length,e;while(++c<d)a[e=arguments[c]]=o(a,b,b[e]);return a},d3.ascending=function(a,b){return a<b?-1:a>b?1:a>=b?0:NaN},d3.descending=function(a,b){return b<a?-1:b>a?1:b>=a?0:NaN},d3.mean=function(a,b){var c=a.length,d,e=0,f=-1,g=0;if(arguments.length===1)while(++f<c)p(d=a[f])&&(e+=(d-e)/++g);else while(++f<c)p(d=b.call(a,a[f],f))&&(e+=(d-e)/++g);return g?e:undefined},d3.median=function(a,b){return arguments.length>1&&(a=a.map(b)),a=a.filter(p),a.length?d3.quantile(a.sort(d3.ascending),.5):undefined},d3.min=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++c<d&&((e=a[c])==null||e!=e))e=undefined;while(++c<d)(f=a[c])!=null&&e>f&&(e=f)}else{while(++c<d&&((e=b.call(a,a[c],c))==null||e!=e))e=undefined;while(++c<d)(f=b.call(a,a[c],c))!=null&&e>f&&(e=f)}return e},d3.max=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++c<d&&((e=a[c])==null||e!=e))e=undefined;while(++c<d)(f=a[c])!=null&&f>e&&(e=f)}else{while(++c<d&&((e=b.call(a,a[c],c))==null||e!=e))e=undefined;while(++c<d)(f=b.call(a,a[c],c))!=null&&f>e&&(e=f)}return e},d3.extent=function(a,b){var c=-1,d=a.length,e,f,g;if(arguments.length===1){while(++c<d&&((e=g=a[c])==null||e!=e))e=g=undefined;while(++c<d)(f=a[c])!=null&&(e>f&&(e=f),g<f&&(g=f))}else{while(++c<d&&((e=g=b.call(a,a[c],c))==null||e!=e))e=undefined;while(++c<d)(f=b.call(a,a[c],c))!=null&&(e>f&&(e=f),g<f&&(g=f))}return[e,g]},d3.random={normal:function(a,b){return arguments.length<2&&(b=1),arguments.length<1&&(a=0),function(){var c,d,e;do c=Math.random()*2-1,d=Math.random()*2-1,e=c*c+d*d;while(!e||e>1);return a+b*c*Math.sqrt(-2*Math.log(e)/e)}}},d3.sum=function(a,b){var c=0,d=a.length,e,f=-1;if(arguments.length===1)while(++f<d)isNaN(e=+a[f])||(c+=e);else while(++f<d)isNaN(e=+b.call(a,a[f],f))||(c+=e);return c},d3.quantile=function(a,b){var c=(a.length-1)*b+1,d=Math.floor(c),e=a[d-1],f=c-d;return f?e+f*(a[d]-e):e},d3.transpose=function(a){return d3.zip.apply(d3,a)},d3.zip=function(){if(!(e=arguments.length))return[];for(var a=-1,b=d3.min(arguments,q),c=new Array(b);++a<b;)for(var d=-1,e,f=c[a]=new Array(e);++d<e;)f[d]=arguments[d][a];return c},d3.bisector=function(a){return{left:function(b,c,d,e){arguments.length<3&&(d=0),arguments.length<4&&(e=b.length);while(d<e){var f=d+e>>1;a.call(b,b[f],f)<c?d=f+1:e=f}return d},right:function(b,c,d,e){arguments.length<3&&(d=0),arguments.length<4&&(e=b.length);while(d<e){var f=d+e>>1;c<a.call(b,b[f],f)?e=f:d=f+1}return d}}};var r=d3.bisector(function(a){return a});d3.bisectLeft=r.left,d3.bisect=d3.bisectRight=r.right,d3.first=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c<d)b.call(a,e,f=a[c])>0&&(e=f);return e},d3.last=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c<d)b.call(a,e,f=a[c])<=0&&(e=f);return e},d3.nest=function(){function f(c,g){if(g>=b.length)return e?e.call(a,c):d?c.sort(d):c;var h=-1,i=c.length,j=b[g++],l,m,n=new k,o,p={};while(++h<i)(o=n.get(l=j(m=c[h])))?o.push(m):n.set(l,[m]);return n.forEach(function(a){p[a]=f(n.get(a),g)}),p}function g(a,d){if(d>=b.length)return a;var e=[],f=c[d++],h;for(h in a)e.push({key:h,values:g(a[h],d)});return f&&e.sort(function(a,b){return f(a.key,b.key)}),e}var a={},b=[],c=[],d,e;return a.map=function(a){return f(a,0)},a.entries=function(a){return g(f(a,0),0)},a.key=function(c){return b.push(c),a},a.sortKeys=function(d){return c[b.length-1]=d,a},a.sortValues=function(b){return d=b,a},a.rollup=function(b){return e=b,a},a},d3.keys=function(a){var b=[];for(var c in a)b.push(c);return b},d3.values=function(a){var b=[];for(var c in a)b.push(a[c]);return b},d3.entries=function(a){var b=[];for(var c in a)b.push({key:c,value:a[c]});return b},d3.permute=function(a,b){var c=[],d=-1,e=b.length;while(++d<e)c[d]=a[b[d]];return c},d3.merge=function(a){return Array.prototype.concat.apply([],a)},d3.split=function(a,b){var c=[],d=[],e,f=-1,g=a.length;arguments.length<2&&(b=s);while(++f<g)b.call(d,e=a[f],f)?d=[]:(d.length||c.push(d),d.push(e));return c},d3.range=function(a,b,c){arguments.length<3&&(c=1,arguments.length<2&&(b=a,a=0));if((b-a)/c===Infinity)throw new Error("infinite range");var d=[],e=u(Math.abs(c)),f=-1,g;a*=e,b*=e,c*=e;if(c<0)while((g=a+c*++f)>b)d.push(g/e);else while((g=a+c*++f)<b)d.push(g/e);return d},d3.requote=function(a){return a.replace(v,"\\$&")};var v=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;d3.round=function(a,b){return b?Math.round(a*(b=Math.pow(10,b)))/b:Math.round(a)},d3.xhr=function(a,b,c){var d=new XMLHttpRequest;arguments.length<3?(c=b,b=null):b&&d.overrideMimeType&&d.overrideMimeType(b),d.open("GET",a,!0),b&&d.setRequestHeader("Accept",b),d.onreadystatechange=function(){d.readyState===4&&c(d.status<300?d:null)},d.send(null)},d3.text=function(a,b,c){function d(a){c(a&&a.responseText)}arguments.length<3&&(c=b,b=null),d3.xhr(a,b,d)},d3.json=function(a,b){d3.text(a,"application/json",function(a){b(a?JSON.parse(a):null)})},d3.html=function(a,b){d3.text(a,"text/html",function(a){if(a!=null){var c=document.createRange();c.selectNode(document.body),a=c.createContextualFragment(a)}b(a)})},d3.xml=function(a,b,c){function d(a){c(a&&a.responseXML)}arguments.length<3&&(c=b,b=null),d3.xhr(a,b,d)};var w={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};d3.ns={prefix:w,qualify:function(a){var b=a.indexOf(":"),c=a;return b>=0&&(c=a.substring(0,b),a=a.substring(b+1)),w.hasOwnProperty(c)?{space:w[c],local:a}:a}},d3.dispatch=function(){var a=new x,b=-1,c=arguments.length;while(++b<c)a[arguments[b]]=y(a);return a},x.prototype.on=function(a,b){var c=a.indexOf("."),d="";return c>0&&(d=a.substring(c+1),a=a.substring(0,c)),arguments.length<2?this[a].on(d):this[a].on(d,b)},d3.format=function(a){var b=z.exec(a),c=b[1]||" ",d=b[3]||"",e=b[5],f=+b[6],g=b[7],h=b[8],i=b[9],j=1,k="",l=!1;h&&(h=+h.substring(1)),e&&(c="0",g&&(f-=Math.floor((f-1)/4)));switch(i){case"n":g=!0,i="g";break;case"%":j=100,k="%",i="f";break;case"p":j=100,k="%",i="r";break;case"d":l=!0,h=0;break;case"s":j=-1,i="r"}return i=="r"&&!h&&(i="g"),i=A.get(i)||C,function(a){if(l&&a%1)return"";var b=a<0&&(a=-a)?"−":d;if(j<0){var m=d3.formatPrefix(a,h);a*=m.scale,k=m.symbol}else a*=j;a=i(a,h);if(e){var n=a.length+b.length;n<f&&(a=(new Array(f-n+1)).join(c)+a),g&&(a=D(a)),a=b+a}else{g&&(a=D(a)),a=b+a;var n=a.length;n<f&&(a=(new Array(f-n+1)).join(c)+a)}return a+k}};var z=/(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,A=d3.map({g:function(a,b){return a.toPrecision(b)},e:function(a,b){return a.toExponential(b)},f:function(a,b){return a.toFixed(b)},r:function(a,b){return d3.round(a,b=B(a,b)).toFixed(Math.max(0,Math.min(20,b)))}}),E=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(F);d3.formatPrefix=function(a,b){var c=0;return a&&(a<0&&(a*=-1),b&&(a=d3.round(a,B(a,b))),c=1+Math.floor(1e-12+Math.log(a)/Math.LN10),c=Math.max(-24,Math.min(24,Math.floor((c<=0?c+1:c-1)/3)*3))),E[8+c/3]};var G=P(2),H=P(3),I=function(){return O},J=d3.map({linear:I,poly:P,quad:function(){return G},cubic:function(){return H},sin:function(){return Q},exp:function(){return R},circle:function(){return S},elastic:T,back:U,bounce:function(){return V}}),K=d3.map({"in":O,out:M,"in-out":N,"out-in":function(a){return N(M(a))}});d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a,d=b>=0?a.substring(b+1):"in";return c=J.get(c)||I,d=K.get(d)||O,L(d(c.apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.interpolate=function(a,b){var c=d3.interpolators.length,d;while(--c>=0&&!(d=d3.interpolators[c](a,b)));return d},d3.interpolateNumber=function(a,b){return b-=a,function(c){return a+b*c}},d3.interpolateRound=function(a,b){return b-=a,function(c){return Math.round(a+b*c)}},d3.interpolateString=function(a,b){var c,d,e,f=0,g=0,h=[],i=[],j,k;Z.lastIndex=0;for(d=0;c=Z.exec(b);++d)c.index&&h.push(b.substring(f,g=c.index)),i.push({i:h.length,x:c[0]}),h.push(null),f=Z.lastIndex;f<b.length&&h.push(b.substring(f));for(d=0,j=i.length;(c=Z.exec(a))&&d<j;++d){k=i[d];if(k.x==c[0]){if(k.i)if(h[k.i+1]==null){h[k.i-1]+=k.x,h.splice(k.i,1);for(e=d+1;e<j;++e)i[e].i--}else{h[k.i-1]+=k.x+h[k.i+1],h.splice(k.i,2);for(e=d+1;e<j;++e)i[e].i-=2}else if(h[k.i+1]==null)h[k.i]=k.x;else{h[k.i]=k.x+h[k.i+1],h.splice(k.i+1,1);for(e=d+1;e<j;++e)i[e].i--}i.splice(d,1),j--,d--}else k.x=d3.interpolateNumber(parseFloat(c[0]),parseFloat(k.x))}while(d<j)k=i.pop(),h[k.i+1]==null?h[k.i]=k.x:(h[k.i]=k.x+h[k.i+1],h.splice(k.i+1,1)),j--;return h.length===1?h[0]==null?i[0].x:function(){return b}:function(a){for(d=0;d<j;++d)h[(k=i[d]).i]=k.x(a);return h.join("")}},d3.interpolateTransform=function(a,b){var c=[],d=[],e,f=d3.transform(a),g=d3.transform(b),h=f.translate,i=g.translate,j=f.rotate,k=g.rotate,l=f.skew,m=g.skew,n=f.scale,o=g.scale;return h[0]!=i[0]||h[1]!=i[1]?(c.push("translate(",null,",",null,")"),d.push({i:1,x:d3.interpolateNumber(h[0],i[0])},{i:3,x:d3.interpolateNumber(h[1],i[1])})):i[0]||i[1]?c.push("translate("+i+")"):c.push(""),j!=k?d.push({i:c.push(c.pop()+"rotate(",null,")")-2,x:d3.interpolateNumber(j,k)}):k&&c.push(c.pop()+"rotate("+k+")"),l!=m?d.push({i:c.push(c.pop()+"skewX(",null,")")-2,x:d3.interpolateNumber(l,m)}):m&&c.push(c.pop()+"skewX("+m+")"),n[0]!=o[0]||n[1]!=o[1]?(e=c.push(c.pop()+"scale(",null,",",null,")"),d.push({i:e-4,x:d3.interpolateNumber(n[0],o[0])},{i:e-2,x:d3.interpolateNumber(n[1],o[1])})):(o[0]!=1||o[1]!=1)&&c.push(c.pop()+"scale("+o+")"),e=d.length,function(a){var b=-1,f;while(++b<e)c[(f=d[b]).i]=f.x(a);return c.join("")}},d3.interpolateRgb=function(a,b){a=d3.rgb(a),b=d3.rgb(b);var c=a.r,d=a.g,e=a.b,f=b.r-c,g=b.g-d,h=b.b-e;return function(a){return"#"+bd(Math.round(c+f*a))+bd(Math.round(d+g*a))+bd(Math.round(e+h*a))}},d3.interpolateHsl=function(a,b){a=d3.hsl(a),b=d3.hsl(b);var c=a.h,d=a.s,e=a.l,f=b.h-c,g=b.s-d,h=b.l-e;return function(a){return bk(c+f*a,d+g*a,e+h*a).toString()}},d3.interpolateArray=function(a,b){var c=[],d=[],e=a.length,f=b.length,g=Math.min(a.length,b.length),h;for(h=0;h<g;++h)c.push(d3.interpolate(a[h],b[h]));for(;h<e;++h)d[h]=a[h];for(;h<f;++h)d[h]=b[h];return function(a){for(h=0;h<g;++h)d[h]=c[h](a);return d}},d3.interpolateObject=function(a,b){var c={},d={},e;for(e in a)e in b?c[e]=$(e)(a[e],b[e]):d[e]=a[e];for(e in b)e in a||(d[e]=b[e]);return function(a){for(e in c)d[e]=c[e](a);return d}};var Z=/[-+]?(?:\d*\.?\d+)(?:[eE][-+]?\d+)?/g;d3.interpolators=[d3.interpolateObject,function(a,b){return b instanceof Array&&d3.interpolateArray(a,b)},function(a,b){return(typeof a=="string"||typeof b=="string")&&d3.interpolateString(a+"",b+"")},function(a,b){return(typeof b=="string"?bh.has(b)||/^(#|rgb\(|hsl\()/.test(b):b instanceof bc||b instanceof bj)&&d3.interpolateRgb(a,b)},function(a,b){return!isNaN(a=+a)&&!isNaN(b=+b)&&d3.interpolateNumber(a,b)}],d3.rgb=function(a,b,c){return arguments.length===1?a instanceof bc?bb(a.r,a.g,a.b):be(""+a,bb,bk):bb(~~a,~~b,~~c)},bc.prototype.brighter=function(a){a=Math.pow(.7,arguments.length?a:1);var b=this.r,c=this.g,d=this.b,e=30;return!b&&!c&&!d?bb(e,e,e):(b&&b<e&&(b=e),c&&c<e&&(c=e),d&&d<e&&(d=e),bb(Math.min(255,Math.floor(b/a)),Math.min(255,Math.floor(c/a)),Math.min(255,Math.floor(d/a))))},bc.prototype.darker=function(a){return a=Math.pow(.7,arguments.length?a:1),bb(Math.floor(a*this.r),Math.floor(a*this.g),Math.floor(a*this.b))},bc.prototype.hsl=function(){return bf(this.r,this.g,this.b)},bc.prototype.toString=function(){return"#"+bd(this.r)+bd(this.g)+bd(this.b)};var bh=d3.map({aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"});bh.forEach(function(a,b){bh.set(a,be(b,bb,bk))}),d3.hsl=function(a,b,c){return arguments.length===1?a instanceof bj?bi(a.h,a.s,a.l):be(""+a,bf,bi):bi(+a,+b,+c)},bj.prototype.brighter=function(a){return a=Math.pow(.7,arguments.length?a:1),bi(this.h,this.s,this.l/a)},bj.prototype.darker=function(a){return a=Math.pow(.7,arguments.length?a:1),bi(this.h,this.s,a*this.l)},bj.prototype.rgb=function(){return bk(this.h,this.s,this.l)},bj.prototype.toString=function(){return this.rgb().toString()};var bm=function(a,b){return b.querySelector(a)},bn=function(a,b){return b.querySelectorAll(a)},bo=document.documentElement,bp=bo.matchesSelector||bo.webkitMatchesSelector||bo.mozMatchesSelector||bo.msMatchesSelector||bo.oMatchesSelector,bq=function(a,b){return bp.call(a,b)};typeof Sizzle=="function"&&(bm=function(a,b){return Sizzle(a,b)[0]},bn=function(a,b){return Sizzle.uniqueSort(Sizzle(a,b))},bq=Sizzle.matchesSelector);var br=[];d3.selection=function(){return bz},d3.selection.prototype=br,br.select=function(a){var b=[],c,d,e,f;typeof a!="function"&&(a=bs(a));for(var g=-1,h=this.length;++g<h;){b.push(c=[]),c.parentNode=(e=this[g]).parentNode;for(var i=-1,j=e.length;++i<j;)(f=e[i])?(c.push(d=a.call(f,f.__data__,i)),d&&"__data__"in f&&(d.__data__=f.__data__)):c.push(null)}return bl(b)},br.selectAll=function(a){var b=[],c,d;typeof a!="function"&&(a=bt(a));for(var e=-1,g=this.length;++e<g;)for(var h=this[e],i=-1,j=h.length;++i<j;)if(d=h[i])b.push(c=f(a.call(d,d.__data__,i))),c.parentNode=d;return bl(b)},br.attr=function(a,b){function d(){this.removeAttribute(a)}function e(){this.removeAttributeNS(a.space,a.local)}function f(){this.setAttribute(a,b)}function g(){this.setAttributeNS(a.space,a.local,b)}function h(){var c=b.apply(this,arguments);c==null?this.removeAttribute(a):this.setAttribute(a,c)}function i(){var c=b.apply(this,arguments);c==null?this.removeAttributeNS(a.space,a.local):this.setAttributeNS(a.space,a.local,c)}a=d3.ns.qualify(a);if(arguments.length<2){var c=this.node();return a.local?c.getAttributeNS(a.space,a.local):c.getAttribute(a)}return this.each(b==null?a.local?e:d:typeof b=="function"?a.local?i:h:a.local?g:f)},br.classed=function(a,b){var c=a.split(bu),d=c.length,e=-1;if(arguments.length>1){while(++e<d)bv.call(this,c[e],b);return this}while(++e<d)if(!bv.call(this,c[e]))return!1;return!0};var bu=/\s+/g;br.style=function(a,b,c){function d(){this.style.removeProperty(a)}function e(){this.style.setProperty(a,b,c)}function f(){var d=b.apply(this,arguments);d==null?this.style.removeProperty(a):this.style.setProperty(a,d,c)}return arguments.length<3&&(c=""),arguments.length<2?window.getComputedStyle(this.node(),null).getPropertyValue(a):this.each(b==null?d:typeof b=="function"?f:e)},br.property=function(a,b){function c(){delete this[a]}function d(){this[a]=b}function e(){var c=b.apply(this,arguments);c==null?delete this[a]:this[a]=c}return arguments.length<2?this.node()[a]:this.each(b==null?c:typeof b=="function"?e:d)},br.text=function(a){return arguments.length<1?this.node().textContent:this.each(typeof a=="function"?function(){var b=a.apply(this,arguments);this.textContent=b==null?"":b}:a==null?function(){this.textContent=""}:function(){this.textContent=a})},br.html=function(a){return arguments.length<1?this.node().innerHTML:this.each(typeof a=="function"?function(){var b=a.apply(this,arguments);this.innerHTML=b==null?"":b}:a==null?function(){this.innerHTML=""}:function(){this.innerHTML=a})},br.append=function(a){function b(){return this.appendChild(document.createElementNS(this.namespaceURI,a))}function c(){return this.appendChild(document.createElementNS(a.space,a.local))}return a=d3.ns.qualify(a),this.select(a.local?c:b)},br.insert=function(a,b){function c(){return this.insertBefore(document.createElementNS(this.namespaceURI,a),bm(b,this))}function d(){return this.insertBefore(document.createElementNS(a.space,a.local),bm(b,this))}return a=d3.ns.qualify(a),this.select(a.local?d:c)},br.remove=function(){return this.each(function(){var a=this.parentNode;a&&a.removeChild(this)})},br.data=function(a,b){function g(a,c){var d,e=a.length,f=c.length,g=Math.min(e,f),l=Math.max(e,f),m=[],n=[],o=[],p,q;if(b){var r=new k,s=[],t,u=c.length;for(d=-1;++d<e;)t=b.call(p=a[d],p.__data__,d),r.has(t)?o[u++]=p:r.set(t,p),s.push(t);for(d=-1;++d<f;)t=b.call(c,q=c[d],d),r.has(t)?(m[d]=p=r.get(t),p.__data__=q,n[d]=o[d]=null):(n[d]=bw(q),m[d]=o[d]=null),r.remove(t);for(d=-1;++d<e;)r.has(s[d])&&(o[d]=a[d])}else{for(d=-1;++d<g;)p=a[d],q=c[d],p?(p.__data__=q,m[d]=p,n[d]=o[d]=null):(n[d]=bw(q),m[d]=o[d]=null);for(;d<f;++d)n[d]=bw(c[d]),m[d]=o[d]=null;for(;d<l;++d)o[d]=a[d],n[d]=m[d]=null}n.update=m,n.parentNode=m.parentNode=o.parentNode=a.parentNode,h.push(n),i.push(m),j.push(o)}var c=-1,d=this.length,e,f;if(!arguments.length){a=new Array(d=(e=this[0]).length);while(++c<d)if(f=e[c])a[c]=f.__data__;return a}var h=bA([]),i=bl([]),j=bl([]);if(typeof a=="function")while(++c<d)g(e=this[c],a.call(e,e.parentNode.__data__,c));else while(++c<d)g(e=this[c],a);return i.enter=function(){return h},i.exit=function(){return j},i},br.datum=br.map=function(a){return arguments.length<1?this.property("__data__"):this.property("__data__",a)},br.filter=function(a){var b=[],c,d,e;typeof a!="function"&&(a=bx(a));for(var f=0,g=this.length;f<g;f++){b.push(c=[]),c.parentNode=(d=this[f]).parentNode;for(var h=0,i=d.length;h<i;h++)(e=d[h])&&a.call(e,e.__data__,h)&&c.push(e)}return bl(b)},br.order=function(){for(var a=-1,b=this.length;++a<b;)for(var c=this[a],d=c.length-1,e=c[d],f;--d>=0;)if(f=c[d])e&&e!==f.nextSibling&&e.parentNode.insertBefore(f,e),e=f;return this},br.sort=function(a){a=by.apply(this,arguments);for(var b=-1,c=this.length;++b<c;)this[b].sort(a);return this.order()},br.on=function(a,b,c){arguments.length<3&&(c=!1);var d="__on"+a,e=a.indexOf(".");return e>0&&(a=a.substring(0,e)),arguments.length<2?(e=this.node()[d])&&e._:this.each(function(e,f){function i(a){var c=d3.event;d3.event=a;try{b.call(g,g.__data__,f)}finally{d3.event=c}}var g=this,h=g[d];h&&(g.removeEventListener(a,h,h.$),delete g[d]),b&&(g.addEventListener(a,g[d]=i,i.$=c),i._=b)})},br.each=function(a){for(var b=-1,c=this.length;++b<c;)for(var d=this[b],e=-1,f=d.length;++e<f;){var g=d[e];g&&a.call(g,g.__data__,e,b)}return this},br.call=function(a){return a.apply(this,(arguments[0]=this,arguments)),this},br.empty=function(){return!this.node()},br.node=function(a){for(var b=0,c=this.length;b<c;b++)for(var d=this[b],e=0,f=d.length;e<f;e++){var g=d[e];if(g)return g}return null},br.transition=function(){var a=[],b,c;for(var d=-1,e=this.length;++d<e;){a.push(b=[]);for(var f=this[d],g=-1,h=f.length;++g<h;)b.push((c=f[g])?{node:c,delay:bM,duration:bN}:null)}return bC(a,bI||++bH,Date.now())};var bz=bl([[document]]);bz[0].parentNode=bo,d3.select=function(a){return typeof a=="string"?bz.select(a):bl([[a]])},d3.selectAll=function(a){return typeof a=="string"?bz.selectAll(a):bl([f(a)])};var bB=[];d3.selection.enter=bA,d3.selection.enter.prototype=bB,bB.append=br.append,bB.insert=br.insert,bB.empty=br.empty,bB.node=br.node,bB.select=function(a){var b=[],c,d,e,f,g;for(var h=-1,i=this.length;++h<i;){e=(f=this[h]).update,b.push(c=[]),c.parentNode=f.parentNode;for(var j=-1,k=f.length;++j<k;)(g=f[j])?(c.push(e[j]=d=a.call(f.parentNode,g.__data__,j)),d.__data__=g.__data__):c.push(null)}return bl(b)};var bD={},bG=[],bH=0,bI=0,bJ=0,bK=250,bL=d3.ease("cubic-in-out"),bM=bJ,bN=bK,bO=bL;bG.call=br.call,d3.transition=function(a){return arguments.length?bI?a.transition():a:bz.transition()},d3.transition.prototype=bG,bG.select=function(a){var b=[],c,d,e;typeof a!="function"&&(a=bs(a));for(var f=-1,g=this.length;++f<g;){b.push(c=[]);for(var h=this[f],i=-1,j=h.length;++i<j;)(e=h[i])&&(d=a.call(e.node,e.node.__data__,i))?("__data__"in e.node&&(d.__data__=e.node.__data__),c.push({node:d,delay:e.delay,duration:e.duration})):c.push(null)}return bC(b,this.id,this.time).ease(this.ease())},bG.selectAll=function(a){var b=[],c,d,e;typeof a!="function"&&(a=bt(a));for(var f=-1,g=this.length;++f<g;)for(var h=this[f],i=-1,j=h.length;++i<j;)if(e=h[i]){d=a.call(e.node,e.node.__data__,i),b.push(c=[]);for(var k=-1,l=d.length;++k<l;)c.push({node:d[k],delay:e.delay,duration:e.duration})}return bC(b,this.id,this.time).ease(this.ease())},bG.attr=function(a,b){return this.attrTween(a,bF(a,b))},bG.attrTween=function(a,b){function d(a,d){var e=b.call(this,a,d,this.getAttribute(c));return e===bD?(this.removeAttribute(c),null):e&&function(a){this.setAttribute(c,e(a))}}function e(a,d){var e=b.call(this,a,d,this.getAttributeNS(c.space,c.local));return e===bD?(this.removeAttributeNS(c.space,c.local),null):e&&function(a){this.setAttributeNS(c.space,c.local,e(a))}}var c=d3.ns.qualify(a);return this.tween("attr."+a,c.local?e:d)},bG.style=function(a,b,c){return arguments.length<3&&(c=""),this.styleTween(a,bF(a,b),c)},bG.styleTween=function(a,b,c){return arguments.length<3&&(c=""),this.tween("style."+a,function(d,e){var f=b.call(this,d,e,window.getComputedStyle(this,null).getPropertyValue(a));return f===bD?(this.style.removeProperty(a),null):f&&function(b){this.style.setProperty(a,f(b),c)}})},bG.text=function(a){return this.tween("text",function(b,c){this.textContent=typeof a=="function"?a.call(this,b,c):a})},bG.remove=function(){return this.each("end.transition",function(){var a;!this.__transition__&&(a=this.parentNode)&&a.removeChild(this)})},bG.delay=function(a){var b=this;return b.each(typeof a=="function"?function(c,d,e){b[e][d].delay=a.apply(this,arguments)|0}:(a|=0,function(c,d,e){b[e][d].delay=a}))},bG.duration=function(a){var b=this;return b.each(typeof a=="function"?function(c,d,e){b[e][d].duration=Math.max(1,a.apply(this,arguments)|0)}:(a=Math.max(1,a|0),function(c,d,e){b[e][d].duration=a}))},bG.transition=function(){return this.select(n)};var bQ=null,bR,bS;d3.timer=function(a,b,c){var d=!1,e,f=bQ;if(arguments.length<3){if(arguments.length<2)b=0;else if(!isFinite(b))return;c=Date.now()}while(f){if(f.callback===a){f.then=c,f.delay=b,d=!0;break}e=f,f=f.next}d||(bQ={callback:a,then:c,delay:b,next:bQ}),bR||(bS=clearTimeout(bS),bR=1,bV(bT))},d3.timer.flush=function(){var a,b=Date.now(),c=bQ;while(c)a=b-c.then,c.delay||(c.flush=c.callback(a)),c=c.next;bU()};var bV=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,17)};d3.transform=function(a){var b=document.createElementNS(d3.ns.prefix.svg,"g"),c={a:1,b:0,c:0,d:1,e:0,f:0};return(d3.transform=function(a){b.setAttribute("transform",a);var d=b.transform.baseVal.consolidate();return new bW(d?d.matrix:c)})(a)},bW.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var b$=180/Math.PI;d3.mouse=function(a){return ca(a,X())};var b_=/WebKit/.test(navigator.userAgent)?-1:0;d3.touches=function(a,b){return arguments.length<2&&(b=X().touches),b?f(b).map(function(b){var c=ca(a,b);return c.identifier=b.identifier,c}):[]},d3.scale={},d3.scale.linear=function(){return cg([0,1],[0,1],d3.interpolate,!1)},d3.scale.log=function(){return co(d3.scale.linear(),cq)};var cp=d3.format(".0e");cq.pow=function(a){return Math.pow(10,a)},cr.pow=function(a){return-Math.pow(10,-a)},d3.scale.pow=function(){return cs(d3.scale.linear(),1)},d3.scale.sqrt=function(){return d3.scale.pow().exponent(.5)},d3.scale.ordinal=function(){return cu([],{t:"range",x:[]})},d3.scale.category10=function(){return d3.scale.ordinal().range(cv)},d3.scale.category20=function(){return d3.scale.ordinal().range(cw)},d3.scale.category20b=function(){return d3.scale.ordinal().range(cx)},d3.scale.category20c=function(){return d3.scale.ordinal().range(cy)};var cv=["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#7f7f7f","#bcbd22","#17becf"],cw=["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"],cx=["#393b79","#5254a3","#6b6ecf","#9c9ede","#637939","#8ca252","#b5cf6b","#cedb9c","#8c6d31","#bd9e39","#e7ba52","#e7cb94","#843c39","#ad494a","#d6616b","#e7969c","#7b4173","#a55194","#ce6dbd","#de9ed6"],cy=["#3182bd","#6baed6","#9ecae1","#c6dbef","#e6550d","#fd8d3c","#fdae6b","#fdd0a2","#31a354","#74c476","#a1d99b","#c7e9c0","#756bb1","#9e9ac8","#bcbddc","#dadaeb","#636363","#969696","#bdbdbd","#d9d9d9"];d3.scale.quantile=function(){return cz([],[])},d3.scale.quantize=function(){return cA(0,1,[0,1])},d3.scale.identity=function(){return cB([0,1])},d3.svg={},d3.svg.arc=function(){function e(){var e=a.apply(this,arguments),f=b.apply(this,arguments),g=c.apply(this,arguments)+cC,h=d.apply(this,arguments)+cC,i=(h<g&&(i=g,g=h,h=i),h-g),j=i<Math.PI?"0":"1",k=Math.cos(g),l=Math.sin(g),m=Math.cos(h),n=Math.sin(h);return i>=cD?e?"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"Z":e?"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L"+e*m+","+e*n+"A"+e+","+e+" 0 "+j+",0 "+e*k+","+e*l+"Z":"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L0,0"+"Z"}var a=cE,b=cF,c=cG,d=cH;return e.innerRadius=function(b){return arguments.length?(a=d3.functor(b),e):a},e.outerRadius=function(a){return arguments.length?(b=d3.functor(a),e):b},e.startAngle=function(a){return arguments.length?(c=d3.functor(a),e):c},e.endAngle=function(a){return arguments.length?(d=d3.functor(a),e):d},e.centroid=function(){var e=(a.apply(this,arguments)+b.apply(this,arguments))/2,f=(c.apply(this,arguments)+d.apply(this,arguments))/2+cC;return[Math.cos(f)*e,Math.sin(f)*e]},e};var cC=-Math.PI/2,cD=2*Math.PI-1e-6;d3.svg.line=function(){return cI(Object)};var cM="linear",cN=d3.map({linear:cO,"step-before":cP,"step-after":cQ,basis:cW,"basis-open":cX,"basis-closed":cY,bundle:cZ,cardinal:cT,"cardinal-open":cR,"cardinal-closed":cS,monotone:dg}),c_=[0,2/3,1/3,0],da=[0,1/3,2/3,0],db=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var a=cI(dh);return a.radius=a.x,delete a.x,a.angle=a.y,delete a.y,a},cP.reverse=cQ,cQ.reverse=cP,d3.svg.area=function(){return di(Object)},d3.svg.area.radial=function(){var a=di(dh);return a.radius=a.x,delete a.x,a.innerRadius=a.x0,delete a.x0,a.outerRadius=a.x1,delete a.x1,a.angle=a.y,delete a.y,a.startAngle=a.y0,delete a.y0,a.endAngle=a.y1,delete a.y1,a},d3.svg.chord=function(){function f(c,d){var e=g(this,a,c,d),f=g(this,b,c,d);return"M"+e.p0+i(e.r,e.p1,e.a1-e.a0)+(h(e,f)?j(e.r,e.p1,e.r,e.p0):j(e.r,e.p1,f.r,f.p0)+i(f.r,f.p1,f.a1-f.a0)+j(f.r,f.p1,e.r,e.p0))+"Z"}function g(a,b,f,g){var h=b.call(a,f,g),i=c.call(a,h,g),j=d.call(a,h,g)+cC,k=e.call(a,h,g)+cC;return{r:i,a0:j,a1:k,p0:[i*Math.cos(j),i*Math.sin(j)],p1:[i*Math.cos(k),i*Math.sin(k)]}}function h(a,b){return a.a0==b.a0&&a.a1==b.a1}function i(a,b,c){return"A"+a+","+a+" 0 "+ +(c>Math.PI)+",1 "+b}function j(a,b,c,d){return"Q 0,0 "+d}var a=dl,b=dm,c=dn,d=cG,e=cH;return f.radius=function(a){return arguments.length?(c=d3.functor(a),f):c},f.source=function(b){return arguments.length?(a=d3.functor(b),f):a},f.target=function(a){return arguments.length?(b=d3.functor(a),f):b},f.startAngle=function(a){return arguments.length?(d=d3.functor(a),f):d},f.endAngle=function(a){return arguments.length?(e=d3.functor(a),f):e},f},d3.svg.diagonal=function(){function d(d,e){var f=a.call(this,d,e),g=b.call(this,d,e),h=(f.y+g.y)/2,i=[f,{x:f.x,y:h},{x:g.x,y:h},g];return i=i.map(c),"M"+i[0]+"C"+i[1]+" "+i[2]+" "+i[3]}var a=dl,b=dm,c=dr;return d.source=function(b){return arguments.length?(a=d3.functor(b),d):a},d.target=function(a){return arguments.length?(b=d3.functor(a),d):b},d.projection=function(a){return arguments.length?(c=a,d):c},d},d3.svg.diagonal.radial=function(){var a=d3.svg.diagonal(),b=dr,c=a.projection;return a.projection=function(a){return arguments.length?c(ds(b=a)):b},a},d3.svg.mouse=d3.mouse,d3.svg.touches=d3.touches,d3.svg.symbol=function(){function c(c,d){return(dw.get(a.call(this,c,d))||dv)(b.call(this,c,d))}var a=du,b=dt;return c.type=function(b){return arguments.length?(a=d3.functor(b),c):a},c.size=function(a){return arguments.length?(b=d3.functor(a),c):b},c};var dw=d3.map({circle
+:dv,cross:function(a){var b=Math.sqrt(a/5)/2;return"M"+ -3*b+","+ -b+"H"+ -b+"V"+ -3*b+"H"+b+"V"+ -b+"H"+3*b+"V"+b+"H"+b+"V"+3*b+"H"+ -b+"V"+b+"H"+ -3*b+"Z"},diamond:function(a){var b=Math.sqrt(a/(2*dy)),c=b*dy;return"M0,"+ -b+"L"+c+",0"+" 0,"+b+" "+ -c+",0"+"Z"},square:function(a){var b=Math.sqrt(a)/2;return"M"+ -b+","+ -b+"L"+b+","+ -b+" "+b+","+b+" "+ -b+","+b+"Z"},"triangle-down":function(a){var b=Math.sqrt(a/dx),c=b*dx/2;return"M0,"+c+"L"+b+","+ -c+" "+ -b+","+ -c+"Z"},"triangle-up":function(a){var b=Math.sqrt(a/dx),c=b*dx/2;return"M0,"+ -c+"L"+b+","+c+" "+ -b+","+c+"Z"}});d3.svg.symbolTypes=dw.keys();var dx=Math.sqrt(3),dy=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function k(k){k.each(function(){var k=d3.select(this),l=h==null?a.ticks?a.ticks.apply(a,g):a.domain():h,m=i==null?a.tickFormat?a.tickFormat.apply(a,g):String:i,n=dB(a,l,j),o=k.selectAll(".minor").data(n,String),p=o.enter().insert("line","g").attr("class","tick minor").style("opacity",1e-6),q=d3.transition(o.exit()).style("opacity",1e-6).remove(),r=d3.transition(o).style("opacity",1),s=k.selectAll("g").data(l,String),t=s.enter().insert("g","path").style("opacity",1e-6),u=d3.transition(s.exit()).style("opacity",1e-6).remove(),v=d3.transition(s).style("opacity",1),w,x=cd(a),y=k.selectAll(".domain").data([0]),z=y.enter().append("path").attr("class","domain"),A=d3.transition(y),B=a.copy(),C=this.__chart__||B;this.__chart__=B,t.append("line").attr("class","tick"),t.append("text"),v.select("text").text(m);switch(b){case"bottom":w=dz,p.attr("y2",d),r.attr("x2",0).attr("y2",d),t.select("line").attr("y2",c),t.select("text").attr("y",Math.max(c,0)+f),v.select("line").attr("x2",0).attr("y2",c),v.select("text").attr("x",0).attr("y",Math.max(c,0)+f).attr("dy",".71em").attr("text-anchor","middle"),A.attr("d","M"+x[0]+","+e+"V0H"+x[1]+"V"+e);break;case"top":w=dz,p.attr("y2",-d),r.attr("x2",0).attr("y2",-d),t.select("line").attr("y2",-c),t.select("text").attr("y",-(Math.max(c,0)+f)),v.select("line").attr("x2",0).attr("y2",-c),v.select("text").attr("x",0).attr("y",-(Math.max(c,0)+f)).attr("dy","0em").attr("text-anchor","middle"),A.attr("d","M"+x[0]+","+ -e+"V0H"+x[1]+"V"+ -e);break;case"left":w=dA,p.attr("x2",-d),r.attr("x2",-d).attr("y2",0),t.select("line").attr("x2",-c),t.select("text").attr("x",-(Math.max(c,0)+f)),v.select("line").attr("x2",-c).attr("y2",0),v.select("text").attr("x",-(Math.max(c,0)+f)).attr("y",0).attr("dy",".32em").attr("text-anchor","end"),A.attr("d","M"+ -e+","+x[0]+"H0V"+x[1]+"H"+ -e);break;case"right":w=dA,p.attr("x2",d),r.attr("x2",d).attr("y2",0),t.select("line").attr("x2",c),t.select("text").attr("x",Math.max(c,0)+f),v.select("line").attr("x2",c).attr("y2",0),v.select("text").attr("x",Math.max(c,0)+f).attr("y",0).attr("dy",".32em").attr("text-anchor","start"),A.attr("d","M"+e+","+x[0]+"H0V"+x[1]+"H"+e)}if(a.ticks)t.call(w,C),v.call(w,B),u.call(w,B),p.call(w,C),r.call(w,B),q.call(w,B);else{var D=B.rangeBand()/2,E=function(a){return B(a)+D};t.call(w,E),v.call(w,E)}})}var a=d3.scale.linear(),b="bottom",c=6,d=6,e=6,f=3,g=[10],h=null,i,j=0;return k.scale=function(b){return arguments.length?(a=b,k):a},k.orient=function(a){return arguments.length?(b=a,k):b},k.ticks=function(){return arguments.length?(g=arguments,k):g},k.tickValues=function(a){return arguments.length?(h=a,k):h},k.tickFormat=function(a){return arguments.length?(i=a,k):i},k.tickSize=function(a,b,f){if(!arguments.length)return c;var g=arguments.length-1;return c=+a,d=g>1?+b:c,e=g>0?+arguments[g]:c,k},k.tickPadding=function(a){return arguments.length?(f=+a,k):f},k.tickSubdivide=function(a){return arguments.length?(j=+a,k):j},k},d3.svg.brush=function(){function g(a){a.each(function(){var a=d3.select(this),e=a.selectAll(".background").data([0]),f=a.selectAll(".extent").data([0]),l=a.selectAll(".resize").data(d,String),m;a.style("pointer-events","all").on("mousedown.brush",k).on("touchstart.brush",k),e.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),f.enter().append("rect").attr("class","extent").style("cursor","move"),l.enter().append("g").attr("class",function(a){return"resize "+a}).style("cursor",function(a){return dC[a]}).append("rect").attr("x",function(a){return/[ew]$/.test(a)?-3:null}).attr("y",function(a){return/^[ns]/.test(a)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),l.style("display",g.empty()?"none":null),l.exit().remove(),b&&(m=cd(b),e.attr("x",m[0]).attr("width",m[1]-m[0]),i(a)),c&&(m=cd(c),e.attr("y",m[0]).attr("height",m[1]-m[0]),j(a)),h(a)})}function h(a){a.selectAll(".resize").attr("transform",function(a){return"translate("+e[+/e$/.test(a)][0]+","+e[+/^s/.test(a)][1]+")"})}function i(a){a.select(".extent").attr("x",e[0][0]),a.selectAll(".extent,.n>rect,.s>rect").attr("width",e[1][0]-e[0][0])}function j(a){a.select(".extent").attr("y",e[0][1]),a.selectAll(".extent,.e>rect,.w>rect").attr("height",e[1][1]-e[0][1])}function k(){function x(){var a=d3.event.changedTouches;return a?d3.touches(d,a)[0]:d3.mouse(d)}function y(){d3.event.keyCode==32&&(q||(r=null,s[0]-=e[1][0],s[1]-=e[1][1],q=2),W())}function z(){d3.event.keyCode==32&&q==2&&(s[0]+=e[1][0],s[1]+=e[1][1],q=0,W())}function A(){var a=x(),d=!1;t&&(a[0]+=t[0],a[1]+=t[1]),q||(d3.event.altKey?(r||(r=[(e[0][0]+e[1][0])/2,(e[0][1]+e[1][1])/2]),s[0]=e[+(a[0]<r[0])][0],s[1]=e[+(a[1]<r[1])][1]):r=null),o&&B(a,b,0)&&(i(m),d=!0),p&&B(a,c,1)&&(j(m),d=!0),d&&(h(m),l({type:"brush",mode:q?"move":"resize"}))}function B(a,b,c){var d=cd(b),g=d[0],h=d[1],i=s[c],j=e[1][c]-e[0][c],k,l;q&&(g-=i,h-=j+i),k=Math.max(g,Math.min(h,a[c])),q?l=(k+=i)+j:(r&&(i=Math.max(g,Math.min(h,2*r[c]-k))),i<k?(l=k,k=i):l=i);if(e[0][c]!==k||e[1][c]!==l)return f=null,e[0][c]=k,e[1][c]=l,!0}function C(){A(),m.style("pointer-events","all").selectAll(".resize").style("display",g.empty()?"none":null),d3.select("body").style("cursor",null),u.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),l({type:"brushend"}),W()}var d=this,k=d3.select(d3.event.target),l=a.of(d,arguments),m=d3.select(d),n=k.datum(),o=!/^(n|s)$/.test(n)&&b,p=!/^(e|w)$/.test(n)&&c,q=k.classed("extent"),r,s=x(),t,u=d3.select(window).on("mousemove.brush",A).on("mouseup.brush",C).on("touchmove.brush",A).on("touchend.brush",C).on("keydown.brush",y).on("keyup.brush",z);if(q)s[0]=e[0][0]-s[0],s[1]=e[0][1]-s[1];else if(n){var v=+/w$/.test(n),w=+/^n/.test(n);t=[e[1-v][0]-s[0],e[1-w][1]-s[1]],s[0]=e[v][0],s[1]=e[w][1]}else d3.event.altKey&&(r=s.slice());m.style("pointer-events","none").selectAll(".resize").style("display",null),d3.select("body").style("cursor",k.style("cursor")),l({type:"brushstart"}),A(),W()}var a=Y(g,"brushstart","brush","brushend"),b=null,c=null,d=dD[0],e=[[0,0],[0,0]],f;return g.x=function(a){return arguments.length?(b=a,d=dD[!b<<1|!c],g):b},g.y=function(a){return arguments.length?(c=a,d=dD[!b<<1|!c],g):c},g.extent=function(a){var d,h,i,j,k;return arguments.length?(f=[[0,0],[0,0]],b&&(d=a[0],h=a[1],c&&(d=d[0],h=h[0]),f[0][0]=d,f[1][0]=h,b.invert&&(d=b(d),h=b(h)),h<d&&(k=d,d=h,h=k),e[0][0]=d|0,e[1][0]=h|0),c&&(i=a[0],j=a[1],b&&(i=i[1],j=j[1]),f[0][1]=i,f[1][1]=j,c.invert&&(i=c(i),j=c(j)),j<i&&(k=i,i=j,j=k),e[0][1]=i|0,e[1][1]=j|0),g):(a=f||e,b&&(d=a[0][0],h=a[1][0],f||(d=e[0][0],h=e[1][0],b.invert&&(d=b.invert(d),h=b.invert(h)),h<d&&(k=d,d=h,h=k))),c&&(i=a[0][1],j=a[1][1],f||(i=e[0][1],j=e[1][1],c.invert&&(i=c.invert(i),j=c.invert(j)),j<i&&(k=i,i=j,j=k))),b&&c?[[d,i],[h,j]]:b?[d,h]:c&&[i,j])},g.clear=function(){return f=null,e[0][0]=e[0][1]=e[1][0]=e[1][1]=0,g},g.empty=function(){return b&&e[0][0]===e[1][0]||c&&e[0][1]===e[1][1]},d3.rebind(g,a,"on")};var dC={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},dD=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]];d3.behavior={},d3.behavior.drag=function(){function c(){this.on("mousedown.drag",d).on("touchstart.drag",d)}function d(){function j(){var a=c.parentNode,b=d3.event.changedTouches;return b?d3.touches(a,b)[0]:d3.mouse(a)}function k(){if(!c.parentNode)return l();var a=j(),b=a[0]-g[0],e=a[1]-g[1];h|=b|e,g=a,W(),d({type:"drag",x:a[0]+f[0],y:a[1]+f[1],dx:b,dy:e})}function l(){d({type:"dragend"}),h&&(W(),d3.event.target===e&&i.on("click.drag",m,!0)),i.on("mousemove.drag",null).on("touchmove.drag",null).on("mouseup.drag",null).on("touchend.drag",null)}function m(){W(),i.on("click.drag",null)}var c=this,d=a.of(c,arguments),e=d3.event.target,f,g=j(),h=0,i=d3.select(window).on("mousemove.drag",k).on("touchmove.drag",k).on("mouseup.drag",l,!0).on("touchend.drag",l,!0);b?(f=b.apply(c,arguments),f=[f.x-g[0],f.y-g[1]]):f=[0,0],d({type:"dragstart"})}var a=Y(c,"drag","dragstart","dragend"),b=null;return c.origin=function(a){return arguments.length?(b=a,c):b},d3.rebind(c,a,"on")},d3.behavior.zoom=function(){function l(){this.on("mousedown.zoom",r).on("mousewheel.zoom",s).on("mousemove.zoom",t).on("DOMMouseScroll.zoom",s).on("dblclick.zoom",u).on("touchstart.zoom",v).on("touchmove.zoom",w).on("touchend.zoom",v)}function m(b){return[(b[0]-a[0])/c,(b[1]-a[1])/c]}function n(b){return[b[0]*c+a[0],b[1]*c+a[1]]}function o(a){c=Math.max(e[0],Math.min(e[1],a))}function p(b,c){c=n(c),a[0]+=b[0]-c[0],a[1]+=b[1]-c[1]}function q(b){h&&h.domain(g.range().map(function(b){return(b-a[0])/c}).map(g.invert)),j&&j.domain(i.range().map(function(b){return(b-a[1])/c}).map(i.invert)),d3.event.preventDefault(),b({type:"zoom",scale:c,translate:a})}function r(){function h(){d=1,p(d3.mouse(a),g),q(b)}function i(){d&&W(),e.on("mousemove.zoom",null).on("mouseup.zoom",null),d&&d3.event.target===c&&e.on("click.zoom",j)}function j(){W(),e.on("click.zoom",null)}var a=this,b=f.of(a,arguments),c=d3.event.target,d=0,e=d3.select(window).on("mousemove.zoom",h).on("mouseup.zoom",i),g=m(d3.mouse(a));window.focus(),W()}function s(){b||(b=m(d3.mouse(this))),o(Math.pow(2,dG()*.002)*c),p(d3.mouse(this),b),q(f.of(this,arguments))}function t(){b=null}function u(){var a=d3.mouse(this),b=m(a);o(d3.event.shiftKey?c/2:c*2),p(a,b),q(f.of(this,arguments))}function v(){var a=d3.touches(this),e=Date.now();d=c,b={},a.forEach(function(a){b[a.identifier]=m(a)}),W();if(a.length===1&&e-k<500){var g=a[0],h=m(a[0]);o(c*2),p(g,h),q(f.of(this,arguments))}k=e}function w(){var a=d3.touches(this),c=a[0],e=b[c.identifier];if(g=a[1]){var g,h=b[g.identifier];c=[(c[0]+g[0])/2,(c[1]+g[1])/2],e=[(e[0]+h[0])/2,(e[1]+h[1])/2],o(d3.event.scale*d)}p(c,e),q(f.of(this,arguments))}var a=[0,0],b,c=1,d,e=dF,f=Y(l,"zoom"),g,h,i,j,k;return l.translate=function(b){return arguments.length?(a=b.map(Number),l):a},l.scale=function(a){return arguments.length?(c=+a,l):c},l.scaleExtent=function(a){return arguments.length?(e=a==null?dF:a.map(Number),l):e},l.x=function(a){return arguments.length?(h=a,g=a.copy(),l):h},l.y=function(a){return arguments.length?(j=a,i=a.copy(),l):j},d3.rebind(l,f,"on")};var dE,dF=[0,Infinity];d3.layout={},d3.layout.bundle=function(){return function(a){var b=[],c=-1,d=a.length;while(++c<d)b.push(dH(a[c]));return b}},d3.layout.chord=function(){function j(){var a={},j=[],l=d3.range(e),m=[],n,o,p,q,r;b=[],c=[],n=0,q=-1;while(++q<e){o=0,r=-1;while(++r<e)o+=d[q][r];j.push(o),m.push(d3.range(e)),n+=o}g&&l.sort(function(a,b){return g(j[a],j[b])}),h&&m.forEach(function(a,b){a.sort(function(a,c){return h(d[b][a],d[b][c])})}),n=(2*Math.PI-f*e)/n,o=0,q=-1;while(++q<e){p=o,r=-1;while(++r<e){var s=l[q],t=m[s][r],u=d[s][t],v=o,w=o+=u*n;a[s+"-"+t]={index:s,subindex:t,startAngle:v,endAngle:w,value:u}}c.push({index:s,startAngle:p,endAngle:o,value:(o-p)/n}),o+=f}q=-1;while(++q<e){r=q-1;while(++r<e){var x=a[q+"-"+r],y=a[r+"-"+q];(x.value||y.value)&&b.push(x.value<y.value?{source:y,target:x}:{source:x,target:y})}}i&&k()}function k(){b.sort(function(a,b){return i((a.source.value+a.target.value)/2,(b.source.value+b.target.value)/2)})}var a={},b,c,d,e,f=0,g,h,i;return a.matrix=function(f){return arguments.length?(e=(d=f)&&d.length,b=c=null,a):d},a.padding=function(d){return arguments.length?(f=d,b=c=null,a):f},a.sortGroups=function(d){return arguments.length?(g=d,b=c=null,a):g},a.sortSubgroups=function(c){return arguments.length?(h=c,b=null,a):h},a.sortChords=function(c){return arguments.length?(i=c,b&&k(),a):i},a.chords=function(){return b||j(),b},a.groups=function(){return c||j(),c},a},d3.layout.force=function(){function r(a){return function(b,c,d,e,f){if(b.point!==a){var g=b.cx-a.x,h=b.cy-a.y,i=1/Math.sqrt(g*g+h*h);if((e-c)*i<k){var j=b.charge*i*i;return a.px-=g*j,a.py-=h*j,!0}if(b.point&&isFinite(i)){var j=b.pointCharge*i*i;a.px-=g*j,a.py-=h*j}}return!b.charge}}function s(b){dM(dL=b),dK=a}var a={},b=d3.dispatch("start","tick","end"),c=[1,1],d,e,f=.9,g=dR,h=dS,i=-30,j=.1,k=.8,l,m=[],n=[],o,p,q;return a.tick=function(){if((e*=.99)<.005)return b.end({type:"end",alpha:e=0}),!0;var a=m.length,d=n.length,g,h,k,l,s,t,u,v,w;for(h=0;h<d;++h){k=n[h],l=k.source,s=k.target,v=s.x-l.x,w=s.y-l.y;if(t=v*v+w*w)t=e*p[h]*((t=Math.sqrt(t))-o[h])/t,v*=t,w*=t,s.x-=v*(u=l.weight/(s.weight+l.weight)),s.y-=w*u,l.x+=v*(u=1-u),l.y+=w*u}if(u=e*j){v=c[0]/2,w=c[1]/2,h=-1;if(u)while(++h<a)k=m[h],k.x+=(v-k.x)*u,k.y+=(w-k.y)*u}if(i){dQ(g=d3.geom.quadtree(m),e,q),h=-1;while(++h<a)(k=m[h]).fixed||g.visit(r(k))}h=-1;while(++h<a)k=m[h],k.fixed?(k.x=k.px,k.y=k.py):(k.x-=(k.px-(k.px=k.x))*f,k.y-=(k.py-(k.py=k.y))*f);b.tick({type:"tick",alpha:e})},a.nodes=function(b){return arguments.length?(m=b,a):m},a.links=function(b){return arguments.length?(n=b,a):n},a.size=function(b){return arguments.length?(c=b,a):c},a.linkDistance=function(b){return arguments.length?(g=d3.functor(b),a):g},a.distance=a.linkDistance,a.linkStrength=function(b){return arguments.length?(h=d3.functor(b),a):h},a.friction=function(b){return arguments.length?(f=b,a):f},a.charge=function(b){return arguments.length?(i=typeof b=="function"?b:+b,a):i},a.gravity=function(b){return arguments.length?(j=b,a):j},a.theta=function(b){return arguments.length?(k=b,a):k},a.alpha=function(c){return arguments.length?(e?c>0?e=c:e=0:c>0&&(b.start({type:"start",alpha:e=c}),d3.timer(a.tick)),a):e},a.start=function(){function s(a,c){var d=t(b),e=-1,f=d.length,g;while(++e<f)if(!isNaN(g=d[e][a]))return g;return Math.random()*c}function t(){if(!l){l=[];for(d=0;d<e;++d)l[d]=[];for(d=0;d<f;++d){var a=n[d];l[a.source.index].push(a.target),l[a.target.index].push(a.source)}}return l[b]}var b,d,e=m.length,f=n.length,j=c[0],k=c[1],l,r;for(b=0;b<e;++b)(r=m[b]).index=b,r.weight=0;o=[],p=[];for(b=0;b<f;++b)r=n[b],typeof r.source=="number"&&(r.source=m[r.source]),typeof r.target=="number"&&(r.target=m[r.target]),o[b]=g.call(this,r,b),p[b]=h.call(this,r,b),++r.source.weight,++r.target.weight;for(b=0;b<e;++b)r=m[b],isNaN(r.x)&&(r.x=s("x",j)),isNaN(r.y)&&(r.y=s("y",k)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);q=[];if(typeof i=="function")for(b=0;b<e;++b)q[b]=+i.call(this,m[b],b);else for(b=0;b<e;++b)q[b]=i;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){d||(d=d3.behavior.drag().origin(Object).on("dragstart",s).on("drag",dP).on("dragend",dO)),this.on("mouseover.force",dM).on("mouseout.force",dN).call(d)},d3.rebind(a,b,"on")};var dK,dL;d3.layout.partition=function(){function c(a,b,d,e){var f=a.children;a.x=b,a.y=a.depth*e,a.dx=d,a.dy=e;if(f&&(h=f.length)){var g=-1,h,i,j;d=a.value?d/a.value:0;while(++g<h)c(i=f[g],b,j=i.value*d,e),b+=j}}function d(a){var b=a.children,c=0;if(b&&(f=b.length)){var e=-1,f;while(++e<f)c=Math.max(c,d(b[e]))}return 1+c}function e(e,f){var g=a.call(this,e,f);return c(g[0],0,b[0],b[1]/d(g[0])),g}var a=d3.layout.hierarchy(),b=[1,1];return e.size=function(a){return arguments.length?(b=a,e):b},ef(e,a)},d3.layout.pie=function(){function f(g,h){var i=g.map(function(b,c){return+a.call(f,b,c)}),j=+(typeof c=="function"?c.apply(this,arguments):c),k=((typeof e=="function"?e.apply(this,arguments):e)-c)/d3.sum(i),l=d3.range(g.length);b!=null&&l.sort(b===dT?function(a,b){return i[b]-i[a]}:function(a,c){return b(g[a],g[c])});var m=[];return l.forEach(function(a){m[a]={data:g[a],value:d=i[a],startAngle:j,endAngle:j+=d*k}}),m}var a=Number,b=dT,c=0,e=2*Math.PI;return f.value=function(b){return arguments.length?(a=b,f):a},f.sort=function(a){return arguments.length?(b=a,f):b},f.startAngle=function(a){return arguments.length?(c=a,f):c},f.endAngle=function(a){return arguments.length?(e=a,f):e},f};var dT={};d3.layout.stack=function(){function g(h,i){var j=h.map(function(b,c){return a.call(g,b,c)}),k=j.map(function(a,b){return a.map(function(a,b){return[e.call(g,a,b),f.call(g,a,b)]})}),l=b.call(g,k,i);j=d3.permute(j,l),k=d3.permute(k,l);var m=c.call(g,k,i),n=j.length,o=j[0].length,p,q,r;for(q=0;q<o;++q){d.call(g,j[0][q],r=m[q],k[0][q][1]);for(p=1;p<n;++p)d.call(g,j[p][q],r+=k[p-1][q][1],k[p][q][1])}return h}var a=Object,b=dZ,c=d$,d=dW,e=dU,f=dV;return g.values=function(b){return arguments.length?(a=b,g):a},g.order=function(a){return arguments.length?(b=typeof a=="function"?a:dX.get(a)||dZ,g):b},g.offset=function(a){return arguments.length?(c=typeof a=="function"?a:dY.get(a)||d$,g):c},g.x=function(a){return arguments.length?(e=a,g):e},g.y=function(a){return arguments.length?(f=a,g):f},g.out=function(a){return arguments.length?(d=a,g):d},g};var dX=d3.map({"inside-out":function(a){var b=a.length,c,d,e=a.map(d_),f=a.map(ea),g=d3.range(b).sort(function(a,b){return e[a]-e[b]}),h=0,i=0,j=[],k=[];for(c=0;c<b;++c)d=g[c],h<i?(h+=f[d],j.push(d)):(i+=f[d],k.push(d));return k.reverse().concat(j)},reverse:function(a){return d3.range(a.length).reverse()},"default":dZ}),dY=d3.map({silhouette:function(a){var b=a.length,c=a[0].length,d=[],e=0,f,g,h,i=[];for(g=0;g<c;++g){for(f=0,h=0;f<b;f++)h+=a[f][g][1];h>e&&(e=h),d.push(h)}for(g=0;g<c;++g)i[g]=(e-d[g])/2;return i},wiggle:function(a){var b=a.length,c=a[0],d=c.length,e=0,f,g,h,i,j,k,l,m,n,o=[];o[0]=m=n=0;for(g=1;g<d;++g){for(f=0,i=0;f<b;++f)i+=a[f][g][1];for(f=0,j=0,l=c[g][0]-c[g-1][0];f<b;++f){for(h=0,k=(a[f][g][1]-a[f][g-1][1])/(2*l);h<f;++h)k+=(a[h][g][1]-a[h][g-1][1])/l;j+=k*a[f][g][1]}o[g]=m-=i?j/i*l:0,m<n&&(n=m)}for(g=0;g<d;++g)o[g]-=n;return o},expand:function(a){var b=a.length,c=a[0].length,d=1/b,e,f,g,h=[];for(f=0;f<c;++f){for(e=0,g=0;e<b;e++)g+=a[e][f][1];if(g)for(e=0;e<b;e++)a[e][f][1]/=g;else for(e=0;e<b;e++)a[e][f][1]=d}for(f=0;f<c;++f)h[f]=0;return h},zero:d$});d3.layout.histogram=function(){function e(e,f){var g=[],h=e.map(b,this),i=c.call(this,h,f),j=d.call(this,i,h,f),k,f=-1,l=h.length,m=j.length-1,n=a?1:1/l,o;while(++f<m)k=g[f]=[],k.dx=j[f+1]-(k.x=j[f]),k.y=0;f=-1;while(++f<l)o=h[f],o>=i[0]&&o<=i[1]&&(k=g[d3.bisect(j,o,1,m)-1],k.y+=n,k.push(e[f]));return g}var a=!0,b=Number,c=ee,d=ec;return e.value=function(a){return arguments.length?(b=a,e):b},e.range=function(a){return arguments.length?(c=d3.functor(a),e):c},e.bins=function(a){return arguments.length?(d=typeof a=="number"?function(b){return ed(b,a)}:d3.functor(a),e):d},e.frequency=function(b){return arguments.length?(a=!!b,e):a},e},d3.layout.hierarchy=function(){function e(f,h,i){var j=b.call(g,f,h),k=ek?f:{data:f};k.depth=h,i.push(k);if(j&&(m=j.length)){var l=-1,m,n=k.children=[],o=0,p=h+1;while(++l<m)d=e(j[l],p,i),d.parent=k,n.push(d),o+=d.value;a&&n.sort(a),c&&(k.value=o)}else c&&(k.value=+c.call(g,f,h)||0);return k}function f(a,b){var d=a.children,e=0;if(d&&(i=d.length)){var h=-1,i,j=b+1;while(++h<i)e+=f(d[h],j)}else c&&(e=+c.call(g,ek?a:a.data,b)||0);return c&&(a.value=e),e}function g(a){var b=[];return e(a,0,b),b}var a=ei,b=eg,c=eh;return g.sort=function(b){return arguments.length?(a=b,g):a},g.children=function(a){return arguments.length?(b=a,g):b},g.value=function(a){return arguments.length?(c=a,g):c},g.revalue=function(a){return f(a,0),a},g};var ek=!1;d3.layout.pack=function(){function c(c,d){var e=a.call(this,c,d),f=e[0];f.x=0,f.y=0,es(f);var g=b[0],h=b[1],i=1/Math.max(2*f.r/g,2*f.r/h);return et(f,g/2,h/2,i),e}var a=d3.layout.hierarchy().sort(el),b=[1,1];return c.size=function(a){return arguments.length?(b=a,c):b},ef(c,a)},d3.layout.cluster=function(){function d(d,e){var f=a.call(this,d,e),g=f[0],h,i=0,j,k;eG(g,function(a){var c=a.children;c&&c.length?(a.x=ew(c),a.y=ev(c)):(a.x=h?i+=b(a,h):0,a.y=0,h=a)});var l=ex(g),m=ey(g),n=l.x-b(l,m)/2,o=m.x+b(m,l)/2;return eG(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=(1-(g.y?a.y/g.y:1))*c[1]}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=ez,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},ef(d,a)},d3.layout.tree=function(){function d(d,e){function h(a,c){var d=a.children,e=a._tree;if(d&&(f=d.length)){var f,g=d[0],i,k=g,l,m=-1;while(++m<f)l=d[m],h(l,i),k=j(l,i,k),i=l;eH(a);var n=.5*(g._tree.prelim+l._tree.prelim);c?(e.prelim=c._tree.prelim+b(a,c),e.mod=e.prelim-n):e.prelim=n}else c&&(e.prelim=c._tree.prelim+b(a,c))}function i(a,b){a.x=a._tree.prelim+b;var c=a.children;if(c&&(e=c.length)){var d=-1,e;b+=a._tree.mod;while(++d<e)i(c[d],b)}}function j(a,c,d){if(c){var e=a,f=a,g=c,h=a.parent.children[0],i=e._tree.mod,j=f._tree.mod,k=g._tree.mod,l=h._tree.mod,m;while(g=eB(g),e=eA(e),g&&e)h=eA(h),f=eB(f),f._tree.ancestor=a,m=g._tree.prelim+k-e._tree.prelim-i+b(g,e),m>0&&(eI(eJ(g,a,d),a,m),i+=m,j+=m),k+=g._tree.mod,i+=e._tree.mod,l+=h._tree.mod,j+=f._tree.mod;g&&!eB(f)&&(f._tree.thread=g,f._tree.mod+=k-j),e&&!eA(h)&&(h._tree.thread=e,h._tree.mod+=i-l,d=a)}return d}var f=a.call(this,d,e),g=f[0];eG(g,function(a,b){a._tree={ancestor:a,prelim:0,mod:0,change:0,shift:0,number:b?b._tree.number+1:0}}),h(g),i(g,-g._tree.prelim);var k=eC(g,eE),l=eC(g,eD),m=eC(g,eF),n=k.x-b(k,l)/2,o=l.x+b(l,k)/2,p=m.depth||1;return eG(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=a.depth/p*c[1],delete a._tree}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=ez,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},ef(d,a)},d3.layout.treemap=function(){function i(a,b){var c=-1,d=a.length,e,f;while(++c<d)f=(e=a[c]).value*(b<0?0:b),e.area=isNaN(f)||f<=0?0:f}function j(a){var b=a.children;if(b&&b.length){var c=e(a),d=[],f=b.slice(),g,h=Infinity,k,n=Math.min(c.dx,c.dy),o;i(f,c.dx*c.dy/a.value),d.area=0;while((o=f.length)>0)d.push(g=f[o-1]),d.area+=g.area,(k=l(d,n))<=h?(f.pop(),h=k):(d.area-=d.pop().area,m(d,n,c,!1),n=Math.min(c.dx,c.dy),d.length=d.area=0,h=Infinity);d.length&&(m(d,n,c,!0),d.length=d.area=0),b.forEach(j)}}function k(a){var b=a.children;if(b&&b.length){var c=e(a),d=b.slice(),f,g=[];i(d,c.dx*c.dy/a.value),g.area=0;while(f=d.pop())g.push(f),g.area+=f.area,f.z!=null&&(m(g,f.z?c.dx:c.dy,c,!d.length),g.length=g.area=0);b.forEach(k)}}function l(a,b){var c=a.area,d,e=0,f=Infinity,g=-1,i=a.length;while(++g<i){if(!(d=a[g].area))continue;d<f&&(f=d),d>e&&(e=d)}return c*=c,b*=b,c?Math.max(b*e*h/c,c/(b*f*h)):Infinity}function m(a,c,d,e){var f=-1,g=a.length,h=d.x,i=d.y,j=c?b(a.area/c):0,k;if(c==d.dx){if(e||j>d.dy)j=d.dy;while(++f<g)k=a[f],k.x=h,k.y=i,k.dy=j,h+=k.dx=Math.min(d.x+d.dx-h,j?b(k.area/j):0);k.z=!0,k.dx+=d.x+d.dx-h,d.y+=j,d.dy-=j}else{if(e||j>d.dx)j=d.dx;while(++f<g)k=a[f],k.x=h,k.y=i,k.dx=j,i+=k.dy=Math.min(d.y+d.dy-i,j?b(k.area/j):0);k.z=!1,k.dy+=d.y+d.dy-i,d.x+=j,d.dx-=j}}function n(b){var d=g||a(b),e=d[0];return e.x=0,e.y=0,e.dx=c[0],e.dy=c[1],g&&a.revalue(e),i([e],e.dx*e.dy/e.value),(g?k:j)(e),f&&(g=d),d}var a=d3.layout.hierarchy(),b=Math.round,c=[1,1],d=null,e=eK,f=!1,g,h=.5*(1+Math.sqrt(5));return n.size=function(a){return arguments.length?(c=a,n):c},n.padding=function(a){function b(b){var c=a.call(n,b,b.depth);return c==null?eK(b):eL(b,typeof c=="number"?[c,c,c,c]:c)}function c(b){return eL(b,a)}if(!arguments.length)return d;var f;return e=(d=a)==null?eK:(f=typeof a)==="function"?b:f==="number"?(a=[a,a,a,a],c):c,n},n.round=function(a){return arguments.length?(b=a?Math.round:Number,n):b!=Number},n.sticky=function(a){return arguments.length?(f=a,g=null,n):f},n.ratio=function(a){return arguments.length?(h=a,n):h},ef(n,a)},d3.csv=function(a,b){d3.text(a,"text/csv",function(a){b(a&&d3.csv.parse(a))})},d3.csv.parse=function(a){var b;return d3.csv.parseRows(a,function(a,c){if(c){var d={},e=-1,f=b.length;while(++e<f)d[b[e]]=a[e];return d}return b=a,null})},d3.csv.parseRows=function(a,b){function j(){if(f.lastIndex>=a.length)return d;if(i)return i=!1,c;var b=f.lastIndex;if(a.charCodeAt(b)===34){var e=b;while(e++<a.length)if(a.charCodeAt(e)===34){if(a.charCodeAt(e+1)!==34)break;e++}f.lastIndex=e+2;var g=a.charCodeAt(e+1);return g===13?(i=!0,a.charCodeAt(e+2)===10&&f.lastIndex++):g===10&&(i=!0),a.substring(b+1,e).replace(/""/g,'"')}var h=f.exec(a);return h?(i=h[0].charCodeAt(0)!==44,a.substring(b,h.index)):(f.lastIndex=a.length,a.substring(b))}var c={},d={},e=[],f=/\r\n|[,\r\n]/g,g=0,h,i;f.lastIndex=0;while((h=j())!==d){var k=[];while(h!==c&&h!==d)k.push(h),h=j();if(b&&!(k=b(k,g++)))continue;e.push(k)}return e},d3.csv.format=function(a){return a.map(eM).join("\n")},d3.geo={};var eO=Math.PI/180;d3.geo.azimuthal=function(){function i(b){var f=b[0]*eO-e,i=b[1]*eO,j=Math.cos(f),k=Math.sin(f),l=Math.cos(i),m=Math.sin(i),n=a!=="orthographic"?h*m+g*l*j:null,o,p=a==="stereographic"?1/(1+n):a==="gnomonic"?1/n:a==="equidistant"?(o=Math.acos(n),o?o/Math.sin(o):0):a==="equalarea"?Math.sqrt(2/(1+n)):1,q=p*l*k,r=p*(h*l*j-g*m);return[c*q+d[0],c*r+d[1]]}var a="orthographic",b,c=200,d=[480,250],e,f,g,h;return i.invert=function(b){var f=(b[0]-d[0])/c,i=(b[1]-d[1])/c,j=Math.sqrt(f*f+i*i),k=a==="stereographic"?2*Math.atan(j):a==="gnomonic"?Math.atan(j):a==="equidistant"?j:a==="equalarea"?2*Math.asin(.5*j):Math.asin(j),l=Math.sin(k),m=Math.cos(k);return[(e+Math.atan2(f*l,j*g*m+i*h*l))/eO,Math.asin(m*h-(j?i*l*g/j:0))/eO]},i.mode=function(b){return arguments.length?(a=b+"",i):a},i.origin=function(a){return arguments.length?(b=a,e=b[0]*eO,f=b[1]*eO,g=Math.cos(f),h=Math.sin(f),i):b},i.scale=function(a){return arguments.length?(c=+a,i):c},i.translate=function(a){return arguments.length?(d=[+a[0],+a[1]],i):d},i.origin([0,0])},d3.geo.albers=function(){function i(a){var b=f*(eO*a[0]-e),i=Math.sqrt(g-2*f*Math.sin(eO*a[1]))/f;return[c*i*Math.sin(b)+d[0],c*(i*Math.cos(b)-h)+d[1]]}function j(){var c=eO*b[0],d=eO*b[1],j=eO*a[1],k=Math.sin(c),l=Math.cos(c);return e=eO*a[0],f=.5*(k+Math.sin(d)),g=l*l+2*f*k,h=Math.sqrt(g-2*f*Math.sin(j))/f,i}var a=[-98,38],b=[29.5,45.5],c=1e3,d=[480,250],e,f,g,h;return i.invert=function(a){var b=(a[0]-d[0])/c,i=(a[1]-d[1])/c,j=h+i,k=Math.atan2(b,j),l=Math.sqrt(b*b+j*j);return[(e+k/f)/eO,Math.asin((g-l*l*f*f)/(2*f))/eO]},i.origin=function(b){return arguments.length?(a=[+b[0],+b[1]],j()):a},i.parallels=function(a){return arguments.length?(b=[+a[0],+a[1]],j()):b},i.scale=function(a){return arguments.length?(c=+a,i):c},i.translate=function(a){return arguments.length?(d=[+a[0],+a[1]],i):d},j()},d3.geo.albersUsa=function(){function e(e){var f=e[0],g=e[1];return(g>50?b:f<-140?c:g<21?d:a)(e)}var a=d3.geo.albers(),b=d3.geo.albers().origin([-160,60]).parallels([55,65]),c=d3.geo.albers().origin([-160,20]).parallels([8,18]),d=d3.geo.albers().origin([-60,10]).parallels([8,18]);return e.scale=function(f){return arguments.length?(a.scale(f),b.scale(f*.6),c.scale(f),d.scale(f*1.5),e.translate(a.translate())):a.scale()},e.translate=function(f){if(!arguments.length)return a.translate();var g=a.scale()/1e3,h=f[0],i=f[1];return a.translate(f),b.translate([h-400*g,i+170*g]),c.translate([h-190*g,i+200*g]),d.translate([h+580*g,i+430*g]),e},e.scale(a.scale())},d3.geo.bonne=function(){function g(g){var h=g[0]*eO-c,i=g[1]*eO-d;if(e){var j=f+e-i,k=h*Math.cos(i)/j;h=j*Math.sin(k),i=j*Math.cos(k)-f}else h*=Math.cos(i),i*=-1;return[a*h+b[0],a*i+b[1]]}var a=200,b=[480,250],c,d,e,f;return g.invert=function(d){var g=(d[0]-b[0])/a,h=(d[1]-b[1])/a;if(e){var i=f+h,j=Math.sqrt(g*g+i*i);h=f+e-j,g=c+j*Math.atan2(g,i)/Math.cos(h)}else h*=-1,g/=Math.cos(h);return[g/eO,h/eO]},g.parallel=function(a){return arguments.length?(f=1/Math.tan(e=a*eO),g):e/eO},g.origin=function(a){return arguments.length?(c=a[0]*eO,d=a[1]*eO,g):[c/eO,d/eO]},g.scale=function(b){return arguments.length?(a=+b,g):a},g.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],g):b},g.origin([0,0]).parallel(45)},d3.geo.equirectangular=function(){function c(c){var d=c[0]/360,e=-c[1]/360;return[a*d+b[0],a*e+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,-360*e]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.mercator=function(){function c(c){var d=c[0]/360,e=-(Math.log(Math.tan(Math.PI/4+c[1]*eO/2))/eO)/360;return[a*d+b[0],a*Math.max(-0.5,Math.min(.5,e))+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,2*Math.atan(Math.exp(-360*e*eO))/eO-90]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.path=function(){function d(c,d){return typeof a=="function"&&(b=eQ(a.apply(this,arguments))),f(c)||null}function e(a){return c(a).join(",")}function h(a){var b=k(a[0]),c=0,d=a.length;while(++c<d)b-=k(a[c]);return b}function i(a){var b=d3.geom.polygon(a[0].map(c)),d=b.area(),e=b.centroid(d<0?(d*=-1,1):-1),f=e[0],g=e[1],h=d,i=0,j=a.length;while(++i<j)b=d3.geom.polygon(a[i].map(c)),d=b.area(),e=b.centroid(d<0?(d*=-1,1):-1),f-=e[0],g-=e[1],h-=d;return[f,g,6*h]}function k(a){return Math.abs(d3.geom.polygon(a.map(c)).area())}var a=4.5,b=eQ(a),c=d3.geo.albersUsa(),f=eP({FeatureCollection:function(a){var b=[],c=a.features,d=-1,e=c.length;while(++d<e)b.push(f(c[d].geometry));return b.join("")},Feature:function(a){return f(a.geometry)},Point:function(a){return"M"+e(a.coordinates)+b},MultiPoint:function(a){var c=[],d=a.coordinates,f=-1,g=d.length;while(++f<g)c.push("M",e(d[f]),b);return c.join("")},LineString:function(a){var b=["M"],c=a.coordinates,d=-1,f=c.length;while(++d<f)b.push(e(c[d]),"L");return b.pop(),b.join("")},MultiLineString:function(a){var b=[],c=a.coordinates,d=-1,f=c.length,g,h,i;while(++d<f){g=c[d],h=-1,i=g.length,b.push("M");while(++h<i)b.push(e(g[h]),"L");b.pop()}return b.join("")},Polygon:function(a){var b=[],c=a.coordinates,d=-1,f=c.length,g,h,i;while(++d<f){g=c[d],h=-1;if((i=g.length-1)>0){b.push("M");while(++h<i)b.push(e(g[h]),"L");b[b.length-1]="Z"}}return b.join("")},MultiPolygon:function(a){var b=[],c=a.coordinates,d=-1,f=c.length,g,h,i,j,k,l;while(++d<f){g=c[d],h=-1,i=g.length;while(++h<i){j=g[h],k=-1;if((l=j.length-1)>0){b.push("M");while(++k<l)b.push(e(j[k]),"L");b[b.length-1]="Z"}}}return b.join("")},GeometryCollection:function(a){var b=[],c=a.geometries,d=-1,e=c.length;while(++d<e)b.push(f(c[d]));return b.join("")}}),g=d.area=eP({FeatureCollection:function(a){var b=0,c=a.features,d=-1,e=c.length;while(++d<e)b+=g(c[d]);return b},Feature:function(a){return g(a.geometry)},Polygon:function(a){return h(a.coordinates)},MultiPolygon:function(a){var b=0,c=a.coordinates,d=-1,e=c.length;while(++d<e)b+=h(c[d]);return b},GeometryCollection:function(a){var b=0,c=a.geometries,d=-1,e=c.length;while(++d<e)b+=g(c[d]);return b}},0),j=d.centroid=eP({Feature:function(a){return j(a.geometry)},Polygon:function(a){var b=i(a.coordinates);return[b[0]/b[2],b[1]/b[2]]},MultiPolygon:function(a){var b=0,c=a.coordinates,d,e=0,f=0,g=0,h=-1,j=c.length;while(++h<j)d=i(c[h]),e+=d[0],f+=d[1],g+=d[2];return[e/g,f/g]}});return d.projection=function(a){return c=a,d},d.pointRadius=function(c){return typeof c=="function"?a=c:(a=+c,b=eQ(a)),d},d},d3.geo.bounds=function(a){var b=Infinity,c=Infinity,d=-Infinity,e=-Infinity;return eR(a,function(a,f){a<b&&(b=a),a>d&&(d=a),f<c&&(c=f),f>e&&(e=f)}),[[b,c],[d,e]]};var eS={Feature:eT,FeatureCollection:eU,GeometryCollection:eV,LineString:eW,MultiLineString:eX,MultiPoint:eW,MultiPolygon:eY,Point:eZ,Polygon:e$};d3.geo.circle=function(){function e(){}function f(a){return d.distance(a)<c}function h(a){var b=-1,e=a.length,f=[],g,h,j,k,l;while(++b<e)l=d.distance(j=a[b]),l<c?(h&&f.push(fb(h,j)((k-c)/(k-l))),f.push(j),g=h=null):(h=j,!g&&f.length&&(f.push(fb(f[f.length-1],h)((c-k)/(l-k))),g=h)),k=l;return h&&f.length&&(l=d.distance(j=f[0]),f.push(fb(h,j)((k-c)/(k-l)))),i(f)}function i(a){var b=0,c=a.length,e,f,g=c?[a[0]]:a,h,i=d.source();while(++b<c){h=d.source(a[b-1])(a[b]).coordinates;for(e=0,f=h.length;++e<f;)g.push(h[e])}return d.source(i),g}var a=[0,0],b=89.99,c=b*eO,d=d3.geo.greatArc().target(Object);e.clip=function(b){return d.source(typeof a=="function"?a.apply(this,arguments):a),g(b)};var g=eP({FeatureCollection:function(a){var b=a.features.map(g).filter(Object);return b&&(a=Object.create(a),a.features=b,a)},Feature:function(a){var b=g(a.geometry);return b&&(a=Object.create(a),a.geometry=b,a)},Point:function(a){return f
+(a.coordinates)&&a},MultiPoint:function(a){var b=a.coordinates.filter(f);return b.length&&{type:a.type,coordinates:b}},LineString:function(a){var b=h(a.coordinates);return b.length&&(a=Object.create(a),a.coordinates=b,a)},MultiLineString:function(a){var b=a.coordinates.map(h).filter(function(a){return a.length});return b.length&&(a=Object.create(a),a.coordinates=b,a)},Polygon:function(a){var b=a.coordinates.map(h);return b[0].length&&(a=Object.create(a),a.coordinates=b,a)},MultiPolygon:function(a){var b=a.coordinates.map(function(a){return a.map(h)}).filter(function(a){return a[0].length});return b.length&&(a=Object.create(a),a.coordinates=b,a)},GeometryCollection:function(a){var b=a.geometries.map(g).filter(Object);return b.length&&(a=Object.create(a),a.geometries=b,a)}});return e.origin=function(b){return arguments.length?(a=b,e):a},e.angle=function(a){return arguments.length?(c=(b=+a)*eO,e):b},e.precision=function(a){return arguments.length?(d.precision(a),e):d.precision()},e},d3.geo.greatArc=function(){function d(){var d=typeof a=="function"?a.apply(this,arguments):a,e=typeof b=="function"?b.apply(this,arguments):b,f=fb(d,e),g=c/f.d,h=0,i=[d];while((h+=g)<1)i.push(f(h));return i.push(e),{type:"LineString",coordinates:i}}var a=e_,b=fa,c=6*eO;return d.distance=function(){var c=typeof a=="function"?a.apply(this,arguments):a,d=typeof b=="function"?b.apply(this,arguments):b;return fb(c,d).d},d.source=function(b){return arguments.length?(a=b,d):a},d.target=function(a){return arguments.length?(b=a,d):b},d.precision=function(a){return arguments.length?(c=a*eO,d):c/eO},d},d3.geo.greatCircle=d3.geo.circle,d3.geom={},d3.geom.contour=function(a,b){var c=b||fe(a),d=[],e=c[0],f=c[1],g=0,h=0,i=NaN,j=NaN,k=0;do k=0,a(e-1,f-1)&&(k+=1),a(e,f-1)&&(k+=2),a(e-1,f)&&(k+=4),a(e,f)&&(k+=8),k===6?(g=j===-1?-1:1,h=0):k===9?(g=0,h=i===1?-1:1):(g=fc[k],h=fd[k]),g!=i&&h!=j&&(d.push([e,f]),i=g,j=h),e+=g,f+=h;while(c[0]!=e||c[1]!=f);return d};var fc=[1,0,1,1,-1,0,-1,1,0,0,0,0,-1,0,-1,NaN],fd=[0,-1,0,0,0,-1,0,0,1,-1,1,1,0,-1,0,NaN];d3.geom.hull=function(a){if(a.length<3)return[];var b=a.length,c=b-1,d=[],e=[],f,g,h=0,i,j,k,l,m,n,o,p;for(f=1;f<b;++f)a[f][1]<a[h][1]?h=f:a[f][1]==a[h][1]&&(h=a[f][0]<a[h][0]?f:h);for(f=0;f<b;++f){if(f===h)continue;j=a[f][1]-a[h][1],i=a[f][0]-a[h][0],d.push({angle:Math.atan2(j,i),index:f})}d.sort(function(a,b){return a.angle-b.angle}),o=d[0].angle,n=d[0].index,m=0;for(f=1;f<c;++f)g=d[f].index,o==d[f].angle?(i=a[n][0]-a[h][0],j=a[n][1]-a[h][1],k=a[g][0]-a[h][0],l=a[g][1]-a[h][1],i*i+j*j>=k*k+l*l?d[f].index=-1:(d[m].index=-1,o=d[f].angle,m=f,n=g)):(o=d[f].angle,m=f,n=g);e.push(h);for(f=0,g=0;f<2;++g)d[g].index!==-1&&(e.push(d[g].index),f++);p=e.length;for(;g<c;++g){if(d[g].index===-1)continue;while(!ff(e[p-2],e[p-1],d[g].index,a))--p;e[p++]=d[g].index}var q=[];for(f=0;f<p;++f)q.push(a[e[f]]);return q},d3.geom.polygon=function(a){return a.area=function(){var b=0,c=a.length,d=a[c-1][0]*a[0][1],e=a[c-1][1]*a[0][0];while(++b<c)d+=a[b-1][0]*a[b][1],e+=a[b-1][1]*a[b][0];return(e-d)*.5},a.centroid=function(b){var c=-1,d=a.length,e=0,f=0,g,h=a[d-1],i;arguments.length||(b=-1/(6*a.area()));while(++c<d)g=h,h=a[c],i=g[0]*h[1]-h[0]*g[1],e+=(g[0]+h[0])*i,f+=(g[1]+h[1])*i;return[e*b,f*b]},a.clip=function(b){var c,d=-1,e=a.length,f,g,h=a[e-1],i,j,k;while(++d<e){c=b.slice(),b.length=0,i=a[d],j=c[(g=c.length)-1],f=-1;while(++f<g)k=c[f],fg(k,h,i)?(fg(j,h,i)||b.push(fh(j,k,h,i)),b.push(k)):fg(j,h,i)&&b.push(fh(j,k,h,i)),j=k;h=i}return b},a},d3.geom.voronoi=function(a){var b=a.map(function(){return[]});return fj(a,function(a){var c,d,e,f,g,h;a.a===1&&a.b>=0?(c=a.ep.r,d=a.ep.l):(c=a.ep.l,d=a.ep.r),a.a===1?(g=c?c.y:-1e6,e=a.c-a.b*g,h=d?d.y:1e6,f=a.c-a.b*h):(e=c?c.x:-1e6,g=a.c-a.a*e,f=d?d.x:1e6,h=a.c-a.a*f);var i=[e,g],j=[f,h];b[a.region.l.index].push(i,j),b[a.region.r.index].push(i,j)}),b.map(function(b,c){var d=a[c][0],e=a[c][1];return b.forEach(function(a){a.angle=Math.atan2(a[0]-d,a[1]-e)}),b.sort(function(a,b){return a.angle-b.angle}).filter(function(a,c){return!c||a.angle-b[c-1].angle>1e-10})})};var fi={l:"r",r:"l"};d3.geom.delaunay=function(a){var b=a.map(function(){return[]}),c=[];return fj(a,function(c){b[c.region.l.index].push(a[c.region.r.index])}),b.forEach(function(b,d){var e=a[d],f=e[0],g=e[1];b.forEach(function(a){a.angle=Math.atan2(a[0]-f,a[1]-g)}),b.sort(function(a,b){return a.angle-b.angle});for(var h=0,i=b.length-1;h<i;h++)c.push([e,b[h],b[h+1]])}),c},d3.geom.quadtree=function(a,b,c,d,e){function k(a,b,c,d,e,f){if(isNaN(b.x)||isNaN(b.y))return;if(a.leaf){var g=a.point;g?Math.abs(g.x-b.x)+Math.abs(g.y-b.y)<.01?l(a,b,c,d,e,f):(a.point=null,l(a,g,c,d,e,f),l(a,b,c,d,e,f)):a.point=b}else l(a,b,c,d,e,f)}function l(a,b,c,d,e,f){var g=(c+e)*.5,h=(d+f)*.5,i=b.x>=g,j=b.y>=h,l=(j<<1)+i;a.leaf=!1,a=a.nodes[l]||(a.nodes[l]=fk()),i?c=g:e=g,j?d=h:f=h,k(a,b,c,d,e,f)}var f,g=-1,h=a.length;h&&isNaN(a[0].x)&&(a=a.map(fm));if(arguments.length<5)if(arguments.length===3)e=d=c,c=b;else{b=c=Infinity,d=e=-Infinity;while(++g<h)f=a[g],f.x<b&&(b=f.x),f.y<c&&(c=f.y),f.x>d&&(d=f.x),f.y>e&&(e=f.y);var i=d-b,j=e-c;i>j?e=c+i:d=b+j}var m=fk();return m.add=function(a){k(m,a,b,c,d,e)},m.visit=function(a){fl(a,m,b,c,d,e)},a.forEach(m.add),m},d3.time={};var fn=Date;fo.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){fp.setUTCDate.apply(this._,arguments)},setDay:function(){fp.setUTCDay.apply(this._,arguments)},setFullYear:function(){fp.setUTCFullYear.apply(this._,arguments)},setHours:function(){fp.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){fp.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){fp.setUTCMinutes.apply(this._,arguments)},setMonth:function(){fp.setUTCMonth.apply(this._,arguments)},setSeconds:function(){fp.setUTCSeconds.apply(this._,arguments)},setTime:function(){fp.setTime.apply(this._,arguments)}};var fp=Date.prototype;d3.time.format=function(a){function c(c){var d=[],e=-1,f=0,g,h;while(++e<b)a.charCodeAt(e)==37&&(d.push(a.substring(f,e),(h=fv[g=a.charAt(++e)])?h(c):g),f=e+1);return d.push(a.substring(f,e)),d.join("")}var b=a.length;return c.parse=function(b){var c={y:1900,m:0,d:1,H:0,M:0,S:0,L:0},d=fq(c,a,b,0);if(d!=b.length)return null;"p"in c&&(c.H=c.H%12+c.p*12);var e=new fn;return e.setFullYear(c.y,c.m,c.d),e.setHours(c.H,c.M,c.S,c.L),e},c.toString=function(){return a},c};var fr=d3.format("02d"),fs=d3.format("03d"),ft=d3.format("04d"),fu=d3.format("2d"),fv={a:function(a){return d3_time_weekdays[a.getDay()].substring(0,3)},A:function(a){return d3_time_weekdays[a.getDay()]},b:function(a){return fG[a.getMonth()].substring(0,3)},B:function(a){return fG[a.getMonth()]},c:d3.time.format("%a %b %e %H:%M:%S %Y"),d:function(a){return fr(a.getDate())},e:function(a){return fu(a.getDate())},H:function(a){return fr(a.getHours())},I:function(a){return fr(a.getHours()%12||12)},j:function(a){return fs(1+d3.time.dayOfYear(a))},L:function(a){return fs(a.getMilliseconds())},m:function(a){return fr(a.getMonth()+1)},M:function(a){return fr(a.getMinutes())},p:function(a){return a.getHours()>=12?"PM":"AM"},S:function(a){return fr(a.getSeconds())},U:function(a){return fr(d3.time.sundayOfYear(a))},w:function(a){return a.getDay()},W:function(a){return fr(d3.time.mondayOfYear(a))},x:d3.time.format("%m/%d/%y"),X:d3.time.format("%H:%M:%S"),y:function(a){return fr(a.getFullYear()%100)},Y:function(a){return ft(a.getFullYear()%1e4)},Z:fW,"%":function(a){return"%"}},fw={a:fx,A:fy,b:fB,B:fD,c:fH,d:fO,e:fO,H:fP,I:fP,L:fS,m:fN,M:fQ,p:fU,S:fR,x:fI,X:fJ,y:fL,Y:fK},fz=/^(?:sun|mon|tue|wed|thu|fri|sat)/i,fA=/^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/i;d3_time_weekdays=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var fC=d3.map({jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11}),fE=/^(?:January|February|March|April|May|June|July|August|September|October|November|December)/ig,fF=d3.map({january:0,february:1,march:2,april:3,may:4,june:5,july:6,august:7,september:8,october:9,november:10,december:11}),fG=["January","February","March","April","May","June","July","August","September","October","November","December"],fT=/\s*\d+/,fV=d3.map({am:0,pm:1});d3.time.format.utc=function(a){function c(a){try{fn=fo;var c=new fn;return c._=a,b(c)}finally{fn=Date}}var b=d3.time.format(a);return c.parse=function(a){try{fn=fo;var c=b.parse(a);return c&&c._}finally{fn=Date}},c.toString=b.toString,c};var fX=d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");d3.time.format.iso=Date.prototype.toISOString?fY:fX,fY.parse=function(a){return new Date(a)},fY.toString=fX.toString,d3.time.second=fZ(function(a){return new fn(Math.floor(a/1e3)*1e3)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*1e3)},function(a){return a.getSeconds()}),d3.time.seconds=d3.time.second.range,d3.time.seconds.utc=d3.time.second.utc.range,d3.time.minute=fZ(function(a){return new fn(Math.floor(a/6e4)*6e4)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*6e4)},function(a){return a.getMinutes()}),d3.time.minutes=d3.time.minute.range,d3.time.minutes.utc=d3.time.minute.utc.range,d3.time.hour=fZ(function(a){var b=a.getTimezoneOffset()/60;return new fn((Math.floor(a/36e5-b)+b)*36e5)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*36e5)},function(a){return a.getHours()}),d3.time.hours=d3.time.hour.range,d3.time.hours.utc=d3.time.hour.utc.range,d3.time.day=fZ(function(a){return new fn(a.getFullYear(),a.getMonth(),a.getDate())},function(a,b){a.setDate(a.getDate()+b)},function(a){return a.getDate()-1}),d3.time.days=d3.time.day.range,d3.time.days.utc=d3.time.day.utc.range,d3.time.dayOfYear=function(a){var b=d3.time.year(a);return Math.floor((a-b)/864e5-(a.getTimezoneOffset()-b.getTimezoneOffset())/1440)},d3_time_weekdays.forEach(function(a,b){a=a.toLowerCase(),b=7-b;var c=d3.time[a]=fZ(function(a){return(a=d3.time.day(a)).setDate(a.getDate()-(a.getDay()+b)%7),a},function(a,b){a.setDate(a.getDate()+Math.floor(b)*7)},function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)-(c!==b)});d3.time[a+"s"]=c.range,d3.time[a+"s"].utc=c.utc.range,d3.time[a+"OfYear"]=function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)}}),d3.time.week=d3.time.sunday,d3.time.weeks=d3.time.sunday.range,d3.time.weeks.utc=d3.time.sunday.utc.range,d3.time.weekOfYear=d3.time.sundayOfYear,d3.time.month=fZ(function(a){return new fn(a.getFullYear(),a.getMonth(),1)},function(a,b){a.setMonth(a.getMonth()+b)},function(a){return a.getMonth()}),d3.time.months=d3.time.month.range,d3.time.months.utc=d3.time.month.utc.range,d3.time.year=fZ(function(a){return new fn(a.getFullYear(),0,1)},function(a,b){a.setFullYear(a.getFullYear()+b)},function(a){return a.getFullYear()}),d3.time.years=d3.time.year.range,d3.time.years.utc=d3.time.year.utc.range;var gf=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],gg=[[d3.time.second,1],[d3.time.second,5],[d3.time.second,15],[d3.time.second,30],[d3.time.minute,1],[d3.time.minute,5],[d3.time.minute,15],[d3.time.minute,30],[d3.time.hour,1],[d3.time.hour,3],[d3.time.hour,6],[d3.time.hour,12],[d3.time.day,1],[d3.time.day,2],[d3.time.week,1],[d3.time.month,1],[d3.time.month,3],[d3.time.year,1]],gh=[[d3.time.format("%Y"),function(a){return!0}],[d3.time.format("%B"),function(a){return a.getMonth()}],[d3.time.format("%b %d"),function(a){return a.getDate()!=1}],[d3.time.format("%a %d"),function(a){return a.getDay()&&a.getDate()!=1}],[d3.time.format("%I %p"),function(a){return a.getHours()}],[d3.time.format("%I:%M"),function(a){return a.getMinutes()}],[d3.time.format(":%S"),function(a){return a.getSeconds()}],[d3.time.format(".%L"),function(a){return a.getMilliseconds()}]],gi=d3.scale.linear(),gj=gc(gh);gg.year=function(a,b){return gi.domain(a.map(ge)).ticks(b).map(gd)},d3.time.scale=function(){return f_(d3.scale.linear(),gg,gj)};var gk=gg.map(function(a){return[a[0].utc,a[1]]}),gl=[[d3.time.format.utc("%Y"),function(a){return!0}],[d3.time.format.utc("%B"),function(a){return a.getUTCMonth()}],[d3.time.format.utc("%b %d"),function(a){return a.getUTCDate()!=1}],[d3.time.format.utc("%a %d"),function(a){return a.getUTCDay()&&a.getUTCDate()!=1}],[d3.time.format.utc("%I %p"),function(a){return a.getUTCHours()}],[d3.time.format.utc("%I:%M"),function(a){return a.getUTCMinutes()}],[d3.time.format.utc(":%S"),function(a){return a.getUTCSeconds()}],[d3.time.format.utc(".%L"),function(a){return a.getUTCMilliseconds()}]],gm=gc(gl);gk.year=function(a,b){return gi.domain(a.map(go)).ticks(b).map(gn)},d3.time.scale.utc=function(){return f_(d3.scale.linear(),gk,gm)}})();
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/lib/jquery.min.js b/src/ext/floodlight/src/main/resources/web/lib/jquery.min.js
new file mode 100644
index 0000000..16ad06c
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/jquery.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.2 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):b_(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&f.type(b)==="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bZ(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bZ(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bZ(a,c,d,e,"*",g));return l}function bY(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?1:0,g=4;if(d>0){if(c!=="border")for(;e<g;e+=2)c||(d-=parseFloat(f.css(a,"padding"+bx[e]))||0),c==="margin"?d+=parseFloat(f.css(a,c+bx[e]))||0:d-=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0;return d+"px"}d=by(a,b);if(d<0||d==null)d=a.style[b];if(bt.test(d))return d;d=parseFloat(d)||0;if(c)for(;e<g;e+=2)d+=parseFloat(f.css(a,"padding"+bx[e]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+bx[e]))||0);return d+"px"}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;b.nodeType===1&&(b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?b.outerHTML=a.outerHTML:c!=="input"||a.type!=="checkbox"&&a.type!=="radio"?c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text):(a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value)),b.removeAttribute(f.expando),b.removeAttribute("_submit_attached"),b.removeAttribute("_change_attached"))}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c,i[c][d])}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h,i){var j,k=d==null,l=0,m=a.length;if(d&&typeof d=="object"){for(l in d)e.access(a,c,l,d[l],1,h,f);g=1}else if(f!==b){j=i===b&&e.isFunction(f),k&&(j?(j=c,c=function(a,b,c){return j.call(e(a),c)}):(c.call(a,f),c=null));if(c)for(;l<m;l++)c(a[l],d,j?f.call(a[l],l,c(a[l],d)):f,i);g=1}return g?a:k?c.call(a):m?c(a[0],d):h},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m,n=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?n(g):h==="function"&&(!a.unique||!p.has(g))&&c.push(g)},o=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,j=!0,m=k||0,k=0,l=c.length;for(;c&&m<l;m++)if(c[m].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}j=!1,c&&(a.once?e===!0?p.disable():c=[]:d&&d.length&&(e=d.shift(),p.fireWith(e[0],e[1])))},p={add:function(){if(c){var a=c.length;n(arguments),j?l=c.length:e&&e!==!0&&(k=a,o(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){j&&f<=l&&(l--,f<=m&&m--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&p.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(j?a.once||d.push([b,c]):(!a.once||!e)&&o(b,c));return this},fire:function(){p.fireWith(this,arguments);return this},fired:function(){return!!i}};return p};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p=c.createElement("div"),q=c.documentElement;p.setAttribute("className","t"),p.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="<div "+n+"display:block;'><div style='"+t+"0;display:block;overflow:hidden;'></div></div>"+"<table "+n+"' cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="<table><tr><td style='"+t+"0;display:none'></td><td>t</td></tr></table>",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="<div style='width:5px;'></div>",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h,i,j=this[0],k=0,m=null;if(a===b){if(this.length){m=f.data(j);if(j.nodeType===1&&!f._data(j,"parsedAttrs")){g=j.attributes;for(i=g.length;k<i;k++)h=g[k].name,h.indexOf("data-")===0&&(h=f.camelCase(h.substring(5)),l(j,h,m[h]));f._data(j,"parsedAttrs",!0)}}return m}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!";return f.access(this,function(c){if(c===b){m=this.triggerHandler("getData"+e,[d[0]]),m===b&&j&&(m=f.data(j,a),m=l(j,a,m));return m===b&&d[1]?this.data(d[0]):m}d[1]=c,this.each(function(){var b=f(this);b.triggerHandler("setData"+e,d),f.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length<d)return f.queue(this[0],a);return c===b?this:this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise(c)}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,f.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i<g;i++)e=d[i],e&&(c=f.propFix[e]||e,h=u.test(e),h||f.attr(a,e,""),a.removeAttribute(v?e:c),h&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0,coords:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(
+a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:g&&G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||i.preDispatch.call(this,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(m=c.target;m!=this;m=m.parentNode||this)if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;k<e;k++)s=d[k],t=s.selector,p[t]===b&&(p[t]=s.quick?H(m,s.quick):n.is(t)),p[t]&&r.push(s);r.length&&j.push({elem:m,matches:r})}}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){q=j[k],c.currentTarget=q.elem;for(l=0;l<q.matches.length&&!c.isImmediatePropagationStopped();l++){s=q.matches[l];if(h||!c.namespace&&!s.namespace||c.namespace_re&&c.namespace_re.test(s.namespace))c.data=s.data,c.handleObj=s,o=((f.event.special[s.origType]||{}).handle||s.handler).apply(q.elem,g),o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()))}}i.postDispatch&&i.postDispatch.call(this,c);return c.result}},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),d._submit_attached=!0)})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9||d===11){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.globalPOS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")[\\s/>]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f
+.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(f.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(g){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,function(a,b){b.src?f.ajax({type:"GET",global:!1,url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1></$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]==="<table>"&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i<u;i++)bn(l[i]);else bn(l);l.nodeType?j.push(l):j=f.merge(j,l)}if(d){g=function(a){return!a.type||be.test(a.type)};for(k=0;j[k];k++){h=j[k];if(e&&f.nodeName(h,"script")&&(!h.type||be.test(h.type)))e.push(h.parentNode?h.parentNode.removeChild(h):h);else{if(h.nodeType===1){var v=f.grep(h.getElementsByTagName("script"),g);j.splice.apply(j,[k+1,0].concat(v))}d.appendChild(h)}}}return j},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bp=/alpha\([^)]*\)/i,bq=/opacity=([^)]*)/,br=/([A-Z]|^ms)/g,bs=/^[\-+]?(?:\d*\.)?\d+$/i,bt=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,bu=/^([\-+])=([\-+.\de]+)/,bv=/^margin/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Top","Right","Bottom","Left"],by,bz,bA;f.fn.css=function(a,c){return f.access(this,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)},a,c,arguments.length>1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),(e===""&&f.css(d,"display")==="none"||!f.contains(d.ownerDocument.documentElement,d))&&f._data(d,"olddisplay",cu(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(ct("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(ct("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o,p,q;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]);if((k=f.cssHooks[g])&&"expand"in k){l=k.expand(a[g]),delete a[g];for(i in l)i in a||(a[i]=l[i])}}for(g in a){h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cu(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cm.test(h)?(q=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),q?(f._data(this,"toggle"+i,q==="show"?"hide":"show"),j[q]()):j[h]()):(m=cn.exec(h),n=j.cur(),m?(o=parseFloat(m[2]),p=m[3]||(f.cssNumber[i]?"":"px"),p!=="px"&&(f.style(this,i,(o||1)+p),n=(o||1)/j.cur()*n,f.style(this,i,n+p)),m[1]&&(o=(m[1]==="-="?-1:1)*o+n),j.custom(n,o,p)):j.custom(n,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:ct("show",1),slideUp:ct("hide",1),slideToggle:ct("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a){return a},swing:function(a){return-Math.cos(a*Math.PI)/2+.5}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cq||cr(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){f._data(e.elem,"fxshow"+e.prop)===b&&(e.options.hide?f._data(e.elem,"fxshow"+e.prop,e.start):e.options.show&&f._data(e.elem,"fxshow"+e.prop,e.end))},h()&&f.timers.push(h)&&!co&&(co=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cq||cr(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(cp.concat.apply([],cp),function(a,b){b.indexOf("margin")&&(f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)})}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cv,cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?cv=function(a,b,c,d){try{d=a.getBoundingClientRect()}catch(e){}if(!d||!f.contains(c,a))return d?{top:d.top,left:d.left}:{top:0,left:0};var g=b.body,h=cy(b),i=c.clientTop||g.clientTop||0,j=c.clientLeft||g.clientLeft||0,k=h.pageYOffset||f.support.boxModel&&c.scrollTop||g.scrollTop,l=h.pageXOffset||f.support.boxModel&&c.scrollLeft||g.scrollLeft,m=d.top+k-i,n=d.left+l-j;return{top:m,left:n}}:cv=function(a,b,c){var d,e=a.offsetParent,g=a,h=b.body,i=b.defaultView,j=i?i.getComputedStyle(a,null):a.currentStyle,k=a.offsetTop,l=a.offsetLeft;while((a=a.parentNode)&&a!==h&&a!==c){if(f.support.fixedPosition&&j.position==="fixed")break;d=i?i.getComputedStyle(a,null):a.currentStyle,k-=a.scrollTop,l-=a.scrollLeft,a===e&&(k+=a.offsetTop,l+=a.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(a.nodeName))&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),g=e,e=a.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),j=d}if(j.position==="relative"||j.position==="static")k+=h.offsetTop,l+=h.offsetLeft;f.support.fixedPosition&&j.position==="fixed"&&(k+=Math.max(c.scrollTop,h.scrollTop),l+=Math.max(c.scrollLeft,h.scrollLeft));return{top:k,left:l}},f.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){f.offset.setOffset(this,a,b)});var c=this[0],d=c&&c.ownerDocument;if(!d)return null;if(c===d.body)return f.offset.bodyOffset(c);return cv(c,d,d.documentElement)},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/lib/underscore-min.js b/src/ext/floodlight/src/main/resources/web/lib/underscore-min.js
new file mode 100644
index 0000000..5a0cb3b
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/underscore-min.js
@@ -0,0 +1,32 @@
+// Underscore.js 1.3.3
+// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the MIT license.
+// Portions of Underscore are inspired or borrowed from Prototype,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+// http://documentcloud.github.com/underscore
+(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
+c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
+g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
+c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
+a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
+c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
+a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
+function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
+(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
+j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
+0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
+e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
+i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
+1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
+i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
+g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
+return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
+c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
+function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
+b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
+b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
+function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
+u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
+b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
+this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/flow-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/flow-list-item.html
new file mode 100644
index 0000000..7c099c3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/flow-list-item.html
@@ -0,0 +1 @@
+        <td><%= cookie %></td><td><%= priority %></td><td><%= matchHTML %></td><td><%= actionText %></td><td><%= packetCount %></td><td><%= byteCount %></td><td><%= durationSeconds %> s</td><td><%= idleTimeout %> s</td>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/flow-list.html b/src/ext/floodlight/src/main/resources/web/tpl/flow-list.html
new file mode 100644
index 0000000..21a30cf
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/flow-list.html
@@ -0,0 +1,19 @@
+<div class="page-header">
+    <h1>Flows (<%= nflows %>)</h1>
+</div>
+<table class="table table-striped flow-table">
+    <thead><tr><th>Cookie</th><th>Priority</th><th>Match</th><th>Action</th><th>Packets</th><th>Bytes</th><th>Age</th><th>Timeout</th></tr></thead>
+    <tbody>
+        <!-- flows will be inserted here by FlowListView:render -->
+    </tbody>
+</table>
+<!-- TODO implement pagination -->
+<!-- 
+<div class="pagination pagination-right"><ul>
+    <li><a href="">&larr;</a></li>
+    <li class="active"><a href="">1</a></li>
+    <li><a href="">2</a></li>
+    <li><a href="">&rarr;</a>
+</ul></div>
+ -->
+ 
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/header.html b/src/ext/floodlight/src/main/resources/web/tpl/header.html
new file mode 100644
index 0000000..78045ef
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/header.html
@@ -0,0 +1,26 @@
+<div class="navbar navbar-fixed-top">
+    <div class="navbar-inner">
+        <div class="container">
+            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </a>
+            <img src="img/floodlight.png" style="float:left;">
+
+            <div class="nav-collapse">
+                <ul class="nav">
+                    <li><a href="/">Dashboard</a></li>
+                    <li><a href="/topology">Topology</a></li>
+                    <li><a href="/switches">Switches</a></li>
+                    <li><a href="/hosts">Hosts</a></li>
+                    <!-- <li><a href="/vlans">VLANs</a></li> -->
+                </ul>
+                <form id="searchForm" class="navbar-search pull-right dropdown">
+                    <input id="searchText" type="text" class="search-query dropdown-toggle"
+                           placeholder="Search (try an IP or MAC address)">
+                </form>
+            </div> <!--/.nav-collapse -->
+        </div>
+    </div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/home.html b/src/ext/floodlight/src/main/resources/web/tpl/home.html
new file mode 100644
index 0000000..6abf494
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/home.html
@@ -0,0 +1,7 @@
+<div id="controller-status"></div>
+
+<div id="switch-list"></div>
+
+<div id="host-list"></div>
+
+<!--  <div id="vlan-list"></div> -->
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/host-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/host-list-item.html
new file mode 100644
index 0000000..169477e
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/host-list-item.html
@@ -0,0 +1 @@
+        <td><a href="/host/<%= mac %>"><%= mac %></a></td><!-- <td><%= vlan %></td> --><td><%= ipv4 %></td><td><%= swport %></td><td><%= lastSeen %></td>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/host-list.html b/src/ext/floodlight/src/main/resources/web/tpl/host-list.html
new file mode 100644
index 0000000..bc1f364
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/host-list.html
@@ -0,0 +1,23 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+    <h1>Hosts (<%= nhosts %>)</h1>
+</div>
+<table class="table table-striped host-table">
+    <thead><tr><th>MAC Address</th><!-- <th>VLAN</th> --><th>IP Address</th><th>Switch Port</th><th>Last Seen</th>
+           </tr></thead>
+    <tbody>
+        <!-- hosts will be inserted here by HostListView.render -->
+    </tbody>
+</table>
+<!-- TODO implement pagination -->
+<!--
+<div class="pagination pagination-right"><ul>
+    <li><a href="">&larr;</a></li>
+    <li class="active"><a href="">1</a></li>
+    <li><a href="">2</a></li>
+    <li><a href="">&rarr;</a>
+</ul></div>
+-->
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/host.html b/src/ext/floodlight/src/main/resources/web/tpl/host.html
new file mode 100644
index 0000000..985940f
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/host.html
@@ -0,0 +1,11 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+    <h1>Host <%= id %></h1>
+</div>
+<p>
+<!-- VLAN: <a href="/vlan/<%= vlan %>"><%= vlan %></a><br> -->
+IP addresses: <%= ipv4 %><br>
+Attachment points: <%= swport %><br>
+Last seen: <%= lastSeen %>
+</p>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/port-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/port-list-item.html
new file mode 100644
index 0000000..bfc1978
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/port-list-item.html
@@ -0,0 +1 @@
+        <td><a id="<%= portNumber %>"><%= name %></a></td><td><%= status %></td><td><%= transmitBytes %></td><td><%= receiveBytes %></td><td><%= transmitPackets %></td><td><%= receivePackets %></td><td><%= dropped %></td><td><%= errors %></td>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/port-list.html b/src/ext/floodlight/src/main/resources/web/tpl/port-list.html
new file mode 100644
index 0000000..9ceb6c3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/port-list.html
@@ -0,0 +1,19 @@
+<div class="page-header">
+    <h1>Ports (<%= nports %>)</h1>
+</div>
+<table class="table table-striped port-table">
+    <thead><tr><th>#</th><th>Link Status</th><th>TX Bytes</th><th>RX Bytes</th><th>TX Pkts</th><th>RX Pkts</th><th>Dropped</th><th>Errors</th></tr></thead>
+    <tbody>
+        <!-- ports will be inserted here by PortListView:render -->
+    </tbody>
+</table>
+<!-- TODO implement pagination -->
+<!--
+<div class="pagination pagination-right"><ul>
+    <li><a href="">&larr;</a></li>
+    <li class="active"><a href="">1</a></li>
+    <li><a href="">2</a></li>
+    <li><a href="">&rarr;</a>
+</ul></div>
+-->
+ 
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/status.html b/src/ext/floodlight/src/main/resources/web/tpl/status.html
new file mode 100644
index 0000000..5a76869
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/status.html
@@ -0,0 +1,13 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+    <h1>Controller Status</h1>
+</div>
+<table class="status-table">
+    <tr><td class="status-head">Hostname:<td><%= host %>:<%= ofport %></td></tr>
+    <tr><td class="status-head">Healthy:<td><%= healthy %></td>
+    <tr><td class="status-head">Uptime:<td><%= uptime %><td>
+    <tr><td class="status-head">JVM memory bloat:<td><%= free %> free out of <%= total %></td>
+    <Tr><td class="status-head">Modules loaded:<td><%= moduleText %></td>
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/switch-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/switch-list-item.html
new file mode 100644
index 0000000..7ce0262
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/switch-list-item.html
@@ -0,0 +1 @@
+        <td><a href="/switch/<%= id %>"><%= id %></a></td><td><%= inetAddress %></td><td><%= manufacturerDescription %><td><%= packetCount %></td><td><%= byteCount %></td><td><%= flowCount %></td><td><%= connectedSince %></td>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/switch-list.html b/src/ext/floodlight/src/main/resources/web/tpl/switch-list.html
new file mode 100644
index 0000000..e7dac09
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/switch-list.html
@@ -0,0 +1,22 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+    <h1>Switches (<%= nswitches %>)</h1>
+</div>
+<table class="table table-striped switch-table">
+    <thead><tr><th>DPID</th><th>IP Address</th><th>Vendor</th><th>Packets</th><th>Bytes</th><th>Flows</th><th>Connected Since</th></tr></thead>
+    <tbody>
+        <!-- switches will be inserted here by SwitchListView:render -->
+    </tbody>
+</table>
+<!-- TODO implement pagination -->
+<!--
+<div class="pagination pagination-right"><ul>
+    <li><a href="">&larr;</a></li>
+    <li class="active"><a href="">1</a></li>
+    <li><a href="">2</a></li>
+    <li><a href="">&rarr;</a>
+</ul></div>
+-->
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/switch.html b/src/ext/floodlight/src/main/resources/web/tpl/switch.html
new file mode 100644
index 0000000..af89797
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/switch.html
@@ -0,0 +1,18 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+    <h1>Switch <%= id %> <%= inetAddress %></h1>
+</div>
+<p>Connected since <%= connectedSince %><br>
+<%= manufacturerDescription %><br>
+<%= hardwareDescription %><br>
+<%= softwareDescription %><br>
+S/N: <%= serialNumber %><br>
+</p>
+
+<div id="port-list"></div> <!-- TODO would be nice to make this collapsible -->
+
+<div id="flow-list"></div>
+
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/topology.html b/src/ext/floodlight/src/main/resources/web/tpl/topology.html
new file mode 100644
index 0000000..ce77cc7
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/topology.html
@@ -0,0 +1,8 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+    <h1>Network Topology</h1>
+</div>
+<div id="topology-graph"></div>
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/vlan-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/vlan-list-item.html
new file mode 100644
index 0000000..55a2f35
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/vlan-list-item.html
@@ -0,0 +1 @@
+        <tr><td><a href="/vlan/<%= id %>"><%= id %></a></td><td><%= name %></td><td><%= nhosts %></td></tr>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/vlan.html b/src/ext/floodlight/src/main/resources/web/tpl/vlan.html
new file mode 100644
index 0000000..edcd408
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/vlan.html
@@ -0,0 +1 @@
+<p>A VLAN probably has a list of ports and hosts.</p>
diff --git a/src/ext/floodlight/src/main/thrift/packetstreamer.thrift b/src/ext/floodlight/src/main/thrift/packetstreamer.thrift
new file mode 100644
index 0000000..827dd85
--- /dev/null
+++ b/src/ext/floodlight/src/main/thrift/packetstreamer.thrift
@@ -0,0 +1,88 @@
+#
+# Interface definition for packetstreamer service
+#
+
+namespace java net.floodlightcontroller.packetstreamer.thrift 
+namespace cpp net.floodlightcontroller.packetstreamer
+namespace py packetstreamer
+namespace php packetstreamer
+namespace perl packetstreamer
+
+const string VERSION = "0.1.0"
+
+#
+# data structures
+#
+
+/**
+ * OFMessage type
+ **/
+enum OFMessageType {
+  HELLO = 0,
+  ERROR = 1,
+  ECHO_REQUEST = 2,
+  ECHO_REPLY = 3,
+  VENDOR = 4,
+  FEATURES_REQUEST = 5,
+  FEATURES_REPLY = 6,
+  GET_CONFIG_REQUEST = 7,
+  GET_CONFIG_REPLY = 8,
+  SET_CONFIG = 9,
+  PACKET_IN = 10,
+  FLOW_REMOVED = 11,
+  PORT_STATUS = 12,
+  PACKET_OUT = 13,
+  FLOW_MOD = 14,
+  PORT_MOD = 15,
+  STATS_REQUEST = 16,
+  STATS_REPLY = 17,
+  BARRIER_REQUEST = 18,
+  BARRIER_REPLY = 19,
+}
+
+/**
+ * A struct that defines switch port tuple
+ */
+struct SwitchPortTuple {
+  1: i64 dpid,
+  2: i16 port,
+}
+
+struct Packet {
+  1: OFMessageType messageType,
+  2: SwitchPortTuple swPortTuple,
+  3: binary data,
+}
+
+struct Message {
+  1: list<string> sessionIDs,
+  2: Packet packet,
+}
+
+/**
+ * Packetstreamer API
+ */
+service PacketStreamer {
+
+   /**
+    * Synchronous method to get packets for a given sessionid
+    */
+   list<binary> getPackets(1:string sessionid),
+
+   /**
+    * Synchronous method to publish a packet.
+    * It ensure the order that the packets are pushed
+    */
+   i32 pushMessageSync(1:Message packet),
+
+   /** 
+    * Asynchronous method to publish a packet.
+    * Order is not guaranteed.
+    */
+   oneway void pushMessageAsync(1:Message packet)
+
+   /** 
+    * Terminate a session
+    */
+   void terminateSession(1:string sessionid)
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java
new file mode 100644
index 0000000..fb063df
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java
@@ -0,0 +1,1250 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.internal;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.FloodlightProvider;
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFMessageFilterManagerService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IListener.Command;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.OFMessageFilterManager;
+import net.floodlightcontroller.core.internal.Controller.IUpdate;
+import net.floodlightcontroller.core.internal.Controller.SwitchUpdate;
+import net.floodlightcontroller.core.internal.Controller.SwitchUpdateType;
+import net.floodlightcontroller.core.internal.OFChannelState.HandshakeState;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.counter.CounterStore;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.packet.ARP;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.perfmon.IPktInProcessingTimeService;
+import net.floodlightcontroller.perfmon.PktInProcessingTime;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.jboss.netty.channel.Channel;
+import org.junit.Test;
+import org.openflow.protocol.OFError;
+import org.openflow.protocol.OFError.OFBadRequestCode;
+import org.openflow.protocol.OFError.OFErrorType;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFPortStatus;
+import org.openflow.protocol.OFStatisticsReply;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.protocol.OFPortStatus.OFPortReason;
+import org.openflow.protocol.OFVendor;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.statistics.OFFlowStatisticsReply;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.util.HexString;
+import org.openflow.vendor.nicira.OFNiciraVendorData;
+import org.openflow.vendor.nicira.OFRoleReplyVendorData;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class ControllerTest extends FloodlightTestCase {
+   
+    private Controller controller;
+    private MockThreadPoolService tp;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        
+        FloodlightProvider cm = new FloodlightProvider();
+        controller = (Controller)cm.getServiceImpls().get(IFloodlightProviderService.class);
+        fmc.addService(IFloodlightProviderService.class, controller);
+        
+        MemoryStorageSource memstorage = new MemoryStorageSource();
+        fmc.addService(IStorageSourceService.class, memstorage);
+        
+        RestApiServer restApi = new RestApiServer();
+        fmc.addService(IRestApiService.class, restApi);
+        
+        CounterStore cs = new CounterStore();
+        fmc.addService(ICounterStoreService.class, cs);
+        
+        PktInProcessingTime ppt = new PktInProcessingTime();
+        fmc.addService(IPktInProcessingTimeService.class, ppt);
+        
+        tp = new MockThreadPoolService();
+        fmc.addService(IThreadPoolService.class, tp);
+        
+        ppt.init(fmc);
+        restApi.init(fmc);
+        memstorage.init(fmc);
+        cm.init(fmc);
+        tp.init(fmc);
+        ppt.startUp(fmc);
+        restApi.startUp(fmc);
+        memstorage.startUp(fmc);
+        cm.startUp(fmc);
+        tp.startUp(fmc);
+    }
+
+    public Controller getController() {
+        return controller;
+    }
+
+    protected OFStatisticsReply getStatisticsReply(int transactionId,
+            int count, boolean moreReplies) {
+        OFStatisticsReply sr = new OFStatisticsReply();
+        sr.setXid(transactionId);
+        sr.setStatisticType(OFStatisticsType.FLOW);
+        List<OFStatistics> statistics = new ArrayList<OFStatistics>();
+        for (int i = 0; i < count; ++i) {
+            statistics.add(new OFFlowStatisticsReply());
+        }
+        sr.setStatistics(statistics);
+        if (moreReplies)
+            sr.setFlags((short) 1);
+        return sr;
+    }
+    
+    /* Set the mock expectations for sw when sw is passed to addSwitch */
+    protected void setupSwitchForAddSwitch(IOFSwitch sw, long dpid) {
+        String dpidString = HexString.toHexString(dpid);
+                
+        expect(sw.getId()).andReturn(dpid).anyTimes();
+        expect(sw.getStringId()).andReturn(dpidString).anyTimes();
+        expect(sw.getConnectedSince()).andReturn(new Date());
+        Channel channel = createMock(Channel.class);
+        expect(sw.getChannel()).andReturn(channel);
+        expect(channel.getRemoteAddress()).andReturn(null);
+
+        expect(sw.getCapabilities()).andReturn(0).anyTimes();
+        expect(sw.getBuffers()).andReturn(0).anyTimes();
+        expect(sw.getTables()).andReturn((byte)0).anyTimes();
+        expect(sw.getActions()).andReturn(0).anyTimes();
+        expect(sw.getPorts()).andReturn(new ArrayList<OFPhysicalPort>()).anyTimes();
+    }
+    
+    /**
+     * Run the controller's main loop so that updates are processed
+     */
+    protected class ControllerRunThread extends Thread {
+        public void run() {
+            controller.openFlowPort = 0; // Don't listen
+            controller.run();
+        }
+    }
+
+    /**
+     * Verify that a listener that throws an exception halts further
+     * execution, and verify that the Commands STOP and CONTINUE are honored.
+     * @throws Exception
+     */
+    @Test
+    public void testHandleMessages() throws Exception {
+        Controller controller = getController();
+        controller.removeOFMessageListeners(OFType.PACKET_IN);
+
+        IOFSwitch sw = createMock(IOFSwitch.class);
+        expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+
+        // Build our test packet
+        IPacket testPacket = new Ethernet()
+        .setSourceMACAddress("00:44:33:22:11:00")
+        .setDestinationMACAddress("00:11:22:33:44:55")
+        .setEtherType(Ethernet.TYPE_ARP)
+        .setPayload(
+                new ARP()
+                .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                .setProtocolType(ARP.PROTO_TYPE_IP)
+                .setHardwareAddressLength((byte) 6)
+                .setProtocolAddressLength((byte) 4)
+                .setOpCode(ARP.OP_REPLY)
+                .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00"))
+                .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+                .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+                .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+        byte[] testPacketSerialized = testPacket.serialize();
+
+        // Build the PacketIn        
+        OFPacketIn pi = ((OFPacketIn) new BasicFactory().getMessage(OFType.PACKET_IN))
+                .setBufferId(-1)
+                .setInPort((short) 1)
+                .setPacketData(testPacketSerialized)
+                .setReason(OFPacketInReason.NO_MATCH)
+                .setTotalLength((short) testPacketSerialized.length);
+
+        IOFMessageListener test1 = createMock(IOFMessageListener.class);
+        expect(test1.getName()).andReturn("test1").anyTimes();
+        expect(test1.isCallbackOrderingPrereq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes();
+        expect(test1.isCallbackOrderingPostreq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes();
+        expect(test1.receive(eq(sw), eq(pi), isA(FloodlightContext.class))).andThrow(new RuntimeException("This is NOT an error! We are testing exception catching."));
+        IOFMessageListener test2 = createMock(IOFMessageListener.class);
+        expect(test2.getName()).andReturn("test2").anyTimes();
+        expect(test2.isCallbackOrderingPrereq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes();
+        expect(test2.isCallbackOrderingPostreq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes();
+        // expect no calls to test2.receive() since test1.receive() threw an exception
+
+        replay(test1, test2, sw);
+        controller.addOFMessageListener(OFType.PACKET_IN, test1);
+        controller.addOFMessageListener(OFType.PACKET_IN, test2);
+        try {
+            controller.handleMessage(sw, pi, null);
+        } catch (RuntimeException e) {
+            assertEquals(e.getMessage().startsWith("This is NOT an error!"), true);
+        }
+        verify(test1, test2, sw);
+
+        // verify STOP works
+        reset(test1, test2, sw);
+        expect(test1.receive(eq(sw), eq(pi), isA(FloodlightContext.class))).andReturn(Command.STOP);       
+        //expect(test1.getId()).andReturn(0).anyTimes();
+        expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+        replay(test1, test2, sw);
+        controller.handleMessage(sw, pi, null);
+        verify(test1, test2, sw);
+    }
+
+    public class FutureFetcher<E> implements Runnable {
+        public E value;
+        public Future<E> future;
+
+        public FutureFetcher(Future<E> future) {
+            this.future = future;
+        }
+
+        @Override
+        public void run() {
+            try {
+                value = future.get();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
+         * @return the value
+         */
+        public E getValue() {
+            return value;
+        }
+
+        /**
+         * @return the future
+         */
+        public Future<E> getFuture() {
+            return future;
+        }
+    }
+
+    /**
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void testOFStatisticsFuture() throws Exception {
+        // Test for a single stats reply
+        IOFSwitch sw = createMock(IOFSwitch.class);
+        sw.cancelStatisticsReply(1);
+        OFStatisticsFuture sf = new OFStatisticsFuture(tp, sw, 1);
+
+        replay(sw);
+        List<OFStatistics> stats;
+        FutureFetcher<List<OFStatistics>> ff = new FutureFetcher<List<OFStatistics>>(sf);
+        Thread t = new Thread(ff);
+        t.start();
+        sf.deliverFuture(sw, getStatisticsReply(1, 10, false));
+
+        t.join();
+        stats = ff.getValue();
+        verify(sw);
+        assertEquals(10, stats.size());
+
+        // Test multiple stats replies
+        reset(sw);
+        sw.cancelStatisticsReply(1);
+
+        sf = new OFStatisticsFuture(tp, sw, 1);
+
+        replay(sw);
+        ff = new FutureFetcher<List<OFStatistics>>(sf);
+        t = new Thread(ff);
+        t.start();
+        sf.deliverFuture(sw, getStatisticsReply(1, 10, true));
+        sf.deliverFuture(sw, getStatisticsReply(1, 5, false));
+        t.join();
+
+        stats = sf.get();
+        verify(sw);
+        assertEquals(15, stats.size());
+
+        // Test cancellation
+        reset(sw);
+        sw.cancelStatisticsReply(1);
+        sf = new OFStatisticsFuture(tp, sw, 1);
+
+        replay(sw);
+        ff = new FutureFetcher<List<OFStatistics>>(sf);
+        t = new Thread(ff);
+        t.start();
+        sf.cancel(true);
+        t.join();
+
+        stats = sf.get();
+        verify(sw);
+        assertEquals(0, stats.size());
+
+        // Test self timeout
+        reset(sw);
+        sw.cancelStatisticsReply(1);
+        sf = new OFStatisticsFuture(tp, sw, 1, 75, TimeUnit.MILLISECONDS);
+
+        replay(sw);
+        ff = new FutureFetcher<List<OFStatistics>>(sf);
+        t = new Thread(ff);
+        t.start();
+        t.join(2000);
+
+        stats = sf.get();
+        verify(sw);
+        assertEquals(0, stats.size());
+    }
+
+    @Test
+    public void testMessageFilterManager() throws Exception {
+        class MyOFMessageFilterManager extends OFMessageFilterManager {
+            public MyOFMessageFilterManager(int timer_interval) {
+                super();
+                TIMER_INTERVAL = timer_interval;
+            }
+        }
+        FloodlightModuleContext fmCntx = new FloodlightModuleContext();
+        MockFloodlightProvider mfp = new MockFloodlightProvider();
+        OFMessageFilterManager mfm = new MyOFMessageFilterManager(100);
+        MockThreadPoolService mtp = new MockThreadPoolService();
+        fmCntx.addService(IOFMessageFilterManagerService.class, mfm);
+        fmCntx.addService(IFloodlightProviderService.class, mfp);
+        fmCntx.addService(IThreadPoolService.class, mtp);
+        String sid = null;
+        
+        mfm.init(fmCntx);
+        mfm.startUp(fmCntx);
+
+        ConcurrentHashMap <String, String> filter;
+        int i;
+
+        //Adding the filter works -- adds up to the maximum filter size.
+        for(i=mfm.getMaxFilterSize(); i > 0; --i) {
+            filter = new ConcurrentHashMap<String,String>();
+            filter.put("mac", String.format("00:11:22:33:44:%d%d", i,i));
+            sid = mfm.setupFilter(null, filter, 60);
+            assertTrue(mfm.getNumberOfFilters() == mfm.getMaxFilterSize() - i +1);
+        }
+
+        // Add one more to see if you can't
+        filter = new ConcurrentHashMap<String,String>();
+        filter.put("mac", "mac2");
+        mfm.setupFilter(null, filter, 10*1000);
+
+        assertTrue(mfm.getNumberOfFilters() == mfm.getMaxFilterSize());
+
+        // Deleting the filter works.
+        mfm.setupFilter(sid, null, -1);        
+        assertTrue(mfm.getNumberOfFilters() == mfm.getMaxFilterSize()-1);
+
+        // Creating mock switch to which we will send packet out and 
+        IOFSwitch sw = createMock(IOFSwitch.class);
+        expect(sw.getId()).andReturn(new Long(0));
+
+        // Mock Packet-in   
+        IPacket testPacket = new Ethernet()
+        .setSourceMACAddress("00:44:33:22:11:00")
+        .setDestinationMACAddress("00:11:22:33:44:55")
+        .setEtherType(Ethernet.TYPE_ARP)
+        .setPayload(
+                new ARP()
+                .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                .setProtocolType(ARP.PROTO_TYPE_IP)
+                .setHardwareAddressLength((byte) 6)
+                .setProtocolAddressLength((byte) 4)
+                .setOpCode(ARP.OP_REPLY)
+                .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00"))
+                .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+                .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+                .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+        byte[] testPacketSerialized = testPacket.serialize();
+
+        // Build the PacketIn        
+        OFPacketIn pi = ((OFPacketIn) new BasicFactory().getMessage(OFType.PACKET_IN))
+                .setBufferId(-1)
+                .setInPort((short) 1)
+                .setPacketData(testPacketSerialized)
+                .setReason(OFPacketInReason.NO_MATCH)
+                .setTotalLength((short) testPacketSerialized.length);
+
+        // Mock Packet-out
+        OFPacketOut packetOut =
+                (OFPacketOut) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
+        packetOut.setBufferId(pi.getBufferId())
+        .setInPort(pi.getInPort());
+        List<OFAction> poactions = new ArrayList<OFAction>();
+        poactions.add(new OFActionOutput(OFPort.OFPP_TABLE.getValue(), (short) 0));
+        packetOut.setActions(poactions)
+        .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
+        .setPacketData(testPacketSerialized)
+        .setLengthU(OFPacketOut.MINIMUM_LENGTH+packetOut.getActionsLength()+testPacketSerialized.length);
+
+        FloodlightContext cntx = new FloodlightContext();
+        IFloodlightProviderService.bcStore.put(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD, (Ethernet) testPacket);
+
+
+        // Let's check the listeners.
+        List <IOFMessageListener> lm; 
+
+        // Check to see if all the listeners are active.
+        lm = mfp.getListeners().get(OFType.PACKET_OUT);
+        assertTrue(lm.size() == 1);
+        assertTrue(lm.get(0).equals(mfm));
+
+        lm = mfp.getListeners().get(OFType.FLOW_MOD);
+        assertTrue(lm.size() == 1);
+        assertTrue(lm.get(0).equals(mfm));
+
+        lm = mfp.getListeners().get(OFType.PACKET_IN);
+        assertTrue(lm.size() == 1);
+        assertTrue(lm.get(0).equals(mfm));
+
+        HashSet<String> matchedFilters;        
+
+        // Send a packet in and check if it matches a filter.
+        matchedFilters = mfm.getMatchedFilters(pi, cntx);
+        assertTrue(matchedFilters.size() == 1);
+
+        // Send a packet out and check if it matches a filter
+        matchedFilters = mfm.getMatchedFilters(packetOut, cntx);
+        assertTrue(matchedFilters.size() == 1);
+
+        // Wait for all filters to be timed out.
+        Thread.sleep(150);
+        assertEquals(0, mfm.getNumberOfFilters());
+    }
+
+    @Test
+    public void testAddSwitch() throws Exception {
+        controller.activeSwitches = new ConcurrentHashMap<Long, IOFSwitch>();
+
+        //OFSwitchImpl oldsw = createMock(OFSwitchImpl.class);
+        OFSwitchImpl oldsw = new OFSwitchImpl();
+        OFFeaturesReply featuresReply = new OFFeaturesReply();
+        featuresReply.setDatapathId(0L);
+        featuresReply.setPorts(new ArrayList<OFPhysicalPort>());
+        oldsw.setFeaturesReply(featuresReply);
+        //expect(oldsw.getId()).andReturn(0L).anyTimes();
+        //expect(oldsw.asyncRemoveSwitchLock()).andReturn(rwlock.writeLock()).anyTimes();
+        //oldsw.setConnected(false);
+        //expect(oldsw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+
+        Channel channel = createNiceMock(Channel.class);
+        //expect(oldsw.getChannel()).andReturn(channel);
+        oldsw.setChannel(channel);
+        expect(channel.close()).andReturn(null);
+
+        IOFSwitch newsw = createMock(IOFSwitch.class);
+        expect(newsw.getId()).andReturn(0L).anyTimes();
+        expect(newsw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+        expect(newsw.getConnectedSince()).andReturn(new Date());
+        Channel channel2 = createMock(Channel.class);
+        expect(newsw.getChannel()).andReturn(channel2);
+        expect(channel2.getRemoteAddress()).andReturn(null);
+        expect(newsw.getPorts()).andReturn(new ArrayList<OFPhysicalPort>());
+        expect(newsw.getCapabilities()).andReturn(0).anyTimes();
+        expect(newsw.getBuffers()).andReturn(0).anyTimes();
+        expect(newsw.getTables()).andReturn((byte)0).anyTimes();
+        expect(newsw.getActions()).andReturn(0).anyTimes();
+        controller.activeSwitches.put(0L, oldsw);
+        replay(newsw, channel, channel2);
+
+        controller.addSwitch(newsw);
+
+        verify(newsw, channel, channel2);
+    }
+    
+    @Test
+    public void testUpdateQueue() throws Exception {
+        class DummySwitchListener implements IOFSwitchListener {
+            public int nAdded;
+            public int nRemoved;
+            public int nPortChanged;
+            public DummySwitchListener() {
+                nAdded = 0;
+                nRemoved = 0;
+                nPortChanged = 0;
+            }
+            public synchronized void addedSwitch(IOFSwitch sw) {
+                nAdded++;
+                notifyAll();
+            }
+            public synchronized void removedSwitch(IOFSwitch sw) {
+                nRemoved++;
+                notifyAll();
+            }
+            public String getName() {
+                return "dummy";
+            }
+            @Override
+            public void switchPortChanged(Long switchId) {
+                nPortChanged++;
+                notifyAll();
+            }
+        }
+        DummySwitchListener switchListener = new DummySwitchListener();
+        IOFSwitch sw = createMock(IOFSwitch.class);
+        ControllerRunThread t = new ControllerRunThread();
+        t.start();
+        
+        controller.addOFSwitchListener(switchListener);
+        synchronized(switchListener) {
+            controller.updates.put(controller.new SwitchUpdate(sw,
+                                      Controller.SwitchUpdateType.ADDED));
+            switchListener.wait(500);
+            assertTrue("IOFSwitchListener.addedSwitch() was not called", 
+                    switchListener.nAdded == 1);
+            controller.updates.put(controller.new SwitchUpdate(sw, 
+                                      Controller.SwitchUpdateType.REMOVED));
+            switchListener.wait(500);
+            assertTrue("IOFSwitchListener.removedSwitch() was not called", 
+                    switchListener.nRemoved == 1);
+            controller.updates.put(controller.new SwitchUpdate(sw, 
+                                      Controller.SwitchUpdateType.PORTCHANGED));
+            switchListener.wait(500);
+            assertTrue("IOFSwitchListener.switchPortChanged() was not called", 
+                    switchListener.nPortChanged == 1);
+        }
+    }
+    
+
+    private Map<String,Object> getFakeControllerIPRow(String id, String controllerId, 
+            String type, int number, String discoveredIP ) {
+        HashMap<String, Object> row = new HashMap<String,Object>();
+        row.put(Controller.CONTROLLER_INTERFACE_ID, id);
+        row.put(Controller.CONTROLLER_INTERFACE_CONTROLLER_ID, controllerId);
+        row.put(Controller.CONTROLLER_INTERFACE_TYPE, type);
+        row.put(Controller.CONTROLLER_INTERFACE_NUMBER, number);
+        row.put(Controller.CONTROLLER_INTERFACE_DISCOVERED_IP, discoveredIP);
+        return row;
+    }
+
+    /**
+     * Test notifications for controller node IP changes. This requires
+     * synchronization between the main test thread and another thread 
+     * that runs Controller's main loop and takes / handles updates. We
+     * synchronize with wait(timeout) / notifyAll(). We check for the 
+     * expected condition after the wait returns. However, if wait returns
+     * due to the timeout (or due to spurious awaking) and the check fails we
+     * might just not have waited long enough. Using a long enough timeout
+     * mitigates this but we cannot get rid of the fundamental "issue". 
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void testControllerNodeIPChanges() throws Exception {
+        class DummyHAListener implements IHAListener {
+            public Map<String, String> curControllerNodeIPs;
+            public Map<String, String> addedControllerNodeIPs;
+            public Map<String, String> removedControllerNodeIPs;
+            public int nCalled;
+            
+            public DummyHAListener() {
+                this.nCalled = 0;
+            }
+                
+            @Override
+            public void roleChanged(Role oldRole, Role newRole) {
+                // ignore
+            }
+    
+            @Override
+            public synchronized void controllerNodeIPsChanged(
+                    Map<String, String> curControllerNodeIPs,
+                    Map<String, String> addedControllerNodeIPs,
+                    Map<String, String> removedControllerNodeIPs) {
+                this.curControllerNodeIPs = curControllerNodeIPs;
+                this.addedControllerNodeIPs = addedControllerNodeIPs;
+                this.removedControllerNodeIPs = removedControllerNodeIPs;
+                this.nCalled++;
+                notifyAll();
+            }
+            
+            public void do_assert(int nCalled,
+                    Map<String, String> curControllerNodeIPs,
+                    Map<String, String> addedControllerNodeIPs,
+                    Map<String, String> removedControllerNodeIPs) {
+                assertEquals("nCalled is not as expected", nCalled, this.nCalled);
+                assertEquals("curControllerNodeIPs is not as expected", 
+                        curControllerNodeIPs, this.curControllerNodeIPs);
+                assertEquals("addedControllerNodeIPs is not as expected", 
+                        addedControllerNodeIPs, this.addedControllerNodeIPs);
+                assertEquals("removedControllerNodeIPs is not as expected", 
+                        removedControllerNodeIPs, this.removedControllerNodeIPs);
+                
+            }
+        }
+        long waitTimeout = 250; // ms
+        DummyHAListener listener  = new DummyHAListener();
+        HashMap<String,String> expectedCurMap = new HashMap<String, String>();
+        HashMap<String,String> expectedAddedMap = new HashMap<String, String>();
+        HashMap<String,String> expectedRemovedMap = new HashMap<String, String>();
+        
+        controller.addHAListener(listener);
+        ControllerRunThread t = new ControllerRunThread();
+        t.start();
+        
+        synchronized(listener) {
+            // Insert a first entry
+            controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+                    getFakeControllerIPRow("row1", "c1", "Ethernet", 0, "1.1.1.1"));
+            expectedCurMap.clear();
+            expectedAddedMap.clear();
+            expectedRemovedMap.clear();
+            expectedCurMap.put("c1", "1.1.1.1");
+            expectedAddedMap.put("c1", "1.1.1.1");
+            listener.wait(waitTimeout);
+            listener.do_assert(1, expectedCurMap, expectedAddedMap, expectedRemovedMap);
+            
+            // Add an interface that we want to ignore. 
+            controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+                    getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2"));
+            listener.wait(waitTimeout); // TODO: do a different check. This call will have to wait for the timeout
+            assertTrue("controllerNodeIPsChanged() should not have been called here", 
+                    listener.nCalled == 1);
+
+            // Add another entry
+            controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+                    getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.2"));
+            expectedCurMap.clear();
+            expectedAddedMap.clear();
+            expectedRemovedMap.clear();
+            expectedCurMap.put("c1", "1.1.1.1");
+            expectedCurMap.put("c2", "2.2.2.2");
+            expectedAddedMap.put("c2", "2.2.2.2");
+            listener.wait(waitTimeout);
+            listener.do_assert(2, expectedCurMap, expectedAddedMap, expectedRemovedMap);
+
+
+            // Update an entry
+            controller.storageSource.updateRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+                    "row3", getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.3"));
+            expectedCurMap.clear();
+            expectedAddedMap.clear();
+            expectedRemovedMap.clear();
+            expectedCurMap.put("c1", "1.1.1.1");
+            expectedCurMap.put("c2", "2.2.2.3");
+            expectedAddedMap.put("c2", "2.2.2.3");
+            expectedRemovedMap.put("c2", "2.2.2.2");
+            listener.wait(waitTimeout);
+            listener.do_assert(3, expectedCurMap, expectedAddedMap, expectedRemovedMap);
+
+            // Delete an entry
+            controller.storageSource.deleteRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, 
+                    "row3");
+            expectedCurMap.clear();
+            expectedAddedMap.clear();
+            expectedRemovedMap.clear();
+            expectedCurMap.put("c1", "1.1.1.1");
+            expectedRemovedMap.put("c2", "2.2.2.3");
+            listener.wait(waitTimeout);
+            listener.do_assert(4, expectedCurMap, expectedAddedMap, expectedRemovedMap);
+        }
+    }
+    
+    @Test
+    public void testGetControllerNodeIPs() {
+        HashMap<String,String> expectedCurMap = new HashMap<String, String>();
+        
+        controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+                getFakeControllerIPRow("row1", "c1", "Ethernet", 0, "1.1.1.1"));
+        controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+                getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2"));
+        controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+                getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.2"));
+        expectedCurMap.put("c1", "1.1.1.1");
+        expectedCurMap.put("c2", "2.2.2.2");    
+        assertEquals("expectedControllerNodeIPs is not as expected", 
+                expectedCurMap, controller.getControllerNodeIPs());
+    }
+    
+    @Test
+    public void testSetRoleNull() {
+        try {
+            controller.setRole(null);
+            fail("Should have thrown an Exception");
+        }
+        catch (NullPointerException e) {
+            //exptected
+        }
+    }
+    
+    @Test 
+    public void testSetRole() {
+        controller.connectedSwitches.add(new OFSwitchImpl());
+        RoleChanger roleChanger = createMock(RoleChanger.class); 
+        roleChanger.submitRequest(controller.connectedSwitches, Role.SLAVE);
+        controller.roleChanger = roleChanger;
+        
+        assertEquals("Check that update queue is empty", 0, 
+                    controller.updates.size());
+        
+        replay(roleChanger);
+        controller.setRole(Role.SLAVE);
+        verify(roleChanger);
+        
+        Controller.IUpdate upd = controller.updates.poll();
+        assertNotNull("Check that update queue has an update", upd);
+        assertTrue("Check that update is HARoleUpdate", 
+                   upd instanceof Controller.HARoleUpdate);
+        Controller.HARoleUpdate roleUpd = (Controller.HARoleUpdate)upd;
+        assertSame(null, roleUpd.oldRole);
+        assertSame(Role.SLAVE, roleUpd.newRole);
+    }
+    
+    @Test
+    public void testCheckSwitchReady() {
+        OFChannelState state = new OFChannelState();
+        Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+        chdlr.sw = createMock(OFSwitchImpl.class);
+        
+        // Wrong current state 
+        // Should not go to READY
+        state.hsState = OFChannelState.HandshakeState.HELLO;
+        state.hasDescription = true;
+        state.hasGetConfigReply = true;
+        replay(chdlr.sw);  // nothing called on sw
+        chdlr.checkSwitchReady();
+        verify(chdlr.sw);
+        assertSame(OFChannelState.HandshakeState.HELLO, state.hsState);
+        reset(chdlr.sw);
+        
+        // Have only config reply
+        state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY;
+        state.hasDescription = false;
+        state.hasGetConfigReply = true;
+        replay(chdlr.sw); 
+        chdlr.checkSwitchReady();
+        verify(chdlr.sw);
+        assertSame(OFChannelState.HandshakeState.FEATURES_REPLY, state.hsState);
+        assertTrue(controller.connectedSwitches.isEmpty());
+        assertTrue(controller.activeSwitches.isEmpty());
+        reset(chdlr.sw);
+        
+        // Have only desc reply
+        state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY;
+        state.hasDescription = true;
+        state.hasGetConfigReply = false;
+        replay(chdlr.sw); 
+        chdlr.checkSwitchReady();
+        verify(chdlr.sw);
+        assertSame(OFChannelState.HandshakeState.FEATURES_REPLY, state.hsState);
+        assertTrue(controller.connectedSwitches.isEmpty());
+        assertTrue(controller.activeSwitches.isEmpty());
+        reset(chdlr.sw);
+        
+        //////////////////////////////////////////
+        // Finally, everything is right. Should advance to READY
+        //////////////////////////////////////////
+        controller.roleChanger = createMock(RoleChanger.class);
+        state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY;
+        state.hasDescription = true;
+        state.hasGetConfigReply = true;
+        // Role support disabled. Switch should be promoted to active switch
+        // list. 
+        setupSwitchForAddSwitch(chdlr.sw, 0L);
+        chdlr.sw.clearAllFlowMods();
+        replay(controller.roleChanger, chdlr.sw);
+        chdlr.checkSwitchReady();
+        verify(controller.roleChanger, chdlr.sw);
+        assertSame(OFChannelState.HandshakeState.READY, state.hsState);
+        assertSame(chdlr.sw, controller.activeSwitches.get(0L));
+        assertTrue(controller.connectedSwitches.contains(chdlr.sw));
+        assertTrue(state.firstRoleReplyReceived);
+        reset(chdlr.sw);
+        reset(controller.roleChanger);
+        controller.connectedSwitches.clear();
+        controller.activeSwitches.clear();
+        
+        
+        // Role support enabled. 
+        state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY;
+        controller.role = Role.MASTER;
+        Capture<Collection<OFSwitchImpl>> swListCapture = 
+                    new Capture<Collection<OFSwitchImpl>>();
+        controller.roleChanger.submitRequest(capture(swListCapture), 
+                    same(Role.MASTER));
+        replay(controller.roleChanger, chdlr.sw);
+        chdlr.checkSwitchReady();
+        verify(controller.roleChanger, chdlr.sw);
+        assertSame(OFChannelState.HandshakeState.READY, state.hsState);
+        assertTrue(controller.activeSwitches.isEmpty());
+        assertTrue(controller.connectedSwitches.contains(chdlr.sw));
+        assertTrue(state.firstRoleReplyReceived);
+        Collection<OFSwitchImpl> swList = swListCapture.getValue();
+        assertEquals(1, swList.size());
+        assertTrue("swList must contain this switch", swList.contains(chdlr.sw));
+    }
+
+    
+    @Test
+    public void testChannelDisconnected() throws Exception {
+        OFChannelState state = new OFChannelState();
+        state.hsState = OFChannelState.HandshakeState.READY;
+        Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+        chdlr.sw = createMock(OFSwitchImpl.class);
+        
+        // Switch is active 
+        expect(chdlr.sw.getId()).andReturn(0L).anyTimes();
+        expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:00")
+                    .anyTimes();
+        chdlr.sw.cancelAllStatisticsReplies();
+        chdlr.sw.setConnected(false);
+        expect(chdlr.sw.isConnected()).andReturn(true);
+        
+        controller.connectedSwitches.add(chdlr.sw);
+        controller.activeSwitches.put(0L, chdlr.sw);
+        
+        replay(chdlr.sw);
+        chdlr.channelDisconnected(null, null);
+        verify(chdlr.sw);
+        
+        // Switch is connected but not active
+        reset(chdlr.sw);
+        expect(chdlr.sw.getId()).andReturn(0L).anyTimes();
+        chdlr.sw.setConnected(false);
+        replay(chdlr.sw);
+        chdlr.channelDisconnected(null, null);
+        verify(chdlr.sw);
+        
+        // Not in ready state
+        state.hsState = HandshakeState.START;
+        reset(chdlr.sw);
+        replay(chdlr.sw);
+        chdlr.channelDisconnected(null, null);
+        verify(chdlr.sw);
+        
+        // Switch is null
+        state.hsState = HandshakeState.READY;
+        chdlr.sw = null;
+        chdlr.channelDisconnected(null, null);
+    }
+    
+    /*
+    @Test
+    public void testRoleChangeForSerialFailoverSwitch() throws Exception {
+        OFSwitchImpl newsw = createMock(OFSwitchImpl.class);
+        expect(newsw.getId()).andReturn(0L).anyTimes();
+        expect(newsw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+        Channel channel2 = createMock(Channel.class);
+        expect(newsw.getChannel()).andReturn(channel2);
+        
+        // newsw.role is null because the switch does not support
+        // role request messages
+        expect(newsw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+                        .andReturn(false);
+        // switch is connected 
+        controller.connectedSwitches.add(newsw);
+
+        // the switch should get disconnected when role is changed to SLAVE
+        expect(channel2.close()).andReturn(null);
+
+        replay(newsw, channel2);
+        controller.setRole(Role.SLAVE);
+        verify(newsw,  channel2);
+    }
+    */
+
+    @Test
+    public void testRoleNotSupportedError() throws Exception {
+        int xid = 424242;
+        OFChannelState state = new OFChannelState();
+        state.hsState = HandshakeState.READY;
+        Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+        chdlr.sw = createMock(OFSwitchImpl.class);
+        Channel ch = createMock(Channel.class);
+        
+        // the error returned when role request message is not supported by sw
+        OFError msg = new OFError();
+        msg.setType(OFType.ERROR);
+        msg.setXid(xid);
+        msg.setErrorType(OFErrorType.OFPET_BAD_REQUEST);
+        msg.setErrorCode(OFBadRequestCode.OFPBRC_BAD_VENDOR);
+        
+        // the switch connection should get disconnected when the controller is
+        // in SLAVE mode and the switch does not support role-request messages
+        state.firstRoleReplyReceived = false;
+        controller.role = Role.SLAVE;
+        expect(chdlr.sw.checkFirstPendingRoleRequestXid(xid)).andReturn(true);
+        chdlr.sw.deliverRoleRequestNotSupported(xid);
+        expect(chdlr.sw.getChannel()).andReturn(ch).anyTimes();
+        expect(ch.close()).andReturn(null);
+        
+        replay(ch, chdlr.sw);
+        chdlr.processOFMessage(msg);
+        verify(ch, chdlr.sw);
+        assertTrue("state.firstRoleReplyReceived must be true", 
+                   state.firstRoleReplyReceived);
+        assertTrue("activeSwitches must be empty",
+                   controller.activeSwitches.isEmpty());
+        reset(ch, chdlr.sw);
+              
+        
+        // a different error message - should also reject role request
+        msg.setErrorType(OFErrorType.OFPET_BAD_REQUEST);
+        msg.setErrorCode(OFBadRequestCode.OFPBRC_EPERM);
+        state.firstRoleReplyReceived = false;
+        controller.role = Role.SLAVE;
+        expect(chdlr.sw.checkFirstPendingRoleRequestXid(xid)).andReturn(true);
+        chdlr.sw.deliverRoleRequestNotSupported(xid);
+        expect(chdlr.sw.getChannel()).andReturn(ch).anyTimes();
+        expect(ch.close()).andReturn(null);
+        replay(ch, chdlr.sw);
+        
+        chdlr.processOFMessage(msg);
+        verify(ch, chdlr.sw);
+        assertTrue("state.firstRoleReplyReceived must be True even with EPERM",
+                   state.firstRoleReplyReceived);
+        assertTrue("activeSwitches must be empty", 
+                   controller.activeSwitches.isEmpty());
+        reset(ch, chdlr.sw);
+    
+        
+        // We are MASTER, the switch should be added to the list of active
+        // switches.
+        state.firstRoleReplyReceived = false;
+        controller.role = Role.MASTER;
+        expect(chdlr.sw.checkFirstPendingRoleRequestXid(xid)).andReturn(true);
+        chdlr.sw.deliverRoleRequestNotSupported(xid);
+        setupSwitchForAddSwitch(chdlr.sw, 0L);
+        chdlr.sw.clearAllFlowMods();
+        replay(ch, chdlr.sw);
+        
+        chdlr.processOFMessage(msg);
+        verify(ch, chdlr.sw);
+        assertTrue("state.firstRoleReplyReceived must be true", 
+                   state.firstRoleReplyReceived);
+        assertSame("activeSwitches must contain this switch",
+                   chdlr.sw, controller.activeSwitches.get(0L));
+        reset(ch, chdlr.sw);
+
+    }
+    
+    
+    @Test 
+    public void testVendorMessageUnknown() throws Exception {
+        // Check behavior with an unknown vendor id
+        OFChannelState state = new OFChannelState();
+        state.hsState = HandshakeState.READY;
+        Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+        OFVendor msg = new OFVendor();
+        msg.setVendor(0);
+        chdlr.processOFMessage(msg);
+    }
+    
+    
+    // Helper function.
+    protected Controller.OFChannelHandler getChannelHandlerForRoleReplyTest() {
+        OFChannelState state = new OFChannelState();
+        state.hsState = HandshakeState.READY;
+        Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+        chdlr.sw = createMock(OFSwitchImpl.class);
+        return chdlr;
+    }
+    
+    // Helper function
+    protected OFVendor getRoleReplyMsgForRoleReplyTest(int xid, int nicira_role) {
+        OFVendor msg = new OFVendor();
+        msg.setXid(xid);
+        msg.setVendor(OFNiciraVendorData.NX_VENDOR_ID);
+        OFRoleReplyVendorData roleReplyVendorData = 
+                new OFRoleReplyVendorData(OFRoleReplyVendorData.NXT_ROLE_REPLY);
+        msg.setVendorData(roleReplyVendorData);
+        roleReplyVendorData.setRole(nicira_role);
+        return msg;
+    }
+   
+    /** invalid role in role reply */
+    @Test 
+    public void testNiciraRoleReplyInvalidRole() 
+                    throws Exception {
+        int xid = 424242;
+        Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+        Channel ch = createMock(Channel.class);
+        expect(chdlr.sw.getChannel()).andReturn(ch);
+        expect(ch.close()).andReturn(null);
+        OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, 232323);
+        replay(chdlr.sw, ch);
+        chdlr.processOFMessage(msg);
+        verify(chdlr.sw, ch);
+    }
+    
+    /** First role reply message received: transition from slave to master */
+    @Test 
+    public void testNiciraRoleReplySlave2MasterFristTime() 
+                    throws Exception {
+        int xid = 424242;
+        Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+        OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid,
+                                       OFRoleReplyVendorData.NX_ROLE_MASTER);
+        
+        chdlr.sw.deliverRoleReply(xid, Role.MASTER);
+        expect(chdlr.sw.isActive()).andReturn(true);
+        setupSwitchForAddSwitch(chdlr.sw, 1L);
+        chdlr.sw.clearAllFlowMods();
+        chdlr.state.firstRoleReplyReceived = false;
+        replay(chdlr.sw);
+        chdlr.processOFMessage(msg);
+        verify(chdlr.sw);
+        assertTrue("state.firstRoleReplyReceived must be true", 
+                   chdlr.state.firstRoleReplyReceived);
+        assertSame("activeSwitches must contain this switch",
+                   chdlr.sw, controller.activeSwitches.get(1L));
+    }
+    
+    
+    /** Not first role reply message received: transition from slave to master */
+    @Test 
+    public void testNiciraRoleReplySlave2MasterNotFristTime() 
+                    throws Exception {
+        int xid = 424242;
+        Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+        OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid,
+                                       OFRoleReplyVendorData.NX_ROLE_MASTER);
+        
+        chdlr.sw.deliverRoleReply(xid, Role.MASTER);
+        expect(chdlr.sw.isActive()).andReturn(true);
+        setupSwitchForAddSwitch(chdlr.sw, 1L);
+        chdlr.state.firstRoleReplyReceived = true;
+        // Flow table shouldn't be wipe
+        replay(chdlr.sw);
+        chdlr.processOFMessage(msg);
+        verify(chdlr.sw);
+        assertTrue("state.firstRoleReplyReceived must be true", 
+                   chdlr.state.firstRoleReplyReceived);
+        assertSame("activeSwitches must contain this switch",
+                   chdlr.sw, controller.activeSwitches.get(1L));
+    }
+    
+    /** transition from slave to equal */
+    @Test 
+    public void testNiciraRoleReplySlave2Equal() 
+                    throws Exception {
+        int xid = 424242;
+        Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+        OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid,
+                                       OFRoleReplyVendorData.NX_ROLE_OTHER);
+        
+        chdlr.sw.deliverRoleReply(xid, Role.EQUAL);
+        expect(chdlr.sw.isActive()).andReturn(true);
+        setupSwitchForAddSwitch(chdlr.sw, 1L);
+        chdlr.sw.clearAllFlowMods();
+        chdlr.state.firstRoleReplyReceived = false;
+        replay(chdlr.sw);
+        chdlr.processOFMessage(msg);
+        verify(chdlr.sw);
+        assertTrue("state.firstRoleReplyReceived must be true", 
+                   chdlr.state.firstRoleReplyReceived);
+        assertSame("activeSwitches must contain this switch",
+                   chdlr.sw, controller.activeSwitches.get(1L));
+    };
+    
+    @Test
+    /** Slave2Slave transition ==> no change */
+    public void testNiciraRoleReplySlave2Slave() throws Exception{
+        int xid = 424242;
+        Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+        OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, 
+                                       OFRoleReplyVendorData.NX_ROLE_SLAVE);
+        
+        chdlr.sw.deliverRoleReply(xid, Role.SLAVE);
+        expect(chdlr.sw.getId()).andReturn(1L).anyTimes();
+        expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:01")
+                    .anyTimes();
+        expect(chdlr.sw.isActive()).andReturn(false);
+        // don't add switch to activeSwitches ==> slave2slave
+        chdlr.state.firstRoleReplyReceived = false;
+        replay(chdlr.sw);
+        chdlr.processOFMessage(msg);
+        verify(chdlr.sw);
+        assertTrue("state.firstRoleReplyReceived must be true", 
+                   chdlr.state.firstRoleReplyReceived);
+        assertTrue("activeSwitches must be empty", 
+                   controller.activeSwitches.isEmpty());
+    }
+    
+    @Test
+    /** Equal2Master transition ==> no change */
+    public void testNiciraRoleReplyEqual2Master() throws Exception{
+        int xid = 424242;
+        Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+        OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, 
+                                       OFRoleReplyVendorData.NX_ROLE_MASTER);
+        
+        chdlr.sw.deliverRoleReply(xid, Role.MASTER);
+        expect(chdlr.sw.getId()).andReturn(1L).anyTimes();
+        expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:01")
+                    .anyTimes();
+        expect(chdlr.sw.isActive()).andReturn(true);
+        controller.activeSwitches.put(1L, chdlr.sw);
+        chdlr.state.firstRoleReplyReceived = false;
+        replay(chdlr.sw);
+        chdlr.processOFMessage(msg);
+        verify(chdlr.sw);
+        assertTrue("state.firstRoleReplyReceived must be true", 
+                   chdlr.state.firstRoleReplyReceived);
+        assertSame("activeSwitches must contain this switch",
+                   chdlr.sw, controller.activeSwitches.get(1L));
+    }
+    
+    @Test 
+    public void testNiciraRoleReplyMaster2Slave() 
+                    throws Exception {
+        int xid = 424242;
+        Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+        OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, 
+                                       OFRoleReplyVendorData.NX_ROLE_SLAVE);
+        
+        chdlr.sw.deliverRoleReply(xid, Role.SLAVE);
+        expect(chdlr.sw.getId()).andReturn(1L).anyTimes();
+        expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:01")
+                    .anyTimes();
+        controller.activeSwitches.put(1L, chdlr.sw);
+        expect(chdlr.sw.isActive()).andReturn(false);
+        expect(chdlr.sw.isConnected()).andReturn(true);
+        chdlr.sw.cancelAllStatisticsReplies();
+        chdlr.state.firstRoleReplyReceived = false;
+        replay(chdlr.sw);
+        chdlr.processOFMessage(msg);
+        verify(chdlr.sw);
+        assertTrue("state.firstRoleReplyReceived must be true", 
+                   chdlr.state.firstRoleReplyReceived);
+        assertTrue("activeSwitches must be empty", 
+                   controller.activeSwitches.isEmpty());
+    }
+    
+    /**
+     * Tests that you can't remove a switch from the active
+     * switch list.
+     * @throws Exception
+     */
+    @Test
+    public void testRemoveActiveSwitch() {
+        IOFSwitch sw = EasyMock.createNiceMock(IOFSwitch.class);
+        boolean exceptionThrown = false;
+        expect(sw.getId()).andReturn(1L).anyTimes();
+        replay(sw);
+        getController().activeSwitches.put(sw.getId(), sw);
+        try {
+            getController().getSwitches().remove(1L);
+        } catch (UnsupportedOperationException e) {
+            exceptionThrown = true;
+        }
+        assertTrue(exceptionThrown);
+        verify(sw);
+    }
+    
+    public void verifyPortChangedUpdateInQueue(IOFSwitch sw) throws Exception {
+        assertEquals(1, controller.updates.size());
+        IUpdate update = controller.updates.take();
+        assertEquals(true, update instanceof SwitchUpdate);
+        SwitchUpdate swUpdate = (SwitchUpdate)update;
+        assertEquals(sw, swUpdate.sw);
+        assertEquals(SwitchUpdateType.PORTCHANGED, swUpdate.switchUpdateType);
+    }
+    
+    /*
+     * Test handlePortStatus()
+     * TODO: test correct updateStorage behavior!
+     */
+    @Test 
+    public void testHandlePortStatus() throws Exception {
+        IOFSwitch sw = createMock(IOFSwitch.class);
+        OFPhysicalPort port = new OFPhysicalPort();
+        port.setName("myPortName1");
+        port.setPortNumber((short)42);
+        
+        OFPortStatus ofps = new OFPortStatus();
+        ofps.setDesc(port);
+        
+        ofps.setReason((byte)OFPortReason.OFPPR_ADD.ordinal());
+        sw.setPort(port);
+        expectLastCall().once();
+        replay(sw);
+        controller.handlePortStatusMessage(sw, ofps, false);
+        verify(sw);
+        verifyPortChangedUpdateInQueue(sw);
+        reset(sw);
+        
+        ofps.setReason((byte)OFPortReason.OFPPR_MODIFY.ordinal());
+        sw.setPort(port);
+        expectLastCall().once();
+        replay(sw);
+        controller.handlePortStatusMessage(sw, ofps, false);
+        verify(sw);
+        verifyPortChangedUpdateInQueue(sw);
+        reset(sw);
+        
+        ofps.setReason((byte)OFPortReason.OFPPR_DELETE.ordinal());
+        sw.deletePort(port.getPortNumber());
+        expectLastCall().once();
+        replay(sw);
+        controller.handlePortStatusMessage(sw, ofps, false);
+        verify(sw);
+        verifyPortChangedUpdateInQueue(sw);
+        reset(sw);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java
new file mode 100644
index 0000000..758cd05
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java
@@ -0,0 +1,236 @@
+package net.floodlightcontroller.core.internal;
+
+import static org.easymock.EasyMock.*;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.List;
+
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.internal.OFSwitchImpl.PendingRoleRequestEntry;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+import org.easymock.Capture;
+import org.jboss.netty.channel.Channel;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFVendor;
+import org.openflow.protocol.vendor.OFVendorData;
+import org.openflow.vendor.nicira.OFNiciraVendorData;
+import org.openflow.vendor.nicira.OFRoleRequestVendorData;
+import org.openflow.vendor.nicira.OFRoleVendorData;
+
+public class OFSwitchImplTest extends FloodlightTestCase {
+    protected OFSwitchImpl sw;
+    
+    
+    @Before
+    public void setUp() throws Exception {
+        sw = new OFSwitchImpl();
+        Channel ch = createMock(Channel.class);
+        SocketAddress sa = new InetSocketAddress(42);
+        expect(ch.getRemoteAddress()).andReturn(sa).anyTimes();
+        sw.setChannel(ch);
+        MockFloodlightProvider floodlightProvider = new MockFloodlightProvider();
+        sw.setFloodlightProvider(floodlightProvider);
+    }
+
+    
+    public void doSendNxRoleRequest(Role role, int nx_role) throws Exception {
+        long cookie = System.nanoTime();
+        
+        // verify that the correct OFMessage is sent
+        Capture<List<OFMessage>> msgCapture = new Capture<List<OFMessage>>();
+        expect(sw.channel.write(capture(msgCapture))).andReturn(null);
+        replay(sw.channel);
+        int xid = sw.sendNxRoleRequest(role, cookie);
+        verify(sw.channel);
+        List<OFMessage> msgList = msgCapture.getValue();
+        assertEquals(1, msgList.size());
+        OFMessage msg = msgList.get(0);
+        assertEquals("Transaction Ids must match", xid, msg.getXid()); 
+        assertTrue("Message must be an OFVendor type", msg instanceof OFVendor);
+        assertEquals(OFType.VENDOR, msg.getType());
+        OFVendor vendorMsg = (OFVendor)msg;
+        assertEquals("Vendor message must be vendor Nicira",
+                     OFNiciraVendorData.NX_VENDOR_ID, vendorMsg.getVendor());
+        OFVendorData vendorData = vendorMsg.getVendorData();
+        assertTrue("Vendor Data must be an OFRoleRequestVendorData",
+                     vendorData instanceof OFRoleRequestVendorData);
+        OFRoleRequestVendorData roleRequest = (OFRoleRequestVendorData)vendorData;
+        assertEquals(nx_role, roleRequest.getRole());
+        
+        // Now verify that we've added the pending request correctly
+        // to the pending queue
+        assertEquals(1, sw.pendingRoleRequests.size());
+        PendingRoleRequestEntry pendingRoleRequest = sw.pendingRoleRequests.poll();
+        assertEquals(msg.getXid(), pendingRoleRequest.xid);
+        assertEquals(role, pendingRoleRequest.role);
+        assertEquals(cookie, pendingRoleRequest.cookie);
+        reset(sw.channel);
+    }
+    
+    @Test
+    public void testSendNxRoleRequest() throws Exception {
+        doSendNxRoleRequest(Role.MASTER, OFRoleVendorData.NX_ROLE_MASTER);
+        doSendNxRoleRequest(Role.SLAVE, OFRoleVendorData.NX_ROLE_SLAVE);
+        doSendNxRoleRequest(Role.EQUAL, OFRoleVendorData.NX_ROLE_OTHER);
+    }
+    
+    
+    @Test
+    public void testDeliverRoleReplyOk() {
+        // test normal case
+        PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+                            (int)System.currentTimeMillis(),  // arbitrary xid
+                            Role.MASTER,
+                            System.nanoTime() // arbitrary cookie
+                            );
+        sw.pendingRoleRequests.add(pending);
+        replay(sw.channel);
+        sw.deliverRoleReply(pending.xid, pending.role);
+        verify(sw.channel);
+        assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+        assertEquals(pending.role, sw.role);
+        assertEquals(0, sw.pendingRoleRequests.size());
+    }
+    
+    @Test
+    public void testDeliverRoleReplyOkRepeated() {
+        // test normal case. Not the first role reply
+        PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+                            (int)System.currentTimeMillis(),  // arbitrary xid
+                            Role.MASTER,
+                            System.nanoTime() // arbitrary cookie
+                            );
+        sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true);
+        sw.pendingRoleRequests.add(pending);
+        replay(sw.channel);
+        sw.deliverRoleReply(pending.xid, pending.role);
+        verify(sw.channel);
+        assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+        assertEquals(pending.role, sw.role);
+        assertEquals(0, sw.pendingRoleRequests.size());
+    }
+    
+    @Test
+    public void testDeliverRoleReplyNonePending() {
+        // nothing pending 
+        expect(sw.channel.close()).andReturn(null);
+        replay(sw.channel);
+        sw.deliverRoleReply(1, Role.MASTER);
+        verify(sw.channel);
+        assertEquals(0, sw.pendingRoleRequests.size());
+    }
+    
+    @Test
+    public void testDeliverRoleReplyWrongXid() {
+        // wrong xid received 
+        PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+                            (int)System.currentTimeMillis(),  // arbitrary xid
+                            Role.MASTER,
+                            System.nanoTime() // arbitrary cookie
+                            );
+        sw.pendingRoleRequests.add(pending);
+        expect(sw.channel.close()).andReturn(null);
+        replay(sw.channel);
+        sw.deliverRoleReply(pending.xid+1, pending.role);
+        verify(sw.channel);
+        assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+        assertEquals(0, sw.pendingRoleRequests.size());
+    }
+    
+    @Test
+    public void testDeliverRoleReplyWrongRole() {
+        // correct xid but incorrect role received
+        PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+                            (int)System.currentTimeMillis(),  // arbitrary xid
+                            Role.MASTER,
+                            System.nanoTime() // arbitrary cookie
+                            );
+        sw.pendingRoleRequests.add(pending);
+        expect(sw.channel.close()).andReturn(null);
+        replay(sw.channel);
+        sw.deliverRoleReply(pending.xid, Role.SLAVE);
+        verify(sw.channel);
+        assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+        assertEquals(0, sw.pendingRoleRequests.size());
+    }
+    
+    @Test
+    public void testCheckFirstPendingRoleRequestXid() {
+        PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+                            54321, Role.MASTER, 232323);
+        replay(sw.channel); // we don't expect any invocations 
+        sw.pendingRoleRequests.add(pending);
+        assertEquals(true, sw.checkFirstPendingRoleRequestXid(54321));
+        assertEquals(false, sw.checkFirstPendingRoleRequestXid(0));
+        sw.pendingRoleRequests.clear();
+        assertEquals(false, sw.checkFirstPendingRoleRequestXid(54321));
+        verify(sw.channel);
+    }
+    
+    @Test
+    public void testCheckFirstPendingRoleRequestCookie() {
+        PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+                            54321, Role.MASTER, 232323);
+        replay(sw.channel); // we don't expect any invocations 
+        sw.pendingRoleRequests.add(pending);
+        assertEquals(true, sw.checkFirstPendingRoleRequestCookie(232323));
+        assertEquals(false, sw.checkFirstPendingRoleRequestCookie(0));
+        sw.pendingRoleRequests.clear();
+        assertEquals(false, sw.checkFirstPendingRoleRequestCookie(232323));
+        verify(sw.channel);
+    }
+    
+    @Test
+    public void testDeliverRoleRequestNotSupported () {
+        // normal case. xid is pending 
+        PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+                            (int)System.currentTimeMillis(),  // arbitrary xid
+                            Role.MASTER,
+                            System.nanoTime() // arbitrary cookie
+                            );
+        sw.role = Role.SLAVE;
+        sw.pendingRoleRequests.add(pending);
+        replay(sw.channel);
+        sw.deliverRoleRequestNotSupported(pending.xid);
+        verify(sw.channel);
+        assertEquals(false, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+        assertEquals(null, sw.role);
+        assertEquals(0, sw.pendingRoleRequests.size());
+    }
+    
+    @Test
+    public void testDeliverRoleRequestNotSupportedNonePending() {
+        // nothing pending 
+        sw.role = Role.SLAVE;
+        expect(sw.channel.close()).andReturn(null);
+        replay(sw.channel);
+        sw.deliverRoleRequestNotSupported(1);
+        verify(sw.channel);
+        assertEquals(null, sw.role);
+        assertEquals(0, sw.pendingRoleRequests.size());
+    }
+    
+    @Test
+    public void testDeliverRoleRequestNotSupportedWrongXid() {
+        // wrong xid received 
+        PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+                            (int)System.currentTimeMillis(),  // arbitrary xid
+                            Role.MASTER,
+                            System.nanoTime() // arbitrary cookie
+                            );
+        sw.role = Role.SLAVE;
+        sw.pendingRoleRequests.add(pending);
+        expect(sw.channel.close()).andReturn(null);
+        replay(sw.channel);
+        sw.deliverRoleRequestNotSupported(pending.xid+1);
+        verify(sw.channel);
+        assertEquals(null, sw.role);
+        assertEquals(0, sw.pendingRoleRequests.size());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java
new file mode 100644
index 0000000..991afff
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java
@@ -0,0 +1,233 @@
+package net.floodlightcontroller.core.internal;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.LinkedList;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.internal.RoleChanger.RoleChangeTask;
+
+import org.easymock.EasyMock;
+import org.jboss.netty.channel.Channel;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RoleChangerTest {
+    public RoleChanger roleChanger;
+    
+    @Before
+    public void setUp() throws Exception {
+        roleChanger = new RoleChanger();
+    }
+    
+    /**
+     * Send a role request for SLAVE to a switch that doesn't support it. 
+     * The connection should be closed.
+     */
+    @Test
+    public void testSendRoleRequestSlaveNotSupported() {
+        LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+        
+        // a switch that doesn't support role requests
+        OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+        Channel channel1 = createMock(Channel.class);
+        expect(sw1.getChannel()).andReturn(channel1);
+        // No support for NX_ROLE
+        expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+                        .andReturn(false); 
+        expect(channel1.close()).andReturn(null);
+        switches.add(sw1);
+        
+        replay(sw1, channel1);
+        roleChanger.sendRoleRequest(switches, Role.SLAVE, 123456);
+        verify(sw1, channel1);
+        
+        // sendRoleRequest needs to remove the switch from the list since
+        // it closed its connection
+        assertTrue(switches.isEmpty());
+    }
+    
+    /**
+     * Send a role request for MASTER to a switch that doesn't support it. 
+     * The connection should be closed.
+     */
+    @Test
+    public void testSendRoleRequestMasterNotSupported() {
+        LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+        
+        // a switch that doesn't support role requests
+        OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+        // No support for NX_ROLE
+        expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+                        .andReturn(false); 
+        switches.add(sw1);
+        
+        replay(sw1);
+        roleChanger.sendRoleRequest(switches, Role.MASTER, 123456);
+        verify(sw1);
+        
+        assertEquals(1, switches.size());
+    }
+    
+    /**
+     * Send a role request a switch that supports it and one that 
+     * hasn't had a role request send to it yet
+     */
+    @Test
+    public void testSendRoleRequestErrorHandling () throws Exception {
+        LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+        
+        // a switch that supports role requests
+        OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+        // No support for NX_ROLE
+        expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+                        .andReturn(true); 
+        expect(sw1.sendNxRoleRequest(Role.MASTER, 123456))
+                    .andThrow(new IOException()).once();
+        Channel channel1 = createMock(Channel.class);
+        expect(sw1.getChannel()).andReturn(channel1);
+        expect(channel1.close()).andReturn(null);
+        switches.add(sw1);
+        
+        replay(sw1);
+        roleChanger.sendRoleRequest(switches, Role.MASTER, 123456);
+        verify(sw1);
+        
+        assertTrue(switches.isEmpty());
+    }
+    
+    /**
+     * Check error handling 
+     * hasn't had a role request send to it yet
+     */
+    @Test
+    public void testSendRoleRequestSupported() throws Exception {
+        LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+        
+        // a switch that supports role requests
+        OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+        // No support for NX_ROLE
+        expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+                        .andReturn(true); 
+        expect(sw1.sendNxRoleRequest(Role.MASTER, 123456)).andReturn(1).once();
+        switches.add(sw1);
+        
+        // a switch for which we don't have SUPPORTS_NX_ROLE yet
+        OFSwitchImpl sw2 = EasyMock.createMock(OFSwitchImpl.class);
+        // No support for NX_ROLE
+        expect(sw2.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+                        .andReturn(null); 
+        expect(sw2.sendNxRoleRequest(Role.MASTER, 123456)).andReturn(1).once();
+        switches.add(sw2);
+        
+        
+        replay(sw1, sw2);
+        roleChanger.sendRoleRequest(switches, Role.MASTER, 123456);
+        verify(sw1, sw2);
+        
+        assertEquals(2, switches.size());
+    }
+    
+    @Test
+    public void testVerifyRoleReplyReceived() {
+        LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+        
+        // Add a switch that has received a role reply
+        OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+        expect(sw1.checkFirstPendingRoleRequestCookie(123456))
+                        .andReturn(false).once();
+        switches.add(sw1);
+        
+        // Add a switch that has not yet received a role reply
+        OFSwitchImpl sw2 = EasyMock.createMock(OFSwitchImpl.class);
+        expect(sw2.checkFirstPendingRoleRequestCookie(123456))
+                        .andReturn(true).once();
+        Channel channel2 = createMock(Channel.class);
+        expect(sw2.getChannel()).andReturn(channel2);
+        expect(channel2.close()).andReturn(null);
+        switches.add(sw2);
+        
+        
+        replay(sw1, sw2);
+        roleChanger.verifyRoleReplyReceived(switches, 123456);
+        verify(sw1, sw2);
+        
+        assertEquals(2, switches.size());
+    }
+    
+    @Test
+    public void testRoleChangeTask() {
+        @SuppressWarnings("unchecked")
+        Collection<OFSwitchImpl> switches = 
+                EasyMock.createMock(Collection.class);
+        long now = System.nanoTime();
+        long dt1 = 10 * 1000*1000*1000L;
+        long dt2 = 20 * 1000*1000*1000L;
+        long dt3 = 15 * 1000*1000*1000L;
+        RoleChangeTask t1 = new RoleChangeTask(switches, null, now+dt1);
+        RoleChangeTask t2 = new RoleChangeTask(switches, null, now+dt2);
+        RoleChangeTask t3 = new RoleChangeTask(switches, null, now+dt3);
+        
+        // FIXME: cannot test comparison against self. grrr
+        //assertTrue( t1.compareTo(t1) <= 0 );
+        assertTrue( t1.compareTo(t2) < 0 );
+        assertTrue( t1.compareTo(t3) < 0 );
+        
+        assertTrue( t2.compareTo(t1) > 0 );
+        //assertTrue( t2.compareTo(t2) <= 0 );
+        assertTrue( t2.compareTo(t3) > 0 );
+    }
+    
+    @Test
+    public void testSubmitRequest() throws Exception {
+        LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+        roleChanger.timeout = 500*1000*1000; // 500 ms
+        
+        // a switch that supports role requests
+        OFSwitchImpl sw1 = EasyMock.createStrictMock(OFSwitchImpl.class);
+        // No support for NX_ROLE
+        expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+                        .andReturn(true); 
+        expect(sw1.sendNxRoleRequest(EasyMock.same(Role.MASTER), EasyMock.anyLong()))
+                       .andReturn(1);
+        expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+                        .andReturn(true); 
+        expect(sw1.sendNxRoleRequest(EasyMock.same(Role.SLAVE), EasyMock.anyLong()))
+                       .andReturn(1);
+        // The following calls happen for timeout handling:
+        expect(sw1.checkFirstPendingRoleRequestCookie(EasyMock.anyLong()))
+                        .andReturn(false);
+        expect(sw1.checkFirstPendingRoleRequestCookie(EasyMock.anyLong()))
+                        .andReturn(false);
+        switches.add(sw1);
+        
+        
+        replay(sw1);
+        roleChanger.submitRequest(switches, Role.MASTER);
+        roleChanger.submitRequest(switches, Role.SLAVE);
+        // Wait until role request has been sent. 
+        // TODO: need to get rid of this sleep somehow
+        Thread.sleep(100);
+        // Now there should be exactly one timeout task pending
+        assertEquals(2, roleChanger.pendingTasks.size());
+        // Make sure it's indeed a timeout task
+        assertSame(RoleChanger.RoleChangeTask.Type.TIMEOUT, 
+                     roleChanger.pendingTasks.peek().type);
+        // Check that RoleChanger indeed made a copy of switches collection
+        assertNotSame(switches, roleChanger.pendingTasks.peek().switches);
+        
+        // Wait until the timeout triggers 
+        // TODO: get rid of this sleep too.
+        Thread.sleep(500);
+        assertEquals(0, roleChanger.pendingTasks.size());
+        verify(sw1);
+        
+    }
+    
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/module/FloodlightTestModuleLoader.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/module/FloodlightTestModuleLoader.java
new file mode 100644
index 0000000..2ba838e
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/module/FloodlightTestModuleLoader.java
@@ -0,0 +1,182 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.module.FloodlightModuleLoader;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.counter.NullCounterStore;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.test.MockDeviceManager;
+import net.floodlightcontroller.perfmon.NullPktInProcessingTime;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import net.floodlightcontroller.topology.TopologyManager;
+
+public class FloodlightTestModuleLoader extends FloodlightModuleLoader {
+	protected static Logger log = LoggerFactory.getLogger(FloodlightTestModuleLoader.class);
+	
+	// List of default modules to use unless specified otherwise
+	public static final Class<? extends IFloodlightModule> DEFAULT_STORAGE_SOURCE =
+			MemoryStorageSource.class;
+	public static final Class<? extends IFloodlightModule> DEFAULT_FLOODLIGHT_PRPOVIDER =
+			MockFloodlightProvider.class;
+	public static final Class<? extends IFloodlightModule> DEFAULT_TOPOLOGY_PROVIDER =
+			TopologyManager.class;
+	public static final Class<? extends IFloodlightModule> DEFAULT_DEVICE_SERVICE =
+			MockDeviceManager.class;
+	public static final Class<? extends IFloodlightModule> DEFAULT_COUNTER_STORE =
+			NullCounterStore.class;
+	public static final Class<? extends IFloodlightModule> DEFAULT_THREADPOOL =
+			MockThreadPoolService.class;
+	public static final Class<? extends IFloodlightModule> DEFAULT_ENTITY_CLASSIFIER =
+			DefaultEntityClassifier.class;
+	public static final Class<? extends IFloodlightModule> DEFAULT_PERFMON =
+			NullPktInProcessingTime.class;
+	
+	protected static final Collection<Class<? extends IFloodlightModule>> DEFAULT_MODULE_LIST;
+	
+	static {
+		DEFAULT_MODULE_LIST = new ArrayList<Class<? extends IFloodlightModule>>();
+		DEFAULT_MODULE_LIST.add(DEFAULT_DEVICE_SERVICE);
+		DEFAULT_MODULE_LIST.add(DEFAULT_FLOODLIGHT_PRPOVIDER);
+		DEFAULT_MODULE_LIST.add(DEFAULT_STORAGE_SOURCE);
+		DEFAULT_MODULE_LIST.add(DEFAULT_TOPOLOGY_PROVIDER);
+		DEFAULT_MODULE_LIST.add(DEFAULT_COUNTER_STORE);
+		DEFAULT_MODULE_LIST.add(DEFAULT_THREADPOOL);
+		DEFAULT_MODULE_LIST.add(DEFAULT_ENTITY_CLASSIFIER);
+		DEFAULT_MODULE_LIST.add(DEFAULT_PERFMON);
+	}
+	
+	protected IFloodlightModuleContext fmc;
+	
+	/**
+	 * Adds default modules to the list of modules to load. This is done
+	 * in order to avoid the module loader throwing errors about duplicate
+	 * modules and neither one is specified by the user.
+	 * @param userModules The list of user specified modules to add to.
+	 */
+	protected void addDefaultModules(Collection<Class<? extends IFloodlightModule>> userModules) {
+		Collection<Class<? extends IFloodlightModule>> defaultModules =
+				new ArrayList<Class<? extends IFloodlightModule>>(DEFAULT_MODULE_LIST.size());
+		defaultModules.addAll(DEFAULT_MODULE_LIST);
+		
+		Iterator<Class<? extends IFloodlightModule>> modIter = userModules.iterator();
+		while (modIter.hasNext()) {
+			Class<? extends IFloodlightModule> userMod = modIter.next();
+			Iterator<Class<? extends IFloodlightModule>> dmIter = defaultModules.iterator();
+			while (dmIter.hasNext()) {
+				Class<? extends IFloodlightModule> dmMod = dmIter.next();
+				Collection<Class<? extends IFloodlightService>> userModServs;
+				Collection<Class<? extends IFloodlightService>> dmModServs;
+				try {
+					dmModServs = dmMod.newInstance().getModuleServices();
+					userModServs = userMod.newInstance().getModuleServices();
+				} catch (InstantiationException e) {
+					log.error(e.getMessage());
+					break;
+				} catch (IllegalAccessException e) {
+					log.error(e.getMessage());
+					break;
+				}
+				
+				// If either of these are null continue as they have no services
+				if (dmModServs == null || userModServs == null) continue;
+				
+				// If the user supplied modules has a service
+				// that is in the default module list we remove
+				// the default module from the list.
+				boolean shouldBreak = false;
+				Iterator<Class<? extends IFloodlightService>> userModServsIter 
+					= userModServs.iterator();
+				while (userModServsIter.hasNext()) {
+					Class<? extends IFloodlightService> userModServIntf = userModServsIter.next();
+					Iterator<Class<? extends IFloodlightService>> dmModsServsIter 
+						= dmModServs.iterator();
+					while (dmModsServsIter.hasNext()) {
+						Class<? extends IFloodlightService> dmModServIntf 
+							= dmModsServsIter.next();
+						
+						if (dmModServIntf.getCanonicalName().equals(
+								userModServIntf.getCanonicalName())) {
+							logger.debug("Removing default module {} because it was " +
+									"overriden by an explicitly specified module",
+									dmModServIntf.getCanonicalName());
+							dmIter.remove();
+							shouldBreak = true;
+							break;
+						}
+					}
+					if (shouldBreak) break;
+				}
+				if (shouldBreak) break;
+			}
+		}
+		
+		// Append the remaining default modules to the user specified ones.
+		// This avoids the module loader throwing duplicate module errors.
+		userModules.addAll(defaultModules);
+		log.debug("Using module set " + userModules.toString());
+	}
+	
+	/**
+	 * Sets up all modules and their dependencies.
+	 * @param modules The list of modules that the user wants to load.
+	 * @param mockedServices The list of services that will be mocked. Any
+	 * module that provides this service will not be loaded.
+	 */
+	public void setupModules(Collection<Class<? extends IFloodlightModule>> modules,
+			Collection<IFloodlightService> mockedServices) {
+		addDefaultModules(modules);
+		Collection<String> modulesAsString = new ArrayList<String>();
+		for (Class<? extends IFloodlightModule> m : modules) {
+			modulesAsString.add(m.getCanonicalName());
+		}
+		
+		try {
+			fmc = loadModulesFromList(modulesAsString, null, mockedServices);
+		} catch (FloodlightModuleException e) {
+			log.error(e.getMessage());
+		}
+	}
+	
+	/**
+	 * Gets the inited/started instance of a module from the context.
+	 * @param ifl The name if the module to get, i.e. "LearningSwitch.class".
+	 * @return The inited/started instance of the module.
+	 */
+	public IFloodlightModule getModuleByName(Class<? extends IFloodlightModule> ifl) {
+		Collection<IFloodlightModule> modules = fmc.getAllModules();
+		for (IFloodlightModule m : modules) {
+			if (ifl.getCanonicalName().equals(m.getClass().getCanonicalName())) {
+				return m;
+			}
+		}
+		return null;
+	}
+	
+	/**
+	 * Gets an inited/started instance of a service from the context.
+	 * @param ifs The name of the service to get, i.e. "ITopologyService.class".
+	 * @return The inited/started instance of the service from teh context.
+	 */
+	public IFloodlightService getModuleByService(Class<? extends IFloodlightService> ifs) {
+		Collection<IFloodlightModule> modules = fmc.getAllModules();
+		for (IFloodlightModule m : modules) {
+			Collection<Class<? extends IFloodlightService>> mServs = m.getModuleServices();
+			if (mServs == null) continue;
+			for (Class<? extends IFloodlightService> mServClass : mServs) {
+				if (mServClass.getCanonicalName().equals(ifs.getCanonicalName())) {
+					assert(m instanceof IFloodlightService);
+					return (IFloodlightService)m;
+				}
+			}
+		}
+		return null;
+	}
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java
new file mode 100644
index 0000000..e83fc58
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java
@@ -0,0 +1,350 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IInfoProvider;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchFilter;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.IListener.Command;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.ListenerDispatcher;
+import net.floodlightcontroller.packet.Ethernet;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.factory.BasicFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class MockFloodlightProvider implements IFloodlightModule, IFloodlightProviderService {
+    protected static Logger log = LoggerFactory.getLogger(MockFloodlightProvider.class);
+    protected ConcurrentMap<OFType, ListenerDispatcher<OFType,IOFMessageListener>> listeners;
+    protected List<IOFSwitchListener> switchListeners;
+    protected List<IHAListener> haListeners;
+    protected Map<Long, IOFSwitch> switches;
+    protected BasicFactory factory;
+
+    /**
+     * 
+     */
+    public MockFloodlightProvider() {
+        listeners = new ConcurrentHashMap<OFType, ListenerDispatcher<OFType, 
+                                   IOFMessageListener>>();
+        switches = new ConcurrentHashMap<Long, IOFSwitch>();
+        switchListeners = new CopyOnWriteArrayList<IOFSwitchListener>();
+        haListeners = new CopyOnWriteArrayList<IHAListener>();
+        factory = new BasicFactory();
+    }
+
+    @Override
+    public synchronized void addOFMessageListener(OFType type, 
+                                                  IOFMessageListener listener) {
+        ListenerDispatcher<OFType, IOFMessageListener> ldd = 
+                listeners.get(type);
+        if (ldd == null) {
+            ldd = new ListenerDispatcher<OFType, IOFMessageListener>();
+            listeners.put(type, ldd);
+        }
+        ldd.addListener(type, listener);
+    }
+
+    @Override
+    public synchronized void removeOFMessageListener(OFType type,
+                                                     IOFMessageListener listener) {
+        ListenerDispatcher<OFType, IOFMessageListener> ldd = 
+                listeners.get(type);
+        if (ldd != null) {
+            ldd.removeListener(listener);
+        }
+    }
+
+    /**
+     * @return the listeners
+     */
+    public Map<OFType, List<IOFMessageListener>> getListeners() {
+        Map<OFType, List<IOFMessageListener>> lers = 
+                new HashMap<OFType, List<IOFMessageListener>>();
+            for(Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> e : 
+                listeners.entrySet()) {
+                lers.put(e.getKey(), e.getValue().getOrderedListeners());
+            }
+            return Collections.unmodifiableMap(lers);
+    }
+
+    public void clearListeners() {
+        this.listeners.clear();
+    }
+    
+    @Override
+    public Map<Long, IOFSwitch> getSwitches() {
+        return this.switches;
+    }
+
+    public void setSwitches(Map<Long, IOFSwitch> switches) {
+        this.switches = switches;
+    }
+
+    @Override
+    public void addOFSwitchListener(IOFSwitchListener listener) {
+        switchListeners.add(listener);
+    }
+
+    @Override
+    public void removeOFSwitchListener(IOFSwitchListener listener) {
+        switchListeners.remove(listener);
+    }
+
+    public void dispatchMessage(IOFSwitch sw, OFMessage msg) {
+        dispatchMessage(sw, msg, new FloodlightContext());
+    }
+    
+    public void dispatchMessage(IOFSwitch sw, OFMessage msg, FloodlightContext bc) {
+        List<IOFMessageListener> theListeners = listeners.get(msg.getType()).getOrderedListeners();
+        if (theListeners != null) {
+            Command result = Command.CONTINUE;
+            Iterator<IOFMessageListener> it = theListeners.iterator();
+            if (OFType.PACKET_IN.equals(msg.getType())) {
+                OFPacketIn pi = (OFPacketIn)msg;
+                Ethernet eth = new Ethernet();
+                eth.deserialize(pi.getPacketData(), 0, pi.getPacketData().length);
+                IFloodlightProviderService.bcStore.put(bc, 
+                        IFloodlightProviderService.CONTEXT_PI_PAYLOAD, 
+                        eth);
+            }
+            while (it.hasNext() && !Command.STOP.equals(result)) {
+                result = it.next().receive(sw, msg, bc);
+            }
+        }
+    }
+    
+    public void handleOutgoingMessage(IOFSwitch sw, OFMessage m, FloodlightContext bc) {
+        List<IOFMessageListener> msgListeners = null;
+        if (listeners.containsKey(m.getType())) {
+            msgListeners = listeners.get(m.getType()).getOrderedListeners();
+        }
+            
+        if (msgListeners != null) {                
+            for (IOFMessageListener listener : msgListeners) {
+                if (listener instanceof IOFSwitchFilter) {
+                    if (!((IOFSwitchFilter)listener).isInterested(sw)) {
+                        continue;
+                    }
+                }
+                if (Command.STOP.equals(listener.receive(sw, m, bc))) {
+                    break;
+                }
+            }
+        }
+    }
+    
+    public void handleOutgoingMessages(IOFSwitch sw, List<OFMessage> msglist, FloodlightContext bc) {
+        for (OFMessage m:msglist) {
+            handleOutgoingMessage(sw, m, bc);
+        }
+    }
+
+    /**
+     * @return the switchListeners
+     */
+    public List<IOFSwitchListener> getSwitchListeners() {
+        return switchListeners;
+    }
+    
+    public void terminate() {
+    }
+
+    @Override
+    public boolean injectOfMessage(IOFSwitch sw, OFMessage msg) {
+        dispatchMessage(sw, msg);
+        return true;
+    }
+    
+    @Override
+    public boolean injectOfMessage(IOFSwitch sw, OFMessage msg, 
+                                   FloodlightContext bContext) {        
+        dispatchMessage(sw, msg, bContext);     
+        return true;
+    }
+    
+    @Override
+    public BasicFactory getOFMessageFactory() {
+        return factory;
+    }
+
+    @Override
+    public void run() {
+        logListeners();
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> services =
+                new ArrayList<Class<? extends IFloodlightService>>(1);
+        services.add(IFloodlightProviderService.class);
+        return services;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                        IFloodlightService>();
+        m.put(IFloodlightProviderService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        return null;
+    }
+    
+    @Override
+    public void init(FloodlightModuleContext context)
+                                                 throws FloodlightModuleException {
+        // TODO Auto-generated method stub
+        
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    @Override
+    public void addInfoProvider(String type, IInfoProvider provider) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    @Override
+    public void removeInfoProvider(String type, IInfoProvider provider) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    @Override
+    public Map<String, Object> getControllerInfo(String type) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void addHAListener(IHAListener listener) {
+        haListeners.add(listener);
+    }
+
+    @Override
+    public void removeHAListener(IHAListener listener) {
+        haListeners.remove(listener);
+    }
+    
+    @Override
+    public Role getRole() {
+        return null;
+    }
+    
+    @Override
+    public void setRole(Role role) {
+        
+    }
+    
+    /**
+     * Dispatches a new role change notification
+     * @param oldRole
+     * @param newRole
+     */
+    public void dispatchRoleChanged(Role oldRole, Role newRole) {
+        for (IHAListener rl : haListeners) {
+            rl.roleChanged(oldRole, newRole);
+        }
+    }
+
+    @Override
+    public String getControllerId() {
+        return "localhost";
+    }
+
+    @Override
+    public Map<String, String> getControllerNodeIPs() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public long getSystemStartTime() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    private void logListeners() {
+        for (Map.Entry<OFType,
+                       ListenerDispatcher<OFType, 
+                                          IOFMessageListener>> entry
+             : listeners.entrySet()) {
+            
+            OFType type = entry.getKey();
+            ListenerDispatcher<OFType, IOFMessageListener> ldd = 
+                    entry.getValue();
+            
+            StringBuffer sb = new StringBuffer();
+            sb.append("OFListeners for ");
+            sb.append(type);
+            sb.append(": ");
+            for (IOFMessageListener l : ldd.getOrderedListeners()) {
+                sb.append(l.getName());
+                sb.append(",");
+            }
+            log.debug(sb.toString());            
+        }
+    }
+
+    @Override
+    public void setAlwaysClearFlowsOnSwAdd(boolean value) {
+        // TODO Auto-generated method stub
+        
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockScheduledExecutor.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockScheduledExecutor.java
new file mode 100644
index 0000000..733ff1a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockScheduledExecutor.java
@@ -0,0 +1,252 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class MockScheduledExecutor implements ScheduledExecutorService {
+    ScheduledExecutorService ses = null;
+
+    public static class MockFuture<T> implements Future<T>,ScheduledFuture<T>{
+        T result;
+        ExecutionException e;
+        
+        /**
+         * @param result
+         */
+        public MockFuture(T result) {
+            super();
+            this.result = result;
+        }
+        
+        /**
+         * @param result
+         */
+        public MockFuture(ExecutionException e) {
+            super();
+            this.e = e;
+        }
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            return false;
+        }
+
+        @Override
+        public T get() throws InterruptedException, ExecutionException {
+            if (e != null) throw e;
+            return result;
+        }
+
+        @Override
+        public T get(long timeout, TimeUnit unit) throws InterruptedException,
+                ExecutionException, TimeoutException {
+            if (e != null) throw e;
+            return result;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return false;
+        }
+
+        @Override
+        public boolean isDone() {
+            return true;
+        }
+
+        @Override
+        public long getDelay(TimeUnit arg0) {
+            return 0;
+        }
+
+        @Override
+        public int compareTo(Delayed arg0) {
+            return 0;
+        }
+    }
+    
+    @Override
+    public boolean awaitTermination(long arg0, TimeUnit arg1)
+            throws InterruptedException {
+        return false;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> arg0)
+            throws InterruptedException {
+        List<Future<T>> rlist = new ArrayList<Future<T>>();
+        for (Callable<T> arg : arg0) {
+            try {
+                rlist.add(new MockFuture<T>(arg.call()));
+            } catch (Exception e) {
+                rlist.add(new MockFuture<T>(new ExecutionException(e)));
+            }
+        }
+        return rlist;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(
+            Collection<? extends Callable<T>> arg0, long arg1, TimeUnit arg2)
+            throws InterruptedException {
+        return this.invokeAll(arg0);
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> arg0)
+            throws InterruptedException, ExecutionException {
+        for (Callable<T> arg : arg0) {
+            try {
+                return arg.call();
+            } catch (Exception e) {
+
+            }
+        }
+        throw new ExecutionException(new Exception("no task completed successfully"));
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> arg0, long arg1,
+            TimeUnit arg2) throws InterruptedException, ExecutionException,
+            TimeoutException {
+        return invokeAny(arg0);
+    }
+
+    @Override
+    public boolean isShutdown() {
+        if (ses != null)
+            return ses.isShutdown();
+
+        return false;
+    }
+
+    @Override
+    public boolean isTerminated() {
+        if (ses != null)
+            return ses.isTerminated();
+        
+        return false;
+    }
+
+    @Override
+    public void shutdown() {
+        if (ses != null)
+            ses.shutdown();
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+        if (ses != null)
+            return ses.shutdownNow();
+        return null;
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> arg0) {
+        try {
+            return new MockFuture<T>(arg0.call());
+        } catch (Exception e) {
+            return new MockFuture<T>(new ExecutionException(e));
+        }
+    }
+
+    @Override
+    public Future<?> submit(Runnable arg0) {
+        try {
+            arg0.run();
+            return new MockFuture<Object>(null);
+        } catch (Exception e) {
+            return new MockFuture<Object>(new ExecutionException(e));
+        }
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable arg0, T arg1) {
+        try {
+            arg0.run();
+            return new MockFuture<T>((T)null);
+        } catch (Exception e) {
+            return new MockFuture<T>(new ExecutionException(e));
+        }
+    }
+
+    @Override
+    public void execute(Runnable arg0) {
+        arg0.run();
+    }
+
+    @Override
+    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+        if (ses == null)
+            ses = Executors.newScheduledThreadPool(1);
+        try {
+            return ses.schedule(command, delay, unit);
+        } catch (Exception e) {
+            return new MockFuture<Object>(new ExecutionException(e));
+        }
+    }
+
+    @Override
+    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay,
+            TimeUnit unit) {
+        if (ses == null)
+            ses = Executors.newScheduledThreadPool(1);
+        try {
+            return ses.schedule(callable, delay, unit);
+        } catch (Exception e) {
+            return new MockFuture<V>(new ExecutionException(e));
+        }
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,
+                                                  long period, TimeUnit unit) {
+        if (ses == null)
+            ses = Executors.newScheduledThreadPool(1);
+        try {
+            return ses.scheduleAtFixedRate(command, initialDelay, period, unit);
+        } catch (Exception e) {
+            return new MockFuture<Object>(new ExecutionException(e));
+        }
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+                                                     long delay, TimeUnit unit) {
+        if (ses == null)
+            ses = Executors.newScheduledThreadPool(1);
+        try {
+            return ses.scheduleWithFixedDelay(command, initialDelay, delay, unit);
+        } catch (Exception e) {
+            return new MockFuture<Object>(new ExecutionException(e));
+        }
+    }
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java
new file mode 100644
index 0000000..67bee30
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java
@@ -0,0 +1,67 @@
+package net.floodlightcontroller.core.test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+public class MockThreadPoolService implements IFloodlightModule, IThreadPoolService {
+    
+    protected ScheduledExecutorService mockExecutor = new MockScheduledExecutor();
+
+    /**
+     * Return a mock executor that will simply execute each task 
+     * synchronously once.
+     */
+    @Override
+    public ScheduledExecutorService getScheduledExecutor() {
+        return mockExecutor;
+    }
+
+    // IFloodlightModule
+    
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IThreadPoolService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+            IFloodlightService> m = 
+                new HashMap<Class<? extends IFloodlightService>,
+                    IFloodlightService>();
+        m.put(IThreadPoolService.class, this);
+        // We are the class that implements the service
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        // No dependencies
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+                                 throws FloodlightModuleException {
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // no-op
+    }
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java
new file mode 100644
index 0000000..4d9ca32
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java
@@ -0,0 +1,147 @@
+package net.floodlightcontroller.core.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.floodlightcontroller.packet.DHCP;
+import net.floodlightcontroller.packet.DHCPOption;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.util.MACAddress;
+
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.protocol.factory.BasicFactory;
+
+/**
+ * A class to that creates many types of L2/L3/L4 or OpenFlow packets.
+ * This is used in testing.
+ * @author alexreimers
+ *
+ */
+public class PacketFactory {
+    public static String broadcastMac = "ff:ff:ff:ff:ff:ff";
+    public static String broadcastIp = "255.255.255.255";
+    protected static BasicFactory OFMessageFactory = new BasicFactory();
+    
+    /**
+     * Generates a DHCP request OFPacketIn.
+     * @param hostMac The host MAC address of for the request.
+     * @return An OFPacketIn that contains a DHCP request packet.
+     */
+    public static OFPacketIn DhcpDiscoveryRequestOFPacketIn(MACAddress hostMac) {
+        byte[] serializedPacket = DhcpDiscoveryRequestEthernet(hostMac).serialize();
+        return (((OFPacketIn)OFMessageFactory
+                .getMessage(OFType.PACKET_IN))
+                .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+                .setInPort((short) 1)
+                .setPacketData(serializedPacket)
+                .setReason(OFPacketInReason.NO_MATCH)
+                .setTotalLength((short)serializedPacket.length));
+    }
+    
+    /**
+     * Generates a DHCP request Ethernet frame.
+     * @param hostMac The host MAC address of for the request.
+     * @returnAn An Ethernet frame that contains a DHCP request packet.
+     */
+    public static Ethernet DhcpDiscoveryRequestEthernet(MACAddress hostMac) {
+        List<DHCPOption> optionList = new ArrayList<DHCPOption>();
+        
+        byte[] requestValue = new byte[4];
+        requestValue[0] = requestValue[1] = requestValue[2] = requestValue[3] = 0;
+        DHCPOption requestOption = 
+                new DHCPOption()
+                    .setCode(DHCP.DHCPOptionCode.OptionCode_RequestedIP.
+                             getValue())
+                    .setLength((byte)4)
+                    .setData(requestValue);
+        
+        byte[] msgTypeValue = new byte[1];
+        msgTypeValue[0] = 1;    // DHCP request
+        DHCPOption msgTypeOption = 
+                new DHCPOption()
+                    .setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.
+                             getValue())
+                    .setLength((byte)1)
+                    .setData(msgTypeValue);
+        
+        byte[] reqParamValue = new byte[4];
+        reqParamValue[0] = 1;   // subnet mask
+        reqParamValue[1] = 3;   // Router
+        reqParamValue[2] = 6;   // Domain Name Server
+        reqParamValue[3] = 42;  // NTP Server
+        DHCPOption reqParamOption = 
+                new DHCPOption()
+                    .setCode(DHCP.DHCPOptionCode.OptionCode_RequestedParameters.
+                             getValue())
+                    .setLength((byte)4)
+                    .setData(reqParamValue);
+        
+        byte[] clientIdValue = new byte[7];
+        clientIdValue[0] = 1;   // Ethernet
+        System.arraycopy(hostMac.toBytes(), 0, 
+                         clientIdValue, 1, 6);
+        DHCPOption clientIdOption = 
+                new DHCPOption()
+                    .setCode(DHCP.DHCPOptionCode.OptionCode_ClientID.
+                             getValue())
+                             .setLength((byte)7)
+                             .setData(clientIdValue);
+        
+        DHCPOption endOption = 
+                new DHCPOption()
+                    .setCode(DHCP.DHCPOptionCode.OptionCode_END.
+                             getValue())
+                             .setLength((byte)0)
+                             .setData(null);
+                                    
+        optionList.add(requestOption);
+        optionList.add(msgTypeOption);
+        optionList.add(reqParamOption);
+        optionList.add(clientIdOption);
+        optionList.add(endOption);
+        
+        Ethernet requestPacket = new Ethernet();
+        requestPacket.setSourceMACAddress(hostMac.toBytes())
+        .setDestinationMACAddress(broadcastMac)
+        .setEtherType(Ethernet.TYPE_IPv4)
+        .setPayload(
+                new IPv4()
+                .setVersion((byte)4)
+                .setDiffServ((byte)0)
+                .setIdentification((short)100)
+                .setFlags((byte)0)
+                .setFragmentOffset((short)0)
+                .setTtl((byte)250)
+                .setProtocol(IPv4.PROTOCOL_UDP)
+                .setChecksum((short)0)
+                .setSourceAddress(0)
+                .setDestinationAddress(broadcastIp)
+                .setPayload(
+                        new UDP()
+                        .setSourcePort(UDP.DHCP_CLIENT_PORT)
+                        .setDestinationPort(UDP.DHCP_SERVER_PORT)
+                        .setChecksum((short)0)
+                        .setPayload(
+                                new DHCP()
+                                .setOpCode(DHCP.OPCODE_REQUEST)
+                                .setHardwareType(DHCP.HWTYPE_ETHERNET)
+                                .setHardwareAddressLength((byte)6)
+                                .setHops((byte)0)
+                                .setTransactionId(0x00003d1d)
+                                .setSeconds((short)0)
+                                .setFlags((short)0)
+                                .setClientIPAddress(0)
+                                .setYourIPAddress(0)
+                                .setServerIPAddress(0)
+                                .setGatewayIPAddress(0)
+                                .setClientHardwareAddress(hostMac.toBytes())
+                                .setOptions(optionList))));
+                
+        return requestPacket;
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java
new file mode 100644
index 0000000..2c77d6a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java
@@ -0,0 +1,38 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.util;
+
+import junit.framework.TestCase;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+
+public class AppCookieTest extends FloodlightTestCase {
+    public void testAppCookie(){
+        int appID = 12;
+        int user = 12345;
+        long cookie = AppCookie.makeCookie(appID, user);
+        TestCase.assertEquals(appID, AppCookie.extractApp(cookie));
+        TestCase.assertEquals(user, AppCookie.extractUser(cookie));
+        
+        // now ensure that we don't exceed our size
+        cookie = AppCookie.makeCookie(appID + 0x10000, user);
+        TestCase.assertEquals(appID, AppCookie.extractApp(cookie));
+        TestCase.assertEquals(user, AppCookie.extractUser(cookie));
+
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java
new file mode 100644
index 0000000..0829aa4
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java
@@ -0,0 +1,153 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.util;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.Test;
+import org.openflow.protocol.OFType;
+
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+public class MessageDispatcherTest extends FloodlightTestCase {
+
+    IOFMessageListener createLMock(String name) {
+        IOFMessageListener mock = createNiceMock(IOFMessageListener.class);
+        expect(mock.getName()).andReturn(name).anyTimes();
+        return mock;
+    }
+    
+    void addPrereqs(IOFMessageListener mock, String... deps) {
+        for (String dep : deps) {
+            expect(mock.isCallbackOrderingPrereq(OFType.PACKET_IN, dep)).andReturn(true).anyTimes();
+        }
+    }
+    
+    void testOrdering(ArrayList<IOFMessageListener> inputListeners) {
+        ListenerDispatcher<OFType, IOFMessageListener> ld = 
+                new ListenerDispatcher<OFType, IOFMessageListener>();
+        
+        for (IOFMessageListener l : inputListeners) {
+            ld.addListener(OFType.PACKET_IN, l);
+        }
+        for (IOFMessageListener l : inputListeners) {
+            verify(l);
+        }
+        
+        List<IOFMessageListener> result = ld.getOrderedListeners();
+        System.out.print("Ordering: ");
+        for (IOFMessageListener l : result) {
+            System.out.print(l.getName());
+            System.out.print(",");
+        }
+        System.out.print("\n");
+
+        for (int ind_i = 0; ind_i < result.size(); ind_i++) {
+            IOFMessageListener i = result.get(ind_i);
+            for (int ind_j = ind_i+1; ind_j < result.size(); ind_j++) {
+                IOFMessageListener j = result.get(ind_j);
+                
+                boolean orderwrong = 
+                        (i.isCallbackOrderingPrereq(OFType.PACKET_IN, j.getName()) ||
+                         j.isCallbackOrderingPostreq(OFType.PACKET_IN, i.getName()));
+                assertFalse("Invalid order: " + 
+                            ind_i + " (" + i.getName() + ") " + 
+                            ind_j + " (" + j.getName() + ") ", orderwrong);
+            }
+        }
+    }
+    
+    void randomTestOrdering(ArrayList<IOFMessageListener> mocks) {
+        Random rand = new Random(0);
+        ArrayList<IOFMessageListener> random = 
+                new ArrayList<IOFMessageListener>();
+        random.addAll(mocks);
+        for (int i = 0; i < 20; i++) {
+            for (int j = 0; j < random.size(); j++) {
+                int ind = rand.nextInt(mocks.size()-1);
+                IOFMessageListener tmp = random.get(j);
+                random.set(j, random.get(ind));
+                random.set(ind, tmp);
+            }
+            testOrdering(random);
+        }
+    }
+    
+    @Test
+    public void testCallbackOrderingSimple() throws Exception {
+        ArrayList<IOFMessageListener> mocks = 
+                new ArrayList<IOFMessageListener>();
+        for (int i = 0; i < 10; i++) {
+            mocks.add(createLMock(""+i));
+        }
+        for (int i = 1; i < 10; i++) {
+            addPrereqs(mocks.get(i), ""+(i-1));
+        }
+        for (IOFMessageListener l : mocks) {
+            replay(l);
+        }
+        randomTestOrdering(mocks);
+    }
+
+    @Test
+    public void testCallbackOrderingPartial() throws Exception {
+        ArrayList<IOFMessageListener> mocks = 
+                new ArrayList<IOFMessageListener>();
+        for (int i = 0; i < 10; i++) {
+            mocks.add(createLMock(""+i));
+        }
+        for (int i = 1; i < 5; i++) {
+            addPrereqs(mocks.get(i), ""+(i-1));
+        }
+        for (int i = 6; i < 10; i++) {
+            addPrereqs(mocks.get(i), ""+(i-1));
+        }
+        for (IOFMessageListener l : mocks) {
+            replay(l);
+        }
+        randomTestOrdering(mocks);
+    }
+    
+
+    @Test
+    public void testCallbackOrderingPartial2() throws Exception {
+        ArrayList<IOFMessageListener> mocks = 
+                new ArrayList<IOFMessageListener>();
+        for (int i = 0; i < 10; i++) {
+            mocks.add(createLMock(""+i));
+        }
+        for (int i = 2; i < 5; i++) {
+            addPrereqs(mocks.get(i), ""+(i-1));
+        }
+        for (int i = 6; i < 9; i++) {
+            addPrereqs(mocks.get(i), ""+(i-1));
+        }
+        for (IOFMessageListener l : mocks) {
+            replay(l);
+        }
+        randomTestOrdering(mocks);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java
new file mode 100644
index 0000000..010c651
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java
@@ -0,0 +1,288 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.core.util;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+public class SingletonTaskTest extends FloodlightTestCase {
+    
+    public int ran = 0;
+    public int finished = 0;
+    public long time = 0;
+    
+    @Before
+    public void setup() {
+        ran = 0;
+        finished = 0;
+        time = 0;
+    }
+    
+    @Test
+    public void testBasic() throws InterruptedException {
+        ScheduledExecutorService ses = 
+            Executors.newSingleThreadScheduledExecutor();
+        
+        SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+            @Override
+            public void run() {
+                ran += 1;
+            }
+        });
+        st1.reschedule(0, null);
+        ses.shutdown();
+        ses.awaitTermination(5, TimeUnit.SECONDS);
+        
+        assertEquals("Check that task ran", 1, ran);
+    }
+
+    @Test
+    public void testDelay() throws InterruptedException {
+        ScheduledExecutorService ses = 
+            Executors.newSingleThreadScheduledExecutor();
+
+        SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+            @Override
+            public void run() {
+                ran += 1;
+                time = System.nanoTime();
+            }
+        });
+        long start = System.nanoTime();
+        st1.reschedule(10, TimeUnit.MILLISECONDS);
+        assertFalse("Check that task hasn't run yet", ran > 0);
+        
+        ses.shutdown();
+        ses.awaitTermination(5, TimeUnit.SECONDS);
+        
+        assertEquals("Check that task ran", 1, ran);
+        assertTrue("Check that time passed appropriately",
+                   (time - start) >= TimeUnit.NANOSECONDS.convert(10, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testReschedule() throws InterruptedException {
+        ScheduledExecutorService ses = 
+            Executors.newSingleThreadScheduledExecutor();
+
+        final Object tc = this;
+        SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+            @Override
+            public void run() {
+                synchronized (tc) {
+                    ran += 1;
+                }
+                time = System.nanoTime();
+            }
+        });
+        long start = System.nanoTime();
+        st1.reschedule(20, TimeUnit.MILLISECONDS);
+        Thread.sleep(5);
+        assertFalse("Check that task hasn't run yet", ran > 0);
+        st1.reschedule(20, TimeUnit.MILLISECONDS);
+        Thread.sleep(5);
+        assertFalse("Check that task hasn't run yet", ran > 0);
+        st1.reschedule(20, TimeUnit.MILLISECONDS);
+        Thread.sleep(5);
+        assertFalse("Check that task hasn't run yet", ran > 0);
+        st1.reschedule(20, TimeUnit.MILLISECONDS);
+        Thread.sleep(5);
+        assertFalse("Check that task hasn't run yet", ran > 0);
+        st1.reschedule(20, TimeUnit.MILLISECONDS);
+        Thread.sleep(5);
+        assertFalse("Check that task hasn't run yet", ran > 0);
+        st1.reschedule(20, TimeUnit.MILLISECONDS);
+        Thread.sleep(5);
+        assertFalse("Check that task hasn't run yet", ran > 0);
+        st1.reschedule(20, TimeUnit.MILLISECONDS);
+        Thread.sleep(5);
+        assertFalse("Check that task hasn't run yet", ran > 0);
+        st1.reschedule(20, TimeUnit.MILLISECONDS);
+        Thread.sleep(5);
+        assertFalse("Check that task hasn't run yet", ran > 0);
+        
+        ses.shutdown();
+        ses.awaitTermination(5, TimeUnit.SECONDS);
+        
+        assertEquals("Check that task ran only once", 1, ran);
+        assertTrue("Check that time passed appropriately: " + (time - start),
+                (time - start) >= TimeUnit.NANOSECONDS.convert(55, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testConcurrentAddDelay() throws InterruptedException {
+        ScheduledExecutorService ses = 
+            Executors.newSingleThreadScheduledExecutor();
+
+        final Object tc = this;
+        SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+            @Override
+            public void run() {
+                synchronized (tc) {
+                    ran += 1;
+                }
+                try {
+                    Thread.sleep(50);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                synchronized (tc) {
+                    finished += 1;
+                    time = System.nanoTime();
+                }
+            }
+        });
+        
+        long start = System.nanoTime();
+        st1.reschedule(5, TimeUnit.MILLISECONDS);
+        Thread.sleep(20);
+        assertEquals("Check that task started", 1, ran);
+        assertEquals("Check that task not finished", 0, finished);
+        st1.reschedule(75, TimeUnit.MILLISECONDS);
+        assertTrue("Check task running state true", st1.context.taskRunning);
+        assertTrue("Check task should run state true", st1.context.taskShouldRun);
+        assertEquals("Check that task started", 1, ran);
+        assertEquals("Check that task not finished", 0, finished);
+
+        Thread.sleep(150);
+
+        assertTrue("Check task running state false", !st1.context.taskRunning);
+        assertTrue("Check task should run state false", !st1.context.taskShouldRun);
+        assertEquals("Check that task ran exactly twice", 2, ran);
+        assertEquals("Check that task finished exactly twice", 2, finished);
+        
+        assertTrue("Check that time passed appropriately: " + (time - start),
+                (time - start) >= TimeUnit.NANOSECONDS.convert(130, TimeUnit.MILLISECONDS));
+        assertTrue("Check that time passed appropriately: " + (time - start),
+                (time - start) <= TimeUnit.NANOSECONDS.convert(160, TimeUnit.MILLISECONDS));
+        
+        ses.shutdown();
+        ses.awaitTermination(5, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testConcurrentAddDelay2() throws InterruptedException {
+        ScheduledExecutorService ses = 
+            Executors.newSingleThreadScheduledExecutor();
+
+        final Object tc = this;
+        SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+            @Override
+            public void run() {
+                synchronized (tc) {
+                    ran += 1;
+                }
+                try {
+                    Thread.sleep(50);
+                } catch (InterruptedException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+                synchronized (tc) {
+                    finished += 1;
+                    time = System.nanoTime();
+                }
+            }
+        });
+
+        long start = System.nanoTime();
+        st1.reschedule(5, TimeUnit.MILLISECONDS);
+        Thread.sleep(20);
+        assertEquals("Check that task started", 1, ran);
+        assertEquals("Check that task not finished", 0, finished);
+        st1.reschedule(25, TimeUnit.MILLISECONDS);
+        assertTrue("Check task running state true", st1.context.taskRunning);
+        assertTrue("Check task should run state true", st1.context.taskShouldRun);
+        assertEquals("Check that task started", 1, ran);
+        assertEquals("Check that task not finished", 0, finished);
+
+        Thread.sleep(150);
+
+        assertTrue("Check task running state false", !st1.context.taskRunning);
+        assertTrue("Check task should run state false", !st1.context.taskShouldRun);
+        assertEquals("Check that task ran exactly twice", 2, ran);
+        assertEquals("Check that task finished exactly twice", 2, finished);
+        
+        assertTrue("Check that time passed appropriately: " + (time - start),
+                (time - start) >= TimeUnit.NANOSECONDS.convert(100, TimeUnit.MILLISECONDS));
+        assertTrue("Check that time passed appropriately: " + (time - start),
+                (time - start) <= TimeUnit.NANOSECONDS.convert(125, TimeUnit.MILLISECONDS));
+        
+        ses.shutdown();
+        ses.awaitTermination(5, TimeUnit.SECONDS);
+    }
+
+
+    @Test
+    public void testConcurrentAddNoDelay() throws InterruptedException {
+        ScheduledExecutorService ses = 
+            Executors.newSingleThreadScheduledExecutor();
+
+        final Object tc = this;
+        SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+            @Override
+            public void run() {
+                synchronized (tc) {
+                    ran += 1;
+                }
+                try {
+                    Thread.sleep(50);
+                } catch (InterruptedException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+                synchronized (tc) {
+                    finished += 1;
+                    time = System.nanoTime();
+                }
+            }
+        });
+        
+        long start = System.nanoTime();
+        st1.reschedule(0, null);
+        Thread.sleep(20);
+        assertEquals("Check that task started", 1, ran);
+        assertEquals("Check that task not finished", 0, finished);
+        st1.reschedule(0, null);
+        assertTrue("Check task running state true", st1.context.taskRunning);
+        assertTrue("Check task should run state true", st1.context.taskShouldRun);
+        assertEquals("Check that task started", 1, ran);
+        assertEquals("Check that task not finished", 0, finished);
+
+        Thread.sleep(150);
+
+        assertTrue("Check task running state false", !st1.context.taskRunning);
+        assertTrue("Check task should run state false", !st1.context.taskShouldRun);
+        assertEquals("Check that task ran exactly twice", 2, ran);
+        assertEquals("Check that task finished exactly twice", 2, finished);
+        
+        assertTrue("Check that time passed appropriately: " + (time - start),
+                (time - start) >= TimeUnit.NANOSECONDS.convert(90, TimeUnit.MILLISECONDS));
+        assertTrue("Check that time passed appropriately: " + (time - start),
+                (time - start) <= TimeUnit.NANOSECONDS.convert(130, TimeUnit.MILLISECONDS));
+        
+        ses.shutdown();
+        ses.awaitTermination(5, TimeUnit.SECONDS);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
new file mode 100644
index 0000000..e266d4a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
@@ -0,0 +1,1756 @@
+/**
+ *    Copyright 2011, Big Switch Networks, Inc.
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+
+import static org.easymock.EasyMock.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.easymock.EasyMock.expectLastCall;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.SwitchPort.ErrorStatus;
+import net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl.ClassState;
+import net.floodlightcontroller.devicemanager.test.MockEntityClassifier;
+import net.floodlightcontroller.devicemanager.test.MockEntityClassifierMac;
+import net.floodlightcontroller.devicemanager.test.MockFlexEntityClassifier;
+import net.floodlightcontroller.flowcache.FlowReconcileManager;
+import net.floodlightcontroller.flowcache.IFlowReconcileService;
+import net.floodlightcontroller.packet.ARP;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.ITopologyService;
+import static org.junit.Assert.*;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DeviceManagerImplTest extends FloodlightTestCase {
+
+    protected static Logger logger =
+            LoggerFactory.getLogger(DeviceManagerImplTest.class);
+
+    protected OFPacketIn packetIn_1, packetIn_2, packetIn_3;
+    protected IPacket testARPReplyPacket_1, testARPReplyPacket_2,
+    testARPReplyPacket_3;
+    protected IPacket testARPReqPacket_1, testARPReqPacket_2;
+    protected byte[] testARPReplyPacket_1_Srld, testARPReplyPacket_2_Srld;
+    private byte[] testARPReplyPacket_3_Serialized;
+    MockFloodlightProvider mockFloodlightProvider;
+    DeviceManagerImpl deviceManager;
+    MemoryStorageSource storageSource;
+    FlowReconcileManager flowReconcileMgr;
+
+    private IOFSwitch makeSwitchMock(long id) {
+        IOFSwitch mockSwitch = createMock(IOFSwitch.class);
+        expect(mockSwitch.getId()).andReturn(id).anyTimes();
+        expect(mockSwitch.getStringId()).
+        andReturn(HexString.toHexString(id, 6)).anyTimes();
+        expect(mockSwitch.getPort(anyShort())).
+        andReturn(new OFPhysicalPort()).anyTimes();
+        expect(mockSwitch.portEnabled(isA(OFPhysicalPort.class))).
+        andReturn(true).anyTimes();
+        return mockSwitch;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        RestApiServer restApi = new RestApiServer();
+        MockThreadPoolService tp = new MockThreadPoolService();
+        ITopologyService topology = createMock(ITopologyService.class);
+        fmc.addService(IThreadPoolService.class, tp);
+        mockFloodlightProvider = getMockFloodlightProvider();
+        deviceManager = new DeviceManagerImpl();
+        flowReconcileMgr = new FlowReconcileManager();
+        DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier();
+        fmc.addService(IDeviceService.class, deviceManager);
+        storageSource = new MemoryStorageSource();
+        fmc.addService(IStorageSourceService.class, storageSource);
+        fmc.addService(IFloodlightProviderService.class, mockFloodlightProvider);
+        fmc.addService(IRestApiService.class, restApi);
+        fmc.addService(IFlowReconcileService.class, flowReconcileMgr);
+        fmc.addService(IEntityClassifierService.class, entityClassifier);
+        fmc.addService(ITopologyService.class, topology);
+        tp.init(fmc);
+        restApi.init(fmc);
+        storageSource.init(fmc);
+        deviceManager.init(fmc);
+        flowReconcileMgr.init(fmc);
+        entityClassifier.init(fmc);
+        storageSource.startUp(fmc);
+        deviceManager.startUp(fmc);
+        flowReconcileMgr.startUp(fmc);
+        tp.startUp(fmc);
+        entityClassifier.startUp(fmc);
+
+        reset(topology);
+        topology.addListener(deviceManager);
+        expectLastCall().anyTimes();
+        replay(topology);
+
+        IOFSwitch mockSwitch1 = makeSwitchMock(1L);
+        IOFSwitch mockSwitch10 = makeSwitchMock(10L);
+        IOFSwitch mockSwitch5 = makeSwitchMock(5L);
+        IOFSwitch mockSwitch50 = makeSwitchMock(50L);
+        Map<Long, IOFSwitch> switches = new HashMap<Long,IOFSwitch>();
+        switches.put(1L, mockSwitch1);
+        switches.put(10L, mockSwitch10);
+        switches.put(5L, mockSwitch5);
+        switches.put(50L, mockSwitch50);
+        mockFloodlightProvider.setSwitches(switches);
+
+        replay(mockSwitch1, mockSwitch5, mockSwitch10, mockSwitch50);
+
+        // Build our test packet
+        this.testARPReplyPacket_1 = new Ethernet()
+        .setSourceMACAddress("00:44:33:22:11:01")
+        .setDestinationMACAddress("00:11:22:33:44:55")
+        .setEtherType(Ethernet.TYPE_ARP)
+        .setVlanID((short)5)
+        .setPayload(
+                    new ARP()
+                    .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                    .setProtocolType(ARP.PROTO_TYPE_IP)
+                    .setHardwareAddressLength((byte) 6)
+                    .setProtocolAddressLength((byte) 4)
+                    .setOpCode(ARP.OP_REPLY)
+                    .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:01"))
+                    .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+                    .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+                    .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+        this.testARPReplyPacket_1_Srld = testARPReplyPacket_1.serialize();
+
+        // Another test packet with a different source IP
+        this.testARPReplyPacket_2 = new Ethernet()
+        .setSourceMACAddress("00:44:33:22:11:01")
+        .setDestinationMACAddress("00:11:22:33:44:55")
+        .setEtherType(Ethernet.TYPE_ARP)
+        .setPayload(
+                    new ARP()
+                    .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                    .setProtocolType(ARP.PROTO_TYPE_IP)
+                    .setHardwareAddressLength((byte) 6)
+                    .setProtocolAddressLength((byte) 4)
+                    .setOpCode(ARP.OP_REPLY)
+                    .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:01"))
+                    .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+                    .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+                    .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+        this.testARPReplyPacket_2_Srld = testARPReplyPacket_2.serialize();
+
+        this.testARPReplyPacket_3 = new Ethernet()
+        .setSourceMACAddress("00:44:33:22:11:01")
+        .setDestinationMACAddress("00:11:22:33:44:55")
+        .setEtherType(Ethernet.TYPE_ARP)
+        .setPayload(
+                    new ARP()
+                    .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                    .setProtocolType(ARP.PROTO_TYPE_IP)
+                    .setHardwareAddressLength((byte) 6)
+                    .setProtocolAddressLength((byte) 4)
+                    .setOpCode(ARP.OP_REPLY)
+                    .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:01"))
+                    .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.3"))
+                    .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+                    .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+        this.testARPReplyPacket_3_Serialized = testARPReplyPacket_3.serialize();
+        
+        // Build the PacketIn
+        this.packetIn_1 = ((OFPacketIn) mockFloodlightProvider.
+                getOFMessageFactory().getMessage(OFType.PACKET_IN))
+                .setBufferId(-1)
+                .setInPort((short) 1)
+                .setPacketData(this.testARPReplyPacket_1_Srld)
+                .setReason(OFPacketInReason.NO_MATCH)
+                .setTotalLength((short) this.testARPReplyPacket_1_Srld.length);
+
+        // Build the PacketIn
+        this.packetIn_2 = ((OFPacketIn) mockFloodlightProvider.
+                getOFMessageFactory().getMessage(OFType.PACKET_IN))
+                .setBufferId(-1)
+                .setInPort((short) 1)
+                .setPacketData(this.testARPReplyPacket_2_Srld)
+                .setReason(OFPacketInReason.NO_MATCH)
+                .setTotalLength((short) this.testARPReplyPacket_2_Srld.length);
+
+        // Build the PacketIn
+        this.packetIn_3 = ((OFPacketIn) mockFloodlightProvider.
+                getOFMessageFactory().getMessage(OFType.PACKET_IN))
+                .setBufferId(-1)
+                .setInPort((short) 1)
+                .setPacketData(this.testARPReplyPacket_3_Serialized)
+                .setReason(OFPacketInReason.NO_MATCH)
+                .setTotalLength((short) this.testARPReplyPacket_3_Serialized.length);
+    }
+
+
+
+
+    
+    @Test
+    public void testLastSeen() throws Exception {
+        Calendar c = Calendar.getInstance();
+        Date d1 = c.getTime();
+        Entity entity1 = new Entity(1L, null, null, null, null, d1);
+        c.add(Calendar.SECOND, 1);
+        Entity entity2 = new Entity(1L, null, 1, null, null, c.getTime());
+
+        IDevice d = deviceManager.learnDeviceByEntity(entity2);
+        assertEquals(c.getTime(), d.getLastSeen());
+        d = deviceManager.learnDeviceByEntity(entity1);
+        assertEquals(c.getTime(), d.getLastSeen());
+
+        deviceManager.startUp(null);
+        d = deviceManager.learnDeviceByEntity(entity1);
+        assertEquals(d1, d.getLastSeen());
+        d = deviceManager.learnDeviceByEntity(entity2);
+        assertEquals(c.getTime(), d.getLastSeen());
+    }
+
+    @Test
+    public void testEntityLearning() throws Exception {
+        IDeviceListener mockListener =
+                createStrictMock(IDeviceListener.class);
+
+        deviceManager.addListener(mockListener);
+        deviceManager.entityClassifier= new MockEntityClassifier();
+        deviceManager.startUp(null);
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        expect(mockTopology.getL2DomainId(anyLong())).
+        andReturn(1L).anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())).
+        andReturn(false).anyTimes();
+
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).andReturn(true).anyTimes();
+        expect(mockTopology.isConsistent(10L, (short)1, 10L, (short)1)).
+        andReturn(true).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)1, 1L, (short)1)).
+        andReturn(true).anyTimes();
+        expect(mockTopology.isConsistent(50L, (short)3, 50L, (short)3)).
+        andReturn(true).anyTimes();
+
+        Date topologyUpdateTime = new Date();
+        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+        anyTimes();
+
+        deviceManager.topology = mockTopology;
+
+        Entity entity1 = new Entity(1L, null, null, 1L, 1, new Date());
+        Entity entity2 = new Entity(1L, null, null, 10L, 1, new Date());
+        Entity entity3 = new Entity(1L, null, 1, 10L, 1, new Date());
+        Entity entity4 = new Entity(1L, null, 1, 1L, 1, new Date());
+        Entity entity5 = new Entity(2L, (short)4, 1, 5L, 2, new Date());
+        Entity entity6 = new Entity(2L, (short)4, 1, 50L, 3, new Date());
+        Entity entity7 = new Entity(2L, (short)4, 2, 50L, 3, new Date());
+
+        mockListener.deviceAdded(isA(IDevice.class));
+        replay(mockListener, mockTopology);
+
+        Device d1 = deviceManager.learnDeviceByEntity(entity1);
+        assertSame(d1, deviceManager.learnDeviceByEntity(entity1));
+        assertSame(d1, deviceManager.findDeviceByEntity(entity1));
+        assertEquals(DefaultEntityClassifier.entityClass ,
+                          d1.entityClass);
+        assertArrayEquals(new Short[] { -1 }, d1.getVlanId());
+        assertArrayEquals(new Integer[] { }, d1.getIPv4Addresses());
+
+        assertEquals(1, deviceManager.getAllDevices().size());
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceAdded(isA(IDevice.class));
+        replay(mockListener);
+
+        Device d2 = deviceManager.learnDeviceByEntity(entity2);
+        assertFalse(d1.equals(d2));
+        assertNotSame(d1, d2);
+        assertNotSame(d1.getDeviceKey(), d2.getDeviceKey());
+        assertEquals(MockEntityClassifier.testEC, d2.entityClass);
+        assertArrayEquals(new Short[] { -1 }, d2.getVlanId());
+        assertArrayEquals(new Integer[] { }, d2.getIPv4Addresses());
+
+        assertEquals(2, deviceManager.getAllDevices().size());
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+        replay(mockListener);
+
+        Device d3 = deviceManager.learnDeviceByEntity(entity3);
+        assertNotSame(d2, d3);
+        assertEquals(d2.getDeviceKey(), d3.getDeviceKey());
+        assertEquals(MockEntityClassifier.testEC, d3.entityClass);
+        assertArrayEquals(new Integer[] { 1 },
+                          d3.getIPv4Addresses());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
+                          d3.getAttachmentPoints());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
+                          d3.getAttachmentPoints(true));
+        assertArrayEquals(new Short[] { -1 },
+                          d3.getVlanId());
+
+        assertEquals(2, deviceManager.getAllDevices().size());
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+        replay(mockListener);
+
+        Device d4 = deviceManager.learnDeviceByEntity(entity4);
+        assertNotSame(d1, d4);
+        assertEquals(d1.getDeviceKey(), d4.getDeviceKey());
+        assertEquals(DefaultEntityClassifier.entityClass, d4.entityClass);
+        assertArrayEquals(new Integer[] { 1 },
+                          d4.getIPv4Addresses());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+                          d4.getAttachmentPoints());
+        assertArrayEquals(new Short[] { -1 },
+                          d4.getVlanId());
+
+        assertEquals(2, deviceManager.getAllDevices().size());
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceAdded((isA(IDevice.class)));
+        replay(mockListener);
+
+        Device d5 = deviceManager.learnDeviceByEntity(entity5);
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 2) },
+                          d5.getAttachmentPoints());
+        assertArrayEquals(new Short[] { (short) 4 },
+                          d5.getVlanId());
+        assertEquals(2L, d5.getMACAddress());
+        assertEquals("00:00:00:00:00:02", d5.getMACAddressString());
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceAdded(isA(IDevice.class));
+        replay(mockListener);
+
+        Device d6 = deviceManager.learnDeviceByEntity(entity6);
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(50L, 3) },
+                          d6.getAttachmentPoints());
+        assertArrayEquals(new Short[] { (short) 4 },
+                          d6.getVlanId());
+
+        assertEquals(4, deviceManager.getAllDevices().size());
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+        replay(mockListener);
+
+        Device d7 = deviceManager.learnDeviceByEntity(entity7);
+        assertNotSame(d6, d7);
+        assertEquals(d6.getDeviceKey(), d7.getDeviceKey());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(50L, 3) },
+                          d7.getAttachmentPoints());
+        assertArrayEquals(new Short[] { (short) 4 },
+                          d7.getVlanId());
+
+        assertEquals(4, deviceManager.getAllDevices().size());
+        verify(mockListener);
+        
+        
+        reset(mockListener);
+        replay(mockListener);
+        
+        reset(deviceManager.topology);
+        deviceManager.topology.addListener(deviceManager);
+        expectLastCall().times(1);
+        replay(deviceManager.topology);
+
+        deviceManager.entityClassifier = new MockEntityClassifierMac();
+        deviceManager.startUp(null);
+        Entity entityNoClass = new Entity(5L, (short)1, 5, -1L, 1, new Date());
+        assertEquals(null, deviceManager.learnDeviceByEntity(entityNoClass));
+        
+        verify(mockListener);
+    }
+    
+
+    @Test
+    public void testAttachmentPointLearning() throws Exception {
+        IDeviceListener mockListener =
+                createStrictMock(IDeviceListener.class);
+
+        deviceManager.addListener(mockListener);
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        expect(mockTopology.getL2DomainId(1L)).
+        andReturn(1L).anyTimes();
+        expect(mockTopology.getL2DomainId(5L)).
+        andReturn(1L).anyTimes();
+        expect(mockTopology.getL2DomainId(10L)).
+        andReturn(10L).anyTimes();
+        expect(mockTopology.getL2DomainId(50L)).
+        andReturn(10L).anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+                                                    anyLong(), anyShort())).andReturn(false).anyTimes();
+
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).andReturn(true).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(5L, (short)1, 10L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(10L, (short)1, 50L, (short)1)).
+        andReturn(false).anyTimes();
+
+        Date topologyUpdateTime = new Date();
+        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+        anyTimes();
+
+        replay(mockTopology);
+
+        deviceManager.topology = mockTopology;
+
+        Calendar c = Calendar.getInstance();
+        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+        Entity entity0 = new Entity(1L, null, null, null, null, c.getTime());
+        c.add(Calendar.SECOND, 1);
+        Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime());
+        c.add(Calendar.SECOND, 1);
+        Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime());
+        c.add(Calendar.SECOND, 1);
+        Entity entity4 = new Entity(1L, null, null, 50L, 1, c.getTime());
+
+        IDevice d;
+        SwitchPort[] aps;
+        Integer[] ips;
+
+        mockListener.deviceAdded(isA(IDevice.class));
+        replay(mockListener);
+
+        deviceManager.learnDeviceByEntity(entity1);
+        d = deviceManager.learnDeviceByEntity(entity0);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+        ips = d.getIPv4Addresses();
+        assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceMoved((isA(IDevice.class)));
+        replay(mockListener);
+
+        d = deviceManager.learnDeviceByEntity(entity2);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps);
+        ips = d.getIPv4Addresses();
+        assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceMoved((isA(IDevice.class)));
+        replay(mockListener);
+
+        d = deviceManager.learnDeviceByEntity(entity3);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertArrayEquals(new SwitchPort[] {new SwitchPort(5L, 1), new SwitchPort(10L, 1)}, aps);
+        ips = d.getIPv4Addresses();
+        assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceMoved((isA(IDevice.class)));
+        replay(mockListener);
+
+        d = deviceManager.learnDeviceByEntity(entity4);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1),
+                                             new SwitchPort(50L, 1) }, aps);
+        ips = d.getIPv4Addresses();
+        assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
+    }
+
+    @Test
+    public void testAttachmentPointSuppression() throws Exception {
+        IDeviceListener mockListener =
+                createStrictMock(IDeviceListener.class);
+
+        deviceManager.addListener(mockListener);
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        expect(mockTopology.getL2DomainId(1L)).
+        andReturn(1L).anyTimes();
+        expect(mockTopology.getL2DomainId(5L)).
+        andReturn(1L).anyTimes();
+        expect(mockTopology.getL2DomainId(10L)).
+        andReturn(10L).anyTimes();
+        expect(mockTopology.getL2DomainId(50L)).
+        andReturn(10L).anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+                                                    anyLong(), anyShort())).andReturn(false).anyTimes();
+
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).andReturn(true).anyTimes();
+        expect(mockTopology.isConsistent(5L, (short)1, 50L, (short)1)).
+        andReturn(false).anyTimes();
+
+        Date topologyUpdateTime = new Date();
+        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+        anyTimes();
+
+        replay(mockTopology);
+
+        deviceManager.topology = mockTopology;
+        // suppress (1L, 1) and (10L, 1)
+        deviceManager.addSuppressAPs(1L, (short)1);
+        deviceManager.addSuppressAPs(10L, (short)1);
+
+        Calendar c = Calendar.getInstance();
+        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+        Entity entity0 = new Entity(1L, null, null, null, null, c.getTime());
+        c.add(Calendar.SECOND, 1);
+        Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime());
+        c.add(Calendar.SECOND, 1);
+        Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime());
+        c.add(Calendar.SECOND, 1);
+        Entity entity4 = new Entity(1L, null, null, 50L, 1, c.getTime());
+
+        IDevice d;
+        SwitchPort[] aps;
+        Integer[] ips;
+
+        mockListener.deviceAdded(isA(IDevice.class));
+        replay(mockListener);
+
+        deviceManager.learnDeviceByEntity(entity1);
+        d = deviceManager.learnDeviceByEntity(entity0);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertEquals(aps.length, 0);
+        ips = d.getIPv4Addresses();
+        assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceMoved((isA(IDevice.class)));
+        replay(mockListener);
+
+        d = deviceManager.learnDeviceByEntity(entity2);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps);
+        ips = d.getIPv4Addresses();
+        assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
+
+        reset(mockListener);
+        mockListener.deviceMoved((isA(IDevice.class)));
+        replay(mockListener);
+
+        d = deviceManager.learnDeviceByEntity(entity3);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps);
+        ips = d.getIPv4Addresses();
+        assertArrayEquals(new Integer[] { 1 }, ips);
+        //verify(mockListener);  // There is no device movement here; no not needed.
+
+        reset(mockListener);
+        mockListener.deviceMoved((isA(IDevice.class)));
+        replay(mockListener);
+
+        d = deviceManager.learnDeviceByEntity(entity4);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1),
+                                             new SwitchPort(50L, 1) }, aps);
+        ips = d.getIPv4Addresses();
+        assertArrayEquals(new Integer[] { 1 }, ips);
+        verify(mockListener);
+    }
+
+    @Test
+    public void testBDAttachmentPointLearning() throws Exception {
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        expect(mockTopology.getL2DomainId(anyLong())).
+        andReturn(1L).anyTimes();
+        expect(mockTopology.isAttachmentPointPort(anyLong(), anyShort())).
+        andReturn(true).anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(1L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(1L, (short)2)).
+        andReturn(true).anyTimes();
+        expect(mockTopology.isInSameBroadcastDomain(1L, (short)1,
+                                                    1L, (short)2)).andReturn(true).anyTimes();
+        expect(mockTopology.isInSameBroadcastDomain(1L, (short)2,
+                                                    1L, (short)1)).andReturn(true).anyTimes();
+        expect(mockTopology.isConsistent(anyLong(), anyShort(), anyLong(), anyShort())).andReturn(false).anyTimes();
+
+        Date topologyUpdateTime = new Date();
+        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+        anyTimes();
+
+        replay(mockTopology);
+
+        deviceManager.topology = mockTopology;
+
+        Calendar c = Calendar.getInstance();
+        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+        c.add(Calendar.MILLISECOND,
+              (int)AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT/ 2);
+        Entity entity2 = new Entity(1L, null, null, 1L, 2, c.getTime());
+        c.add(Calendar.MILLISECOND,
+              (int)AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT / 2 + 1);
+        Entity entity3 = new Entity(1L, null, null, 1L, 2, c.getTime());
+
+        IDevice d;
+        SwitchPort[] aps;
+
+        d = deviceManager.learnDeviceByEntity(entity1);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+
+        // this timestamp is too soon; don't switch
+        d = deviceManager.learnDeviceByEntity(entity2);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+
+        // it should switch when we learn with a timestamp after the
+        // timeout
+        d = deviceManager.learnDeviceByEntity(entity3);
+        assertEquals(1, deviceManager.getAllDevices().size());
+        aps = d.getAttachmentPoints();
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 2) }, aps);
+    }
+
+
+    @Test
+    public void testPacketIn() throws Exception {
+        byte[] dataLayerSource =
+                ((Ethernet)this.testARPReplyPacket_1).getSourceMACAddress();
+
+        // Mock up our expected behavior
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        deviceManager.topology = mockTopology;
+        expect(mockTopology.isAttachmentPointPort(EasyMock.anyLong(),
+                                                  EasyMock.anyShort())).
+                                                  andReturn(true).anyTimes();
+        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+                                         EasyMock.anyShort(),
+                                         EasyMock.anyLong(),
+                                         EasyMock.anyShort())).andReturn(false).
+                                         anyTimes();
+        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+        replay(mockTopology);
+
+        Date currentDate = new Date();
+
+        // build our expected Device
+        Integer ipaddr = IPv4.toIPv4Address("192.168.1.1");
+        Device device =
+                new Device(deviceManager,
+                           new Long(deviceManager.deviceKeyCounter),
+                           new Entity(Ethernet.toLong(dataLayerSource),
+                                      (short)5,
+                                      ipaddr,
+                                      1L,
+                                      1,
+                                      currentDate),
+                                      DefaultEntityClassifier.entityClass);
+
+
+
+
+        // Get the listener and trigger the packet in
+        IOFSwitch switch1 = mockFloodlightProvider.getSwitches().get(1L);
+        mockFloodlightProvider.dispatchMessage(switch1, this.packetIn_1);
+
+        // Verify the replay matched our expectations
+        // verify(mockTopology);
+
+        // Verify the device
+        Device rdevice = (Device)
+                deviceManager.findDevice(Ethernet.toLong(dataLayerSource),
+                                         (short)5, null, null, null);
+
+        assertEquals(device, rdevice);
+        assertEquals(new Short((short)5), rdevice.getVlanId()[0]);
+
+        Device result = null;
+        Iterator<? extends IDevice> dstiter =
+                deviceManager.queryClassDevices(device, null, null, ipaddr,
+                                                null, null);
+        if (dstiter.hasNext()) {
+            result = (Device)dstiter.next();
+        }
+
+        assertEquals(device, result);
+
+        device =
+                new Device(device,
+                           new Entity(Ethernet.toLong(dataLayerSource),
+                                      (short)5,
+                                      ipaddr,
+                                      5L,
+                                      2,
+                                      currentDate));
+
+        reset(mockTopology);
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(true).
+                                                  anyTimes();
+        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+                                         EasyMock.anyShort(),
+                                         EasyMock.anyLong(),
+                                         EasyMock.anyShort())).andReturn(false).
+                                         anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+                                                  EasyMock.anyShort()))
+                                                  .andReturn(false)
+                                                  .anyTimes();
+        expect(mockTopology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+        expect(mockTopology.getL2DomainId(5L)).andReturn(1L).anyTimes();
+        expect(mockTopology.isInSameBroadcastDomain(1L, (short)1, 5L, (short)2)).
+               andReturn(false).anyTimes();
+
+        // Start recording the replay on the mocks
+        replay(mockTopology);
+        // Get the listener and trigger the packet in
+        IOFSwitch switch5 = mockFloodlightProvider.getSwitches().get(5L);
+        mockFloodlightProvider.
+        dispatchMessage(switch5, this.packetIn_1.setInPort((short)2));
+
+        // Verify the replay matched our expectations
+        verify(mockTopology);
+
+        // Verify the device
+        rdevice = (Device)
+                deviceManager.findDevice(Ethernet.toLong(dataLayerSource),
+                                         (short)5, null, null, null);
+        assertEquals(device, rdevice);
+    }
+
+    
+    /**
+     * Note: Entity expiration does not result in device moved notification.
+     * @throws Exception
+     */
+    public void doTestEntityExpiration() throws Exception {
+        IDeviceListener mockListener =
+                createStrictMock(IDeviceListener.class);
+        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(true).anyTimes();
+
+        expect(mockTopology.isBroadcastDomainPort(1L, (short)1)).andReturn(false).anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(5L, (short)1)).andReturn(false).anyTimes();
+        expect(mockTopology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+        expect(mockTopology.getL2DomainId(5L)).andReturn(5L).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+        andReturn(false).anyTimes();
+
+        Date topologyUpdateTime = new Date();
+        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+        anyTimes();
+
+        replay(mockTopology);
+        deviceManager.topology = mockTopology;
+
+        Calendar c = Calendar.getInstance();
+        Entity entity1 = new Entity(1L, null, 2, 1L, 1, c.getTime());
+        c.add(Calendar.MILLISECOND, -DeviceManagerImpl.ENTITY_TIMEOUT-1);
+        Entity entity2 = new Entity(1L, null, 1, 5L, 1, c.getTime());
+
+        deviceManager.learnDeviceByEntity(entity1);
+        IDevice d = deviceManager.learnDeviceByEntity(entity2);
+        assertArrayEquals(new Integer[] { 1, 2 }, d.getIPv4Addresses());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 1)},
+                                             d.getAttachmentPoints());
+        Iterator<? extends IDevice> diter =
+                deviceManager.queryClassDevices(d, null, null, 1, null, null);
+        assertTrue(diter.hasNext());
+        assertEquals(d.getDeviceKey(), diter.next().getDeviceKey());
+        diter = deviceManager.queryClassDevices(d, null, null, 2, null, null);
+        assertTrue(diter.hasNext());
+        assertEquals(d.getDeviceKey(), diter.next().getDeviceKey());
+
+
+        deviceManager.addListener(mockListener);
+        replay(mockListener);
+        deviceManager.entityCleanupTask.reschedule(0, null);
+
+        d = deviceManager.getDevice(d.getDeviceKey());
+        assertArrayEquals(new Integer[] { 2 }, d.getIPv4Addresses());
+
+        // Attachment points are not removed, previous ones are still valid.
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 1) },
+                          d.getAttachmentPoints());
+        diter = deviceManager.queryClassDevices(d, null, null, 2, null, null);
+        assertTrue(diter.hasNext());
+        assertEquals(d.getDeviceKey(), diter.next().getDeviceKey());
+        diter = deviceManager.queryClassDevices(d, null, null, 1, null, null);
+        assertFalse(diter.hasNext());
+
+        d = deviceManager.findDevice(1L, null, null, null, null);
+        assertArrayEquals(new Integer[] { 2 }, d.getIPv4Addresses());
+
+        // Attachment points are not removed, previous ones are still valid.
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 1) },
+                          d.getAttachmentPoints());
+
+        verify(mockListener);
+    }
+
+    public void doTestDeviceExpiration() throws Exception {
+        IDeviceListener mockListener =
+                createStrictMock(IDeviceListener.class);
+        mockListener.deviceRemoved(isA(IDevice.class));
+        
+        Calendar c = Calendar.getInstance();
+        c.add(Calendar.MILLISECOND, -DeviceManagerImpl.ENTITY_TIMEOUT-1);
+        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+        Entity entity2 = new Entity(1L, null, 2, 5L, 1, c.getTime());
+        
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        deviceManager.topology = mockTopology;
+
+        expect(mockTopology.isAttachmentPointPort(EasyMock.anyLong(),
+                                           EasyMock.anyShort())).
+                                           andReturn(true).
+                                           anyTimes();
+        expect(mockTopology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+        expect(mockTopology.getL2DomainId(5L)).andReturn(1L).anyTimes();
+        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+                                         EasyMock.anyShort(),
+                                         EasyMock.anyLong(),
+                                         EasyMock.anyShort())).andReturn(false).
+                                         anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+                                                  EasyMock.anyShort())).
+                                                  andReturn(false).anyTimes();
+        replay(mockTopology);
+
+        IDevice d = deviceManager.learnDeviceByEntity(entity2);
+        d = deviceManager.learnDeviceByEntity(entity1);
+        assertArrayEquals(new Integer[] { 1, 2 }, d.getIPv4Addresses());
+
+        deviceManager.addListener(mockListener);
+        replay(mockListener);
+        deviceManager.entityCleanupTask.reschedule(0, null);
+
+        IDevice r = deviceManager.getDevice(d.getDeviceKey());
+        assertNull(r);
+        Iterator<? extends IDevice> diter =
+                deviceManager.queryClassDevices(d, null, null, 1, null, null);
+        assertFalse(diter.hasNext());
+
+        r = deviceManager.findDevice(1L, null, null, null, null);
+        assertNull(r);
+        
+        verify(mockListener);
+    }
+    
+    /*
+     * A ConcurrentHashMap for devices (deviceMap) that can be used to test 
+     * code that specially handles concurrent modification situations. In
+     * particular, we overwrite values() and will replace / remove all the
+     * elements returned by values. 
+     * 
+     * The remove flag in the constructor specifies if devices returned by 
+     * values() should be removed or replaced.
+     */
+    protected static class ConcurrentlyModifiedDeviceMap
+                            extends ConcurrentHashMap<Long, Device> {
+        private static final long serialVersionUID = 7784938535441180562L;
+        protected boolean remove;
+        public ConcurrentlyModifiedDeviceMap(boolean remove) {
+            super();
+            this.remove = remove;
+        }
+        
+        @Override
+        public Collection<Device> values() {
+            // Get the values from the real map and copy them since
+            // the collection returned by values can reflect changed
+            Collection<Device> devs = new ArrayList<Device>(super.values());
+            for (Device d: devs) {
+                if (remove) {
+                    // We remove the device from the underlying map 
+                    super.remove(d.getDeviceKey());
+                } else {
+                    super.remove(d.getDeviceKey());
+                    // We add a different Device instance with the same
+                    // key to the map. We'll do some hackery so the device
+                    // is different enough to compare differently in equals 
+                    // but otherwise looks the same.
+                    // It's ugly but it works. 
+                    Entity[] curEntities = new Entity[d.getEntities().length];
+                    int i = 0;
+                    // clone entities
+                    for (Entity e: d.getEntities()) {
+                        curEntities[i] = new Entity (e.macAddress,
+                                                     e.vlan,
+                                                     e.ipv4Address,
+                                                     e.switchDPID,
+                                                     e.switchPort,
+                                                     e.lastSeenTimestamp);
+                        if (e.vlan == null) 
+                            curEntities[i].vlan = (short)1;
+                        else 
+                            curEntities[i].vlan = (short)((e.vlan + 1 % 4095)+1);
+                        i++;
+                    }
+                    Device newDevice = new Device(d, curEntities[0]);
+                    newDevice.entities = curEntities;
+                    assertEquals(false, newDevice.equals(d));
+                    super.put(newDevice.getDeviceKey(), newDevice);
+                }
+            }
+            return devs; 
+        }
+    }
+   
+    @Test
+    public void testEntityExpiration() throws Exception {
+        doTestEntityExpiration();
+    }
+    
+    @Test
+    public void testDeviceExpiration() throws Exception {
+        doTestDeviceExpiration();
+    }
+    
+    /* Test correct entity cleanup behavior when a concurrent modification
+     * occurs. 
+     */
+    @Test
+    public void testEntityExpirationConcurrentModification() throws Exception {
+        deviceManager.deviceMap = new ConcurrentlyModifiedDeviceMap(false);
+        doTestEntityExpiration();
+    }
+    
+    /* Test correct entity cleanup behavior when a concurrent remove
+     * occurs. 
+     */
+    @Test
+    public void testDeviceExpirationConcurrentRemove() throws Exception {
+        deviceManager.deviceMap = new ConcurrentlyModifiedDeviceMap(true);
+        doTestDeviceExpiration();
+    }
+    
+    /* Test correct entity cleanup behavior when a concurrent modification
+     * occurs. 
+     */
+    @Test
+    public void testDeviceExpirationConcurrentModification() throws Exception {
+        deviceManager.deviceMap = new ConcurrentlyModifiedDeviceMap(false);
+        doTestDeviceExpiration();
+    }
+    
+
+    @Test
+    public void testAttachmentPointFlapping() throws Exception {
+        Calendar c = Calendar.getInstance();
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).andReturn(true).anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(false).anyTimes();
+        expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+                                                    anyLong(), anyShort())).andReturn(false).anyTimes();
+        expect(mockTopology.getL2DomainId(anyLong())).
+        andReturn(1L).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)1, 1L, (short)1)).
+        andReturn(true).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)1, 10L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(5L, (short)1, 10L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(10L, (short)1, 1L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(5L, (short)1, 1L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(10L, (short)1, 5L, (short)1)).
+        andReturn(false).anyTimes();
+
+        Date topologyUpdateTime = new Date();
+        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+        anyTimes();
+
+
+        replay(mockTopology);
+        deviceManager.topology = mockTopology;
+
+        Entity entity1 = new Entity(1L, null, null, 1L, 1, c.getTime());
+        Entity entity1a = new Entity(1L, null, 1, 1L, 1, c.getTime());
+        Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime());
+        Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime());
+        entity1.setLastSeenTimestamp(c.getTime());
+        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT/2);
+        entity1a.setLastSeenTimestamp(c.getTime());
+        c.add(Calendar.MILLISECOND, 1);
+        entity2.setLastSeenTimestamp(c.getTime());
+        c.add(Calendar.MILLISECOND, 1);
+        entity3.setLastSeenTimestamp(c.getTime());
+
+
+
+        IDevice d;
+        d = deviceManager.learnDeviceByEntity(entity1);
+        d = deviceManager.learnDeviceByEntity(entity1a);
+        d = deviceManager.learnDeviceByEntity(entity2);
+        d = deviceManager.learnDeviceByEntity(entity3);
+
+        // all entities are active, so entity3 should win
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
+                          d.getAttachmentPoints());
+
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1),},
+                              d.getAttachmentPoints(true));
+
+        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT/4);
+        entity1.setLastSeenTimestamp(c.getTime());
+        d = deviceManager.learnDeviceByEntity(entity1);
+
+        // all are still active; entity3 should still win
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+                          d.getAttachmentPoints());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 1,
+                                                            ErrorStatus.DUPLICATE_DEVICE),
+                                                            new SwitchPort(10L, 1,
+                                                                           ErrorStatus.DUPLICATE_DEVICE) },
+                                                                           d.getAttachmentPoints(true));
+
+        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT+2000);
+        entity1.setLastSeenTimestamp(c.getTime());
+        d = deviceManager.learnDeviceByEntity(entity1);
+
+        assertEquals(entity1.getActiveSince(), entity1.getLastSeenTimestamp());
+        // entity1 should now be the only active entity
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+                          d.getAttachmentPoints());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+                          d.getAttachmentPoints(true));
+    }
+
+
+    @Test
+    public void testAttachmentPointFlappingTwoCluster() throws Exception {
+        Calendar c = Calendar.getInstance();
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).andReturn(true).anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(false).anyTimes();
+        expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+                                                    anyLong(), anyShort())).andReturn(false).anyTimes();
+        expect(mockTopology.getL2DomainId(1L)).
+        andReturn(1L).anyTimes();
+        expect(mockTopology.getL2DomainId(5L)).
+        andReturn(5L).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)1, 1L, (short)2)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)2, 5L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(5L, (short)1, 5L, (short)2)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)2, 1L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)2)).
+        andReturn(false).anyTimes();
+        expect(mockTopology.isConsistent(5L, (short)2, 5L, (short)1)).
+        andReturn(false).anyTimes();
+
+        Date topologyUpdateTime = new Date();
+        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+        anyTimes();
+
+        replay(mockTopology);
+        deviceManager.topology = mockTopology;
+
+        Entity entity1 = new Entity(1L, null, null, 1L, 1, c.getTime());
+        Entity entity2 = new Entity(1L, null, null, 1L, 2, c.getTime());
+        Entity entity3 = new Entity(1L, null, null, 5L, 1, c.getTime());
+        Entity entity4 = new Entity(1L, null, null, 5L, 2, c.getTime());
+        entity1.setLastSeenTimestamp(c.getTime());
+        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT/2);
+        c.add(Calendar.MILLISECOND, 1);
+        entity2.setLastSeenTimestamp(c.getTime());
+        c.add(Calendar.MILLISECOND, 1);
+        entity3.setLastSeenTimestamp(c.getTime());
+        c.add(Calendar.MILLISECOND, 1);
+        entity4.setLastSeenTimestamp(c.getTime());
+
+        deviceManager.learnDeviceByEntity(entity1);
+        deviceManager.learnDeviceByEntity(entity2);
+        deviceManager.learnDeviceByEntity(entity3);
+        IDevice d = deviceManager.learnDeviceByEntity(entity4);
+
+        // all entities are active, so entities 2,4 should win
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 2),
+                                             new SwitchPort(5L, 2) },
+                                             d.getAttachmentPoints());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 2),
+                                             new SwitchPort(5L, 2)},
+                                             d.getAttachmentPoints(true));
+
+        c.add(Calendar.MILLISECOND, 1);
+        entity1.setLastSeenTimestamp(c.getTime());
+        d = deviceManager.learnDeviceByEntity(entity1);
+
+        // all entities are active, so entities 2,4 should win
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 2) },
+                                             d.getAttachmentPoints());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 2),
+                                             new SwitchPort(1L, 2, ErrorStatus.DUPLICATE_DEVICE)},
+                                             d.getAttachmentPoints(true));
+
+        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT+1);
+        entity1.setLastSeenTimestamp(c.getTime());
+        d = deviceManager.learnDeviceByEntity(entity1);
+
+        // entities 3,4 are still in conflict, but 1 should be resolved
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 2) },
+                                             d.getAttachmentPoints());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 2)},
+                                             d.getAttachmentPoints(true));
+
+        entity3.setLastSeenTimestamp(c.getTime());
+        d = deviceManager.learnDeviceByEntity(entity3);
+
+        // no conflicts, 1 and 3 will win
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 1) },
+                                             d.getAttachmentPoints());
+        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+                                             new SwitchPort(5L, 1) },
+                                             d.getAttachmentPoints(true));
+
+    }
+
+    protected void doTestDeviceQuery() throws Exception {
+        Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+        Entity entity2 = new Entity(2L, (short)2, 2, 1L, 2, new Date());
+        Entity entity3 = new Entity(3L, (short)3, 3, 5L, 1, new Date());
+        Entity entity4 = new Entity(4L, (short)4, 3, 5L, 2, new Date());
+        Entity entity5 = new Entity(1L, (short)4, 3, 5L, 2, new Date());
+
+        deviceManager.learnDeviceByEntity(entity1);
+        deviceManager.learnDeviceByEntity(entity2);
+        deviceManager.learnDeviceByEntity(entity3);
+        deviceManager.learnDeviceByEntity(entity4);
+
+        Iterator<? extends IDevice> iter =
+                deviceManager.queryDevices(null, (short)1, 1, null, null);
+        int count = 0;
+        while (iter.hasNext()) {
+            count += 1;
+            iter.next();
+        }
+        assertEquals(1, count);
+
+        iter = deviceManager.queryDevices(null, (short)3, 3, null, null);
+        count = 0;
+        while (iter.hasNext()) {
+            count += 1;
+            iter.next();
+        }
+        assertEquals(1, count);
+
+        iter = deviceManager.queryDevices(null, (short)1, 3, null, null);
+        count = 0;
+        while (iter.hasNext()) {
+            count += 1;
+            iter.next();
+        }
+        assertEquals(0, count);
+
+        deviceManager.learnDeviceByEntity(entity5);
+        iter = deviceManager.queryDevices(null, (short)4, 3, null, null);
+        count = 0;
+        while (iter.hasNext()) {
+            count += 1;
+            iter.next();
+        }
+        assertEquals(2, count);
+    }
+
+    @Test
+    public void testDeviceIndex() throws Exception {
+        EnumSet<IDeviceService.DeviceField> indexFields =
+                EnumSet.noneOf(IDeviceService.DeviceField.class);
+        indexFields.add(IDeviceService.DeviceField.IPV4);
+        indexFields.add(IDeviceService.DeviceField.VLAN);
+        deviceManager.addIndex(false, indexFields);
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        deviceManager.topology = mockTopology;
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(true).anyTimes();
+        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+        replay(mockTopology);
+        doTestDeviceQuery();
+    }
+
+    @Test
+    public void testDeviceQuery() throws Exception {
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        deviceManager.topology = mockTopology;
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(true).anyTimes();
+        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+        replay(mockTopology);
+        
+        doTestDeviceQuery();
+    }
+
+    protected void doTestDeviceClassQuery() throws Exception {
+        Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+        Entity entity2 = new Entity(2L, (short)2, 2, 1L, 2, new Date());
+        Entity entity3 = new Entity(3L, (short)3, 3, 5L, 1, new Date());
+        Entity entity4 = new Entity(4L, (short)4, 3, 5L, 2, new Date());
+        Entity entity5 = new Entity(1L, (short)4, 3, 5L, 2, new Date());
+
+        IDevice d = deviceManager.learnDeviceByEntity(entity1);
+        deviceManager.learnDeviceByEntity(entity2);
+        deviceManager.learnDeviceByEntity(entity3);
+        deviceManager.learnDeviceByEntity(entity4);
+
+        Iterator<? extends IDevice> iter =
+                deviceManager.queryClassDevices(d, null,
+                                                (short)1, 1, null, null);
+        int count = 0;
+        while (iter.hasNext()) {
+            count += 1;
+            iter.next();
+        }
+        assertEquals(1, count);
+
+        iter = deviceManager.queryClassDevices(d, null,
+                                               (short)3, 3, null, null);
+        count = 0;
+        while (iter.hasNext()) {
+            count += 1;
+            iter.next();
+        }
+        assertEquals(1, count);
+
+        iter = deviceManager.queryClassDevices(d, null,
+                                               (short)1, 3, null, null);
+        count = 0;
+        while (iter.hasNext()) {
+            count += 1;
+            iter.next();
+        }
+        assertEquals(0, count);
+
+        deviceManager.learnDeviceByEntity(entity5);
+        iter = deviceManager.queryClassDevices(d, null,
+                                               (short)4, 3, null, null);
+        count = 0;
+        while (iter.hasNext()) {
+            count += 1;
+            iter.next();
+        }
+        assertEquals(2, count);
+    }
+
+    @Test
+    public void testDeviceClassIndex() throws Exception {
+        EnumSet<IDeviceService.DeviceField> indexFields =
+                EnumSet.noneOf(IDeviceService.DeviceField.class);
+        indexFields.add(IDeviceService.DeviceField.IPV4);
+        indexFields.add(IDeviceService.DeviceField.VLAN);
+        deviceManager.addIndex(true, indexFields);
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        deviceManager.topology = mockTopology;
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(true).anyTimes();
+        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+        replay(mockTopology);
+
+        doTestDeviceClassQuery();
+    }
+
+    @Test
+    public void testDeviceClassQuery() throws Exception {
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        deviceManager.topology = mockTopology;
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(true).anyTimes();
+        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+        replay(mockTopology);
+
+        doTestDeviceClassQuery();
+    }
+
+    @Test
+    public void testFindDevice() {
+        boolean exceptionCaught;
+        deviceManager.entityClassifier= new MockEntityClassifierMac();
+        deviceManager.startUp(null);
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        deviceManager.topology = mockTopology;
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(true).anyTimes();
+        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+        replay(mockTopology);
+
+        Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+        Entity entity2 = new Entity(2L, (short)2, 2, 1L, 2, new Date());
+        Entity entity2b = new Entity(22L, (short)2, 2, 1L, 2, new Date());
+        
+        Entity entity3 = new Entity(3L, (short)1, 3, 2L, 1, new Date());
+        Entity entity4 = new Entity(4L, (short)2, 4, 2L, 2, new Date());
+        
+        Entity entity5 = new Entity(5L, (short)1, 5, 3L, 1, new Date());
+        
+
+        IDevice d1 = deviceManager.learnDeviceByEntity(entity1);
+        IDevice d2 = deviceManager.learnDeviceByEntity(entity2);
+        IDevice d3 = deviceManager.learnDeviceByEntity(entity3);
+        IDevice d4 = deviceManager.learnDeviceByEntity(entity4);
+        IDevice d5 = deviceManager.learnDeviceByEntity(entity5);
+        
+        // Make sure the entity classifier worked as expected
+        assertEquals(MockEntityClassifierMac.testECMac1, d1.getEntityClass());
+        assertEquals(MockEntityClassifierMac.testECMac1, d2.getEntityClass());
+        assertEquals(MockEntityClassifierMac.testECMac2, d3.getEntityClass());
+        assertEquals(MockEntityClassifierMac.testECMac2, d4.getEntityClass());
+        assertEquals(DefaultEntityClassifier.entityClass,
+                     d5.getEntityClass());
+        
+        // Look up the device using findDevice() which uses only the primary
+        // index
+        assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(), 
+                                                  entity1.getVlan(),
+                                                  entity1.getIpv4Address(),
+                                                  entity1.getSwitchDPID(),
+                                                  entity1.getSwitchPort()));
+        // port changed. Device will be found through class index
+        assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(), 
+                                                  entity1.getVlan(),
+                                                  entity1.getIpv4Address(),
+                                                  entity1.getSwitchDPID(),
+                                                  entity1.getSwitchPort()+1));
+        // VLAN changed. No device matches
+        assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(), 
+                                                  (short)42,
+                                                  entity1.getIpv4Address(),
+                                                  entity1.getSwitchDPID(),
+                                                  entity1.getSwitchPort()));
+        assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(), 
+                                                  null,
+                                                  entity1.getIpv4Address(),
+                                                  entity1.getSwitchDPID(),
+                                                  entity1.getSwitchPort()));
+        assertEquals(d2, deviceManager.findDeviceByEntity(entity2));
+        assertEquals(null, deviceManager.findDeviceByEntity(entity2b));
+        assertEquals(d3, deviceManager.findDevice(entity3.getMacAddress(), 
+                                                  entity3.getVlan(),
+                                                  entity3.getIpv4Address(),
+                                                  entity3.getSwitchDPID(),
+                                                  entity3.getSwitchPort()));
+        // switch and port not set. throws exception
+        exceptionCaught = false;
+        try {
+            assertEquals(null, deviceManager.findDevice(entity3.getMacAddress(), 
+                                                        entity3.getVlan(),
+                                                        entity3.getIpv4Address(),
+                                                        null,
+                                                        null));
+        } 
+        catch (IllegalArgumentException e) {
+            exceptionCaught = true;
+        }
+        if (!exceptionCaught)
+            fail("findDevice() did not throw IllegalArgumentException");
+        assertEquals(d4, deviceManager.findDeviceByEntity(entity4));
+        assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(), 
+                                                  entity5.getVlan(),
+                                                  entity5.getIpv4Address(),
+                                                  entity5.getSwitchDPID(),
+                                                  entity5.getSwitchPort()));
+        // switch and port not set. throws exception (swith/port are key 
+        // fields of IEntityClassifier but not d5.entityClass
+        exceptionCaught = false;
+        try {
+            assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(), 
+                                                      entity5.getVlan(),
+                                                      entity5.getIpv4Address(),
+                                                      null,
+                                                      null));
+        } 
+        catch (IllegalArgumentException e) {
+            exceptionCaught = true;
+        }
+        if (!exceptionCaught)
+            fail("findDevice() did not throw IllegalArgumentException");
+        
+        
+        Entity entityNoClass = new Entity(5L, (short)1, 5, -1L, 1, new Date());
+        assertEquals(null, deviceManager.findDeviceByEntity(entityNoClass));
+        
+        
+        // Now look up destination devices
+        assertEquals(d1, deviceManager.findDestDevice(d2, 
+                                                  entity1.getMacAddress(), 
+                                                  entity1.getVlan(),
+                                                  entity1.getIpv4Address()));
+        assertEquals(d1, deviceManager.findDestDevice(d2, 
+                                                  entity1.getMacAddress(), 
+                                                  entity1.getVlan(),
+                                                  null));
+        assertEquals(null, deviceManager.findDestDevice(d2, 
+                                                  entity1.getMacAddress(), 
+                                                  (short) -1,
+                                                  0));
+    }
+    
+    @Test
+    public void testGetIPv4Addresses() {
+        // Looks like Date is only 1s granularity
+        
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        deviceManager.topology = mockTopology;
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(true).anyTimes();
+        expect(mockTopology.getL2DomainId(anyLong())).andReturn(1L).anyTimes();
+        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+                                         EasyMock.anyShort(),
+                                         EasyMock.anyLong(),
+                                         EasyMock.anyShort()))
+                                         .andReturn(false)
+                                         .anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+                                                  EasyMock.anyShort()))
+                                                  .andReturn(false)
+                                                  .anyTimes();
+        expect(mockTopology.isInSameBroadcastDomain(EasyMock.anyLong(),
+                                                    EasyMock.anyShort(),
+                                                    EasyMock.anyLong(),
+                                                    EasyMock.anyShort())).
+                                                    andReturn(false).anyTimes();
+        replay(mockTopology);
+
+        Entity e1 = new Entity(1L, (short)1, null, null, null, new Date(2000));
+        Device d1 = deviceManager.learnDeviceByEntity(e1);
+        assertArrayEquals(new Integer[0], d1.getIPv4Addresses());
+        
+        
+        Entity e2 = new Entity(2L, (short)2, 2, null, null, new Date(2000));
+        Device d2 = deviceManager.learnDeviceByEntity(e2);
+        d2 = deviceManager.learnDeviceByEntity(e2);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        // More than one entity
+        Entity e2b = new Entity(2L, (short)2, null, 2L, 2, new Date(3000));
+        d2 = deviceManager.learnDeviceByEntity(e2b);
+        assertEquals(2, d2.entities.length);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        // and now add an entity with an IP
+        Entity e2c = new Entity(2L, (short)2, 2, 2L, 3, new Date(3000));
+        d2 = deviceManager.learnDeviceByEntity(e2c);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        assertEquals(3, d2.entities.length);
+        
+        // Other devices with different IPs shouldn't interfere
+        Entity e3 = new Entity(3L, (short)3, 3, null, null, new Date(4000));
+        Entity e3b = new Entity(3L, (short)3, 3, 3L, 3, new Date(4400));
+        Device d3 = deviceManager.learnDeviceByEntity(e3);
+        d3 = deviceManager.learnDeviceByEntity(e3b);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        assertArrayEquals(new Integer[] { 3 }, d3.getIPv4Addresses());
+        
+        // Add another IP to d3
+        Entity e3c = new Entity(3L, (short)3, 33, 3L, 3, new Date(4400));
+        d3 = deviceManager.learnDeviceByEntity(e3c);
+        Integer[] ips = d3.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 3, 33 }, ips);
+        
+        // Add another device that also claims IP2 but is older than e2
+        Entity e4 = new Entity(4L, (short)4, 2, null, null, new Date(1000));
+        Entity e4b = new Entity(4L, (short)4, null, 4L, 4, new Date(1000));
+        Device d4 = deviceManager.learnDeviceByEntity(e4);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        assertArrayEquals(new Integer[0],  d4.getIPv4Addresses());
+        // add another entity to d4
+        d4 = deviceManager.learnDeviceByEntity(e4b);
+        assertArrayEquals(new Integer[0], d4.getIPv4Addresses());
+        
+        // Make e4 and e4a newer
+        Entity e4c = new Entity(4L, (short)4, 2, null, null, new Date(5000));
+        Entity e4d = new Entity(4L, (short)4, null, 4L, 5, new Date(5000));
+        d4 = deviceManager.learnDeviceByEntity(e4c);
+        d4 = deviceManager.learnDeviceByEntity(e4d);
+        assertArrayEquals(new Integer[0], d2.getIPv4Addresses());
+        // FIXME: d4 should not return IP4
+        assertArrayEquals(new Integer[] { 2 }, d4.getIPv4Addresses());
+        
+        // Add another newer entity to d2 but with different IP
+        Entity e2d = new Entity(2L, (short)2, 22, 4L, 6, new Date(6000));
+        d2 = deviceManager.learnDeviceByEntity(e2d);
+        assertArrayEquals(new Integer[] { 22 }, d2.getIPv4Addresses());
+        assertArrayEquals(new Integer[] { 2 }, d4.getIPv4Addresses());
+
+        // new IP for d2,d4 but with same timestamp. Both devices get the IP
+        Entity e2e = new Entity(2L, (short)2, 42, 2L, 4, new Date(7000));
+        d2 = deviceManager.learnDeviceByEntity(e2e);
+        ips= d2.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 22, 42 }, ips);
+        Entity e4e = new Entity(4L, (short)4, 42, 4L, 7, new Date(7000));
+        d4 = deviceManager.learnDeviceByEntity(e4e);
+        ips= d4.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 2, 42 }, ips);
+        
+        // add a couple more IPs
+        Entity e2f = new Entity(2L, (short)2, 4242, 2L, 5, new Date(8000));
+        d2 = deviceManager.learnDeviceByEntity(e2f);
+        ips= d2.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 22, 42, 4242 }, ips);
+        Entity e4f = new Entity(4L, (short)4, 4242, 4L, 8, new Date(9000));
+        d4 = deviceManager.learnDeviceByEntity(e4f);
+        ips= d4.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 2, 42, 4242 }, ips);
+    }
+    
+    // TODO: this test should really go into a separate class that collects
+    // unit tests for Device
+    @Test
+    public void testGetSwitchPortVlanId() {
+            Entity entity1 = new Entity(1L, (short)1, null, 10L, 1, new Date());
+            Entity entity2 = new Entity(1L, null, null, 10L, 1, new Date());
+            Entity entity3 = new Entity(1L, (short)3, null,  1L, 1, new Date());
+            Entity entity4 = new Entity(1L, (short)42, null,  1L, 1, new Date());
+            Entity[] entities = new Entity[] { entity1, entity2, 
+                                               entity3, entity4
+                                             };
+            Device d = new Device(null,1L, null, null, Arrays.asList(entities), null);
+            SwitchPort swp1x1 = new SwitchPort(1L, 1);
+            SwitchPort swp1x2 = new SwitchPort(1L, 2);
+            SwitchPort swp2x1 = new SwitchPort(2L, 1);
+            SwitchPort swp10x1 = new SwitchPort(10L, 1);
+            assertArrayEquals(new Short[] { -1, 1}, 
+                              d.getSwitchPortVlanIds(swp10x1));
+            assertArrayEquals(new Short[] { 3, 42}, 
+                              d.getSwitchPortVlanIds(swp1x1));
+            assertArrayEquals(new Short[0],
+                              d.getSwitchPortVlanIds(swp1x2));
+            assertArrayEquals(new Short[0],
+                              d.getSwitchPortVlanIds(swp2x1));
+    }
+    
+    @Test
+    public void testReclassifyDevice() {
+    	MockFlexEntityClassifier flexClassifier = 
+    			new MockFlexEntityClassifier();
+        deviceManager.entityClassifier= flexClassifier;
+        deviceManager.startUp(null);
+
+        ITopologyService mockTopology = createMock(ITopologyService.class);
+        deviceManager.topology = mockTopology;
+        expect(mockTopology.isAttachmentPointPort(anyLong(),
+                                                  anyShort())).
+                                                  andReturn(true).anyTimes();
+        expect(mockTopology.getL2DomainId(anyLong())).andReturn(1L).anyTimes();
+        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+                                         EasyMock.anyShort(),
+                                         EasyMock.anyLong(),
+                                         EasyMock.anyShort()))
+                                         .andReturn(false)
+                                         .anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+                                                  EasyMock.anyShort()))
+                                                  .andReturn(false)
+                                                  .anyTimes();
+        replay(mockTopology);
+        
+        //flexClassifier.createTestEntityClass("Class1");
+        
+        Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+        Entity entity1b = new Entity(1L, (short)2, 1, 1L, 1, new Date());
+        Entity entity2 = new Entity(2L, (short)1, 2, 2L, 2, new Date());
+        Entity entity2b = new Entity(2L, (short)2, 2, 2L, 2, new Date());
+        
+        
+        Device d1 = deviceManager.learnDeviceByEntity(entity1);
+        Device d2 = deviceManager.learnDeviceByEntity(entity2);
+        Device d1b = deviceManager.learnDeviceByEntity(entity1b);
+        Device d2b = deviceManager.learnDeviceByEntity(entity2b);
+        
+        d1 = deviceManager.getDeviceIteratorForQuery(entity1.getMacAddress(), 
+        				entity1.getVlan(), entity1.getIpv4Address(), 
+        				entity1.getSwitchDPID(), entity1.getSwitchPort())
+        				.next();
+        d1b = deviceManager.getDeviceIteratorForQuery(entity1b.getMacAddress(), 
+				entity1b.getVlan(), entity1b.getIpv4Address(), 
+				entity1b.getSwitchDPID(), entity1b.getSwitchPort()).next();
+        
+        assertEquals(d1, d1b);
+        
+        d2 = deviceManager.getDeviceIteratorForQuery(entity2.getMacAddress(), 
+				entity2.getVlan(), entity2.getIpv4Address(), 
+				entity2.getSwitchDPID(), entity2.getSwitchPort()).next();
+        d2b = deviceManager.getDeviceIteratorForQuery(entity2b.getMacAddress(), 
+				entity2b.getVlan(), entity2b.getIpv4Address(), 
+				entity2b.getSwitchDPID(), entity2b.getSwitchPort()).next();
+        assertEquals(d2, d2b);
+        
+        IEntityClass eC1 = flexClassifier.createTestEntityClass("C1");
+        IEntityClass eC2 = flexClassifier.createTestEntityClass("C2");
+        
+        flexClassifier.addVlanEntities((short)1, eC1);
+        flexClassifier.addVlanEntities((short)2, eC1);
+        
+        deviceManager.reclassifyDevice(d1);
+        deviceManager.reclassifyDevice(d2);
+        
+        d1 = deviceManager.deviceMap.get(
+        		deviceManager.primaryIndex.findByEntity(entity1));
+        d1b = deviceManager.deviceMap.get(
+        		deviceManager.primaryIndex.findByEntity(entity1b));
+        
+        assertEquals(d1, d1b);
+        
+        d2 = deviceManager.deviceMap.get(
+        		deviceManager.primaryIndex.findByEntity(entity2));
+        d2b = deviceManager.deviceMap.get(
+        		deviceManager.primaryIndex.findByEntity(entity2b));
+        
+        assertEquals(d2, d2b);
+        				
+        flexClassifier.addVlanEntities((short)1, eC2);
+        
+        deviceManager.reclassifyDevice(d1);
+        deviceManager.reclassifyDevice(d2);
+        d1 = deviceManager.deviceMap.get(
+        		deviceManager.primaryIndex.findByEntity(entity1));
+        d1b = deviceManager.deviceMap.get(
+        		deviceManager.primaryIndex.findByEntity(entity1b));
+        d2 = deviceManager.deviceMap.get(
+        		deviceManager.primaryIndex.findByEntity(entity2));
+        d2b = deviceManager.deviceMap.get(
+        		deviceManager.primaryIndex.findByEntity(entity2b));
+        
+        assertNotSame(d1, d1b);
+       
+        assertNotSame(d2, d2b);
+        
+        flexClassifier.addVlanEntities((short)1, eC1);
+        deviceManager.reclassifyDevice(d1);
+        deviceManager.reclassifyDevice(d2);
+        ClassState classState = deviceManager.classStateMap.get(eC1.getName());
+
+        Long deviceKey1 = null;
+        Long deviceKey1b = null;
+        Long deviceKey2 = null;
+        Long deviceKey2b = null;
+
+        deviceKey1 =
+        		classState.classIndex.findByEntity(entity1);
+        deviceKey1b =
+        		classState.classIndex.findByEntity(entity1b);
+        deviceKey2 =
+        		classState.classIndex.findByEntity(entity2);
+        deviceKey2b =
+        		classState.classIndex.findByEntity(entity2b);
+
+        assertEquals(deviceKey1, deviceKey1b);
+        
+        assertEquals(deviceKey2, deviceKey2b);
+
+
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndexTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndexTest.java
new file mode 100644
index 0000000..2a6ee9d
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndexTest.java
@@ -0,0 +1,167 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Iterator;
+
+import org.junit.Test;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import junit.framework.TestCase;
+
+/**
+ * 
+ * @author gregor
+ *
+ */
+public class DeviceUniqueIndexTest extends TestCase {
+    protected Entity e1a;
+    protected Entity e1b;
+    protected Device d1;
+    protected Entity e2;
+    protected Entity e2alt;
+    protected Entity e3;
+    protected Entity e4;
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        e1a = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+        e1b = new Entity(1L, (short)2, 1, 1L, 1, new Date());
+        List<Entity> d1Entities = new ArrayList<Entity>(2);
+        d1Entities.add(e1a);
+        d1Entities.add(e1b);
+        d1 = new Device(null, Long.valueOf(1), null, null, d1Entities, null);
+        
+        // e2 and e2 alt match in MAC and VLAN
+        e2 = new Entity(2L, (short)2, 2, 2L, 2, new Date());
+        e2alt = new Entity(2, (short)2, null, null, null, null);
+        
+        // IP is null
+        e3 = new Entity(3L, (short)3, null, 3L, 3, new Date());
+        
+        // IP and switch and port are null
+        e4 = new Entity(4L, (short)4, null, null, null, new Date());
+    }
+    
+    /*
+     * Checks that the iterator it returns the elements in the Set expected
+     * Doesn't check how often an element is returned as long it's at least
+     * once
+     */
+    protected void verifyIterator(Set<Long> expected, Iterator<Long> it) {
+        HashSet<Long> actual = new HashSet<Long>();
+        while (it.hasNext()) {
+            actual.add(it.next());
+        }
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void testDeviceUniqueIndex() {
+        DeviceUniqueIndex idx1 = new DeviceUniqueIndex(
+                                             EnumSet.of(DeviceField.MAC, 
+                                                        DeviceField.VLAN));
+        
+        idx1.updateIndex(d1, d1.getDeviceKey());
+        idx1.updateIndex(e2, 2L);
+        
+        //-------------
+        // Test findByEntity lookups
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1a));
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1b));
+        assertEquals(Long.valueOf(2L), idx1.findByEntity(e2));
+        // we didn't add e2alt but since they key fields are the same we 
+        // should find it 
+        assertEquals(Long.valueOf(2L), idx1.findByEntity(e2alt));
+        assertEquals(null, idx1.findByEntity(e3));
+        assertEquals(null, idx1.findByEntity(e4));
+        
+        //-------------
+        // Test getAll()
+        HashSet<Long> expectedKeys = new HashSet<Long>();
+        expectedKeys.add(1L);
+        expectedKeys.add(2L);
+        verifyIterator(expectedKeys, idx1.getAll());
+        
+                
+        //-------------
+        // Test queryByEntity()
+        verifyIterator(Collections.<Long>singleton(1L), 
+                       idx1.queryByEntity(e1a));
+        verifyIterator(Collections.<Long>singleton(1L), 
+                       idx1.queryByEntity(e1b));
+        verifyIterator(Collections.<Long>singleton(2L), 
+                       idx1.queryByEntity(e2));
+        verifyIterator(Collections.<Long>singleton(2L),
+                       idx1.queryByEntity(e2alt));
+        assertEquals(false, idx1.queryByEntity(e3).hasNext());
+        assertEquals(false, idx1.queryByEntity(e3).hasNext());
+        
+        
+        //-------------
+        // Test removal
+        idx1.removeEntity(e1a, 42L); // No-op. e1a isn't mapped to this key
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1a));
+        idx1.removeEntity(e1a, 1L); 
+        assertEquals(null, idx1.findByEntity(e1a));
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1b));
+        assertEquals(Long.valueOf(2L), idx1.findByEntity(e2));
+        idx1.removeEntity(e2);  
+        assertEquals(null, idx1.findByEntity(e2));
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1b));
+        
+        
+        //-------------
+        // Test null keys
+        DeviceUniqueIndex idx2 = new DeviceUniqueIndex(
+                                             EnumSet.of(DeviceField.IPV4,
+                                                        DeviceField.SWITCH));
+        // only one key field is null
+        idx2.updateIndex(e3, 3L);
+        assertEquals(Long.valueOf(3L), idx2.findByEntity(e3));
+        e3.ipv4Address = 3;
+        assertEquals(null, idx2.findByEntity(e3));
+        // all key fields are null
+        idx2.updateIndex(e4, 4L);
+        assertEquals(null, idx2.findByEntity(e4));
+        Device d4 = new Device(null, 4L, null, null, Collections.<Entity>singleton(e4), null);
+        idx2.updateIndex(d4, 4L);
+        assertEquals(null, idx2.findByEntity(e4));
+        
+        
+
+        //-------------
+        // entity already exists with different deviceKey
+        DeviceUniqueIndex idx3 = new DeviceUniqueIndex(
+                                             EnumSet.of(DeviceField.MAC, 
+                                                        DeviceField.VLAN));
+        idx3.updateIndex(e1a, 42L);
+        assertEquals(false, idx3.updateIndex(d1, 1L));
+        // TODO: shouldn't this fail as well so that the behavior
+        // is consistent?
+        idx3.updateIndex(e1a, 1L);
+        // anyways. We can now add d1 ;-)
+        assertEquals(true, idx3.updateIndex(d1, 1L));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java
new file mode 100644
index 0000000..5460ea3
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java
@@ -0,0 +1,91 @@
+/**
+*    Copyright 2011,2012 Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.devicemanager.test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.TreeSet;
+
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.internal.AttachmentPoint;
+import net.floodlightcontroller.devicemanager.internal.Device;
+import net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/**
+ * This mock device removes the dependency on topology and a parent device
+ * manager and simply assumes all its entities are current and correct
+ */
+public class MockDevice extends Device {
+
+    public MockDevice(DeviceManagerImpl deviceManager,
+                      Long deviceKey,
+                      Entity entity, 
+                      IEntityClass entityClass)  {
+        super(deviceManager, deviceKey, entity, entityClass);
+    }
+
+    public MockDevice(Device device, Entity newEntity) {
+        super(device, newEntity);
+    }
+    
+    public MockDevice(DeviceManagerImpl deviceManager, Long deviceKey,
+                      List<AttachmentPoint> aps,
+                      List<AttachmentPoint> trueAPs,
+                      Collection<Entity> entities,
+                      IEntityClass entityClass) {
+        super(deviceManager, deviceKey, aps, trueAPs, entities, entityClass);
+    }
+
+    @Override
+    public Integer[] getIPv4Addresses() {
+        TreeSet<Integer> vals = new TreeSet<Integer>();
+        for (Entity e : entities) {
+            if (e.getIpv4Address() == null) continue;
+            vals.add(e.getIpv4Address());
+        }
+        
+        return vals.toArray(new Integer[vals.size()]);
+    }
+
+    @Override
+    public SwitchPort[] getAttachmentPoints() {
+        ArrayList<SwitchPort> vals = 
+                new ArrayList<SwitchPort>(entities.length);
+        for (Entity e : entities) {
+            if (e.getSwitchDPID() != null &&
+                e.getSwitchPort() != null &&
+                deviceManager.isValidAttachmentPoint(e.getSwitchDPID(), e.getSwitchPort())) {
+                SwitchPort sp = new SwitchPort(e.getSwitchDPID(), 
+                                               e.getSwitchPort());
+                vals.add(sp);
+            }
+        }
+        return vals.toArray(new SwitchPort[vals.size()]);
+    }
+    
+    @Override
+    public String toString() {
+        String rv = "MockDevice[entities=+";
+        rv += entities.toString();
+        rv += "]";
+        return rv;
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java
new file mode 100644
index 0000000..8b539f6
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java
@@ -0,0 +1,103 @@
+package net.floodlightcontroller.devicemanager.test;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.devicemanager.internal.AttachmentPoint;
+import net.floodlightcontroller.devicemanager.internal.Device;
+import net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/**
+ * Mock device manager useful for unit tests
+ * @author readams
+ */
+public class MockDeviceManager extends DeviceManagerImpl {
+    /**
+     * Set a new IEntityClassifier
+     * Use this as a quick way to use a particular entity classifier in a 
+     * single test without having to setup the full FloodlightModuleContext
+     * again.
+     * @param ecs 
+     */
+    public void setEntityClassifier(IEntityClassifierService ecs) {
+        this.entityClassifier = ecs;
+        this.startUp(null);
+    }
+    
+    /**
+     * Learn a device using the given characteristics. 
+     * @param macAddress the MAC
+     * @param vlan the VLAN (can be null)
+     * @param ipv4Address the IP (can be null)
+     * @param switchDPID the attachment point switch DPID (can be null)
+     * @param switchPort the attachment point switch port (can be null)
+     * @param processUpdates if false, will not send updates.  Note that this 
+     * method is not thread safe if this is false
+     * @return the device, either new or not
+     */
+    public IDevice learnEntity(long macAddress, Short vlan, 
+                               Integer ipv4Address, Long switchDPID, 
+                               Integer switchPort,
+                               boolean processUpdates) {
+        Set<IDeviceListener> listeners = deviceListeners;
+        if (!processUpdates) {
+            deviceListeners = Collections.<IDeviceListener>emptySet();
+        }
+        
+        if (vlan != null && vlan.shortValue() <= 0)
+            vlan = null;
+        if (ipv4Address != null && ipv4Address == 0)
+            ipv4Address = null;
+        IDevice res =  learnDeviceByEntity(new Entity(macAddress, vlan, 
+                                                      ipv4Address, switchDPID, 
+                                                      switchPort, new Date()));
+        deviceListeners = listeners;
+        return res;
+    }
+    
+    /**
+     * Learn a device using the given characteristics. 
+     * @param macAddress the MAC
+     * @param vlan the VLAN (can be null)
+     * @param ipv4Address the IP (can be null)
+     * @param switchDPID the attachment point switch DPID (can be null)
+     * @param switchPort the attachment point switch port (can be null)
+     * @return the device, either new or not
+     */
+    public IDevice learnEntity(long macAddress, Short vlan, 
+                               Integer ipv4Address, Long switchDPID, 
+                               Integer switchPort) {
+        return learnEntity(macAddress, vlan, ipv4Address, 
+                           switchDPID, switchPort, true);
+    }
+
+    @Override
+    protected Device allocateDevice(Long deviceKey,
+                                    Entity entity, 
+                                    IEntityClass entityClass) {
+        return new MockDevice(this, deviceKey, entity, entityClass);
+    }
+    
+    @Override
+    protected Device allocateDevice(Long deviceKey,
+                                    List<AttachmentPoint> aps,
+                                    List<AttachmentPoint> trueAPs,
+                                    Collection<Entity> entities,
+                                    IEntityClass entityClass) {
+        return new MockDevice(this, deviceKey, aps, trueAPs, entities, entityClass);
+    }
+    
+    @Override
+    protected Device allocateDevice(Device device,
+                                    Entity entity) {
+        return new MockDevice(device, entity);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifier.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifier.java
new file mode 100644
index 0000000..679dc9a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifier.java
@@ -0,0 +1,47 @@
+package net.floodlightcontroller.devicemanager.test;
+
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.MAC;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.PORT;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.SWITCH;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/** A simple IEntityClassifier. Useful for tests that need IEntityClassifiers 
+ * and IEntityClass'es with switch and/or port key fields 
+ */
+public class MockEntityClassifier extends DefaultEntityClassifier {
+    public static class TestEntityClass implements IEntityClass {
+        @Override
+        public EnumSet<DeviceField> getKeyFields() {
+            return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+        }
+
+        @Override
+        public String getName() {
+            return "TestEntityClass";
+        }
+    }
+    public static IEntityClass testEC = 
+            new MockEntityClassifier.TestEntityClass();
+    
+    @Override
+    public IEntityClass classifyEntity(Entity entity) {
+        if (entity.getSwitchDPID() >= 10L) {
+            return testEC;
+        }
+        return DefaultEntityClassifier.entityClass;
+    }
+
+    @Override
+    public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+        return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+    }
+
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifierMac.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifierMac.java
new file mode 100644
index 0000000..398b6c0
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifierMac.java
@@ -0,0 +1,57 @@
+package net.floodlightcontroller.devicemanager.test;
+
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.MAC;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.PORT;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.SWITCH;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/** A simple IEntityClassifier. Useful for tests that need an IEntityClassifier
+ * with switch/port as key fields. 
+ */
+public class MockEntityClassifierMac extends DefaultEntityClassifier {
+    public static class TestEntityClassMac implements IEntityClass {
+        protected String name;
+        public TestEntityClassMac(String name) {
+            this.name = name;
+        }
+        
+        @Override
+        public EnumSet<DeviceField> getKeyFields() {
+            return EnumSet.of(MAC, VLAN);
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+    }
+    public static IEntityClass testECMac1 = 
+            new MockEntityClassifierMac.TestEntityClassMac("testECMac1");
+    public static IEntityClass testECMac2 = 
+            new MockEntityClassifierMac.TestEntityClassMac("testECMac2");
+    
+    @Override
+    public IEntityClass classifyEntity(Entity entity) {
+        if (entity.getSwitchDPID() == 1L) {
+            return testECMac1;
+        } else if (entity.getSwitchDPID() == 2L) {
+            return testECMac2;
+        } else if (entity.getSwitchDPID() == -1L) {
+            return null;
+        }
+        return DefaultEntityClassifier.entityClass;
+    }
+
+    @Override
+    public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+        return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+    }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockFlexEntityClassifier.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockFlexEntityClassifier.java
new file mode 100644
index 0000000..2224551
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockFlexEntityClassifier.java
@@ -0,0 +1,78 @@
+/**
+ * 
+ */
+package net.floodlightcontroller.devicemanager.test;
+
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.MAC;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.PORT;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.SWITCH;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/**
+ * Extension to simple entity classifier to help in unit tests to provide table
+ * based multiple entity classification mock for reclassification tests
+ *
+ */
+public class MockFlexEntityClassifier extends DefaultEntityClassifier {
+	Map <Long, IEntityClass> switchEntities;
+	Map <Short, IEntityClass> vlanEntities;
+	
+    public static class TestEntityClass implements IEntityClass {
+    	String name;
+    	public TestEntityClass(String name) {
+    		this.name = name;
+    	}
+        @Override
+        public EnumSet<DeviceField> getKeyFields() {
+            return EnumSet.of(MAC);
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+    }
+    public static IEntityClass defaultClass = new TestEntityClass("default");
+    public MockFlexEntityClassifier() {
+    	switchEntities = new HashMap<Long, IEntityClass> ();
+    	vlanEntities = new HashMap<Short, IEntityClass> ();
+    }
+    public IEntityClass createTestEntityClass(String name) {
+    	return new TestEntityClass(name);
+    }
+    
+    public void addSwitchEntity(Long dpid, IEntityClass entityClass) {
+    	switchEntities.put(dpid, entityClass);
+    }
+    public void removeSwitchEntity(Long dpid) {
+    	switchEntities.remove(dpid);
+    }
+    public void addVlanEntities(Short vlan, IEntityClass entityClass) {
+    	vlanEntities.put(vlan, entityClass);
+    }
+    public void removeVlanEntities(Short vlan) {
+    	vlanEntities.remove(vlan);
+    }
+    @Override
+    public IEntityClass classifyEntity(Entity entity) {
+        if (switchEntities.containsKey(entity.getSwitchDPID()))
+        	return switchEntities.get(entity.getSwitchDPID());
+        if (vlanEntities.containsKey(entity.getVlan()))
+        	return vlanEntities.get(entity.getVlan());
+        return defaultClass;
+    }
+    @Override
+    public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+        return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java
new file mode 100644
index 0000000..959bb49
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java
@@ -0,0 +1,513 @@
+package net.floodlightcontroller.firewall;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.packet.ARP;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.TCP;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.routing.IRoutingDecision;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.util.MACAddress;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.protocol.OFType;
+import org.openflow.util.HexString;
+
+/**
+ * Unit test for stateless firewall implemented as a Google Summer of Code project.
+ * 
+ * @author Amer Tahir
+ */
+public class FirewallTest extends FloodlightTestCase {
+    protected MockFloodlightProvider mockFloodlightProvider;
+    protected FloodlightContext cntx;
+    protected OFPacketIn packetIn;
+    protected IOFSwitch sw;
+    protected IPacket tcpPacket;
+    protected IPacket broadcastARPPacket;
+    protected IPacket ARPReplyPacket;
+    protected IPacket broadcastIPPacket;
+    protected IPacket tcpPacketReply;
+    protected IPacket broadcastMalformedPacket;
+    private Firewall firewall;
+    public static String TestSwitch1DPID = "00:00:00:00:00:00:00:01";
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        cntx = new FloodlightContext();
+        mockFloodlightProvider = getMockFloodlightProvider();
+        firewall = new Firewall();
+        IStorageSourceService storageService = new MemoryStorageSource();
+        RestApiServer restApi = new RestApiServer();
+
+        // Mock switches
+        long dpid = HexString.toLong(TestSwitch1DPID);
+        sw = EasyMock.createNiceMock(IOFSwitch.class);
+        expect(sw.getId()).andReturn(dpid).anyTimes();
+        expect(sw.getStringId()).andReturn(TestSwitch1DPID).anyTimes();
+        replay(sw);
+        // Load the switch map
+        Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>();
+        switches.put(dpid, sw);
+        mockFloodlightProvider.setSwitches(switches);
+
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        fmc.addService(IFloodlightProviderService.class, 
+                mockFloodlightProvider);
+        fmc.addService(IFirewallService.class, firewall);
+        fmc.addService(IStorageSourceService.class, storageService);
+        fmc.addService(IRestApiService.class, restApi);
+
+        try {
+            restApi.init(fmc);
+        } catch (FloodlightModuleException e) {
+            e.printStackTrace();
+        }
+
+        firewall.init(fmc);
+        firewall.startUp(fmc);
+
+        // Build our test packet
+        this.tcpPacket = new Ethernet()
+        .setDestinationMACAddress("00:11:22:33:44:55")
+        .setSourceMACAddress("00:44:33:22:11:00")
+        .setVlanID((short) 42)
+        .setEtherType(Ethernet.TYPE_IPv4)
+        .setPayload(
+                new IPv4()
+                .setTtl((byte) 128)
+                .setSourceAddress("192.168.1.1")
+                .setDestinationAddress("192.168.1.2")
+                .setPayload(new TCP()
+                .setSourcePort((short) 81)
+                .setDestinationPort((short) 80)
+                .setPayload(new Data(new byte[] {0x01}))));
+        
+        // Build a broadcast ARP packet
+        this.broadcastARPPacket = new Ethernet()
+        .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+        .setSourceMACAddress("00:44:33:22:11:00")
+        .setVlanID((short) 42)
+        .setEtherType(Ethernet.TYPE_ARP)
+        .setPayload(
+                new ARP()
+                .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                .setProtocolType(ARP.PROTO_TYPE_IP)
+                .setOpCode(ARP.OP_REQUEST)
+                .setHardwareAddressLength((byte)6)
+                .setProtocolAddressLength((byte)4)
+                .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00"))
+                .setSenderProtocolAddress(IPv4.toIPv4Address("192.168.1.1"))
+                .setTargetHardwareAddress(Ethernet.toMACAddress("00:00:00:00:00:00"))
+                .setTargetProtocolAddress(IPv4.toIPv4Address("192.168.1.2"))
+                .setPayload(new Data(new byte[] {0x01})));
+        
+        // Build a ARP packet
+        this.ARPReplyPacket = new Ethernet()
+        .setDestinationMACAddress("00:44:33:22:11:00")
+        .setSourceMACAddress("00:11:22:33:44:55")
+        .setVlanID((short) 42)
+        .setEtherType(Ethernet.TYPE_ARP)
+        .setPayload(
+                new ARP()
+                .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                .setProtocolType(ARP.PROTO_TYPE_IP)
+                .setOpCode(ARP.OP_REQUEST)
+                .setHardwareAddressLength((byte)6)
+                .setProtocolAddressLength((byte)4)
+                .setSenderHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+                .setSenderProtocolAddress(IPv4.toIPv4Address("192.168.1.2"))
+                .setTargetHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00"))
+                .setTargetProtocolAddress(IPv4.toIPv4Address("192.168.1.1"))
+                .setPayload(new Data(new byte[] {0x01})));
+        
+        // Build a broadcast IP packet
+        this.broadcastIPPacket = new Ethernet()
+        .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+        .setSourceMACAddress("00:44:33:22:11:00")
+        .setVlanID((short) 42)
+        .setEtherType(Ethernet.TYPE_IPv4)
+        .setPayload(
+                new IPv4()
+                .setTtl((byte) 128)
+                .setSourceAddress("192.168.1.1")
+                .setDestinationAddress("192.168.1.255")
+                .setPayload(new UDP()
+                .setSourcePort((short) 5000)
+                .setDestinationPort((short) 5001)
+                .setPayload(new Data(new byte[] {0x01}))));
+
+        // Build a malformed broadcast packet
+        this.broadcastMalformedPacket = new Ethernet()
+        .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+        .setSourceMACAddress("00:44:33:22:11:00")
+        .setVlanID((short) 42)
+        .setEtherType(Ethernet.TYPE_IPv4)
+        .setPayload(
+                new IPv4()
+                .setTtl((byte) 128)
+                .setSourceAddress("192.168.1.1")
+                .setDestinationAddress("192.168.1.2")
+                .setPayload(new UDP()
+                .setSourcePort((short) 5000)
+                .setDestinationPort((short) 5001)
+                .setPayload(new Data(new byte[] {0x01}))));
+
+        this.tcpPacketReply = new Ethernet()
+        .setDestinationMACAddress("00:44:33:22:11:00")
+        .setSourceMACAddress("00:11:22:33:44:55")
+        .setVlanID((short) 42)
+        .setEtherType(Ethernet.TYPE_IPv4)
+        .setPayload(
+                new IPv4()
+                .setTtl((byte) 128)
+                .setSourceAddress("192.168.1.2")
+                .setDestinationAddress("192.168.1.1")
+                .setPayload(new TCP()
+                .setSourcePort((short) 80)
+                .setDestinationPort((short) 81)
+                .setPayload(new Data(new byte[] {0x02}))));
+    }
+
+    protected void setPacketIn(IPacket packet) {
+        byte[] serializedPacket = packet.serialize();
+        // Build the PacketIn
+        this.packetIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN))
+                .setBufferId(-1)
+                .setInPort((short) 1)
+                .setPacketData(serializedPacket)
+                .setReason(OFPacketInReason.NO_MATCH)
+                .setTotalLength((short) serializedPacket.length);
+
+        // Add the packet to the context store
+        IFloodlightProviderService.bcStore.
+        put(cntx, 
+                IFloodlightProviderService.CONTEXT_PI_PAYLOAD, 
+                (Ethernet)packet);
+    }
+
+    @Test
+    public void testNoRules() throws Exception {
+        // enable firewall first
+        firewall.enableFirewall(true);
+        // simulate a packet-in event
+        this.setPacketIn(tcpPacket);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        assertEquals(0, firewall.rules.size());
+
+        IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        // no rules to match, so firewall should deny
+        assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+    }
+    
+    @Test
+    public void testReadRulesFromStorage() throws Exception {
+        // add 2 rules first
+        FirewallRule rule = new FirewallRule();
+        rule.in_port = 2;
+        rule.dl_src = MACAddress.valueOf("00:00:00:00:00:01").toLong();
+        rule.dl_dst = MACAddress.valueOf("00:00:00:00:00:02").toLong();
+        rule.priority = 1;
+        rule.action = FirewallRule.FirewallAction.DENY;
+        firewall.addRule(rule);
+        rule = new FirewallRule();
+        rule.in_port = 3;
+        rule.dl_src = MACAddress.valueOf("00:00:00:00:00:02").toLong();
+        rule.dl_dst = MACAddress.valueOf("00:00:00:00:00:01").toLong();
+        rule.nw_proto = IPv4.PROTOCOL_TCP;
+        rule.wildcard_nw_proto = false;
+        rule.tp_dst = 80;
+        rule.priority = 2;
+        rule.action = FirewallRule.FirewallAction.ALLOW;
+        firewall.addRule(rule);
+
+        List<FirewallRule> rules = firewall.readRulesFromStorage();
+        // verify rule 1
+        FirewallRule r = rules.get(0);
+        assertEquals(r.in_port, 2);
+        assertEquals(r.priority, 1);
+        assertEquals(r.dl_src, MACAddress.valueOf("00:00:00:00:00:01").toLong());
+        assertEquals(r.dl_dst, MACAddress.valueOf("00:00:00:00:00:02").toLong());
+        assertEquals(r.action, FirewallRule.FirewallAction.DENY);
+        // verify rule 2
+        r = rules.get(1);
+        assertEquals(r.in_port, 3);
+        assertEquals(r.priority, 2);
+        assertEquals(r.dl_src, MACAddress.valueOf("00:00:00:00:00:02").toLong());
+        assertEquals(r.dl_dst, MACAddress.valueOf("00:00:00:00:00:01").toLong());
+        assertEquals(r.nw_proto, IPv4.PROTOCOL_TCP);
+        assertEquals(r.tp_dst, 80);
+        assertEquals(r.wildcard_nw_proto, false);
+        assertEquals(r.action, FirewallRule.FirewallAction.ALLOW);
+    }
+
+    @Test
+    public void testRuleInsertionIntoStorage() throws Exception {
+        // add TCP rule
+        FirewallRule rule = new FirewallRule();
+        rule.nw_proto = IPv4.PROTOCOL_TCP;
+        rule.wildcard_nw_proto = false;
+        rule.priority = 1;
+        firewall.addRule(rule);
+
+        List<Map<String, Object>> rulesFromStorage = firewall.getStorageRules();
+        assertEquals(1, rulesFromStorage.size());
+        assertEquals(Integer.parseInt((String)rulesFromStorage.get(0).get("ruleid")), rule.ruleid);
+    }
+
+    @Test
+    public void testRuleDeletion() throws Exception {
+        // add TCP rule
+        FirewallRule rule = new FirewallRule();
+        rule.nw_proto = IPv4.PROTOCOL_TCP;
+        rule.wildcard_nw_proto = false;
+        rule.priority = 1;
+        firewall.addRule(rule);
+        int rid = rule.ruleid;
+
+        List<Map<String, Object>> rulesFromStorage = firewall.getStorageRules();
+        assertEquals(1, rulesFromStorage.size());
+        assertEquals(Integer.parseInt((String)rulesFromStorage.get(0).get("ruleid")), rid);
+
+        // delete rule
+        firewall.deleteRule(rid);
+        rulesFromStorage = firewall.getStorageRules();
+        assertEquals(0, rulesFromStorage.size());
+    }
+
+    @Test
+    public void testFirewallDisabled() throws Exception {
+        // firewall isn't enabled by default
+        // so, it shouldn't make any decision
+
+        // add TCP rule
+        FirewallRule rule = new FirewallRule();
+        rule.nw_proto = IPv4.PROTOCOL_TCP;
+        rule.wildcard_nw_proto = false;
+        rule.priority = 1;
+        firewall.addRule(rule);
+
+        this.setPacketIn(tcpPacket);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        assertEquals(1, firewall.rules.size());
+
+        IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertNull(decision);
+    }
+
+    @Test
+    public void testSimpleAllowRule() throws Exception {
+        // enable firewall first
+        firewall.enableFirewall(true);
+
+        // add TCP rule
+        FirewallRule rule = new FirewallRule();
+        rule.dl_type = Ethernet.TYPE_IPv4;
+        rule.wildcard_dl_type = false;
+        rule.nw_proto = IPv4.PROTOCOL_TCP;
+        rule.wildcard_nw_proto = false;
+        // source is IP 192.168.1.2
+        rule.nw_src_prefix = IPv4.toIPv4Address("192.168.1.2");
+        rule.wildcard_nw_src = false;
+        // dest is network 192.168.1.0/24
+        rule.nw_dst_prefix = IPv4.toIPv4Address("192.168.1.0");
+        rule.nw_dst_maskbits = 24;
+        rule.wildcard_nw_dst = false;
+        rule.priority = 1;
+        firewall.addRule(rule);
+
+        // simulate a packet-in events
+
+        this.setPacketIn(tcpPacketReply);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
+
+        // clear decision
+        IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION);
+
+        this.setPacketIn(tcpPacket);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+    }
+
+    @Test
+    public void testOverlappingRules() throws Exception {
+        firewall.enableFirewall(true);
+
+        // add TCP port 80 (destination only) allow rule
+        FirewallRule rule = new FirewallRule();
+        rule.dl_type = Ethernet.TYPE_IPv4;
+        rule.wildcard_dl_type = false;
+        rule.nw_proto = IPv4.PROTOCOL_TCP;
+        rule.wildcard_nw_proto = false;
+        rule.tp_dst = 80;
+        rule.priority = 1;
+        firewall.addRule(rule);
+
+        // add block all rule
+        rule = new FirewallRule();
+        rule.action = FirewallRule.FirewallAction.DENY;
+        rule.priority = 2;
+        firewall.addRule(rule);
+
+        assertEquals(2, firewall.rules.size());
+
+        // packet destined to TCP port 80 - should be allowed
+
+        this.setPacketIn(tcpPacket);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
+
+        // clear decision
+        IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION);
+
+        // packet destined for port 81 - should be denied
+
+        this.setPacketIn(tcpPacketReply);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+    }
+
+    @Test
+    public void testARP() throws Exception {
+        // enable firewall first
+        firewall.enableFirewall(true);
+
+        // no rules inserted so all traffic other than broadcast and ARP-request-broadcast should be blocked
+
+        // simulate an ARP broadcast packet-in event
+
+        this.setPacketIn(broadcastARPPacket);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        // broadcast-ARP traffic should be allowed
+        IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertEquals(IRoutingDecision.RoutingAction.MULTICAST, decision.getRoutingAction());
+        
+        // clear decision
+        IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION);
+        
+        // simulate an ARP reply packet-in event
+
+        this.setPacketIn(ARPReplyPacket);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        // ARP reply traffic should be denied
+        decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+    }
+    
+    @Test
+    public void testIPBroadcast() throws Exception {
+        // enable firewall first
+        firewall.enableFirewall(true);
+        
+        // set subnet mask for IP broadcast
+        firewall.setSubnetMask("255.255.255.0");
+
+        // no rules inserted so all traffic other than broadcast and ARP-request-broadcast should be blocked
+
+        // simulate a packet-in event
+
+        this.setPacketIn(broadcastIPPacket);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        // broadcast traffic should be allowed
+        IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertEquals(IRoutingDecision.RoutingAction.MULTICAST, decision.getRoutingAction());
+    }
+
+    @Test
+    public void testMalformedIPBroadcast() throws Exception {
+        // enable firewall first
+        firewall.enableFirewall(true);
+
+        // no rules inserted so all traffic other than broadcast and ARP-request-broadcast should be blocked
+
+        // simulate a packet-in event
+
+        this.setPacketIn(broadcastMalformedPacket);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        // malformed broadcast traffic should NOT be allowed
+        IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+    }
+
+    @Test
+    public void testLayer2Rule() throws Exception {
+        // enable firewall first
+        firewall.enableFirewall(true);
+
+        // add L2 rule
+        FirewallRule rule = new FirewallRule();
+        rule.dl_src = MACAddress.valueOf("00:44:33:22:11:00").toLong();
+        rule.wildcard_dl_src = false;
+        rule.dl_dst = MACAddress.valueOf("00:11:22:33:44:55").toLong();
+        rule.wildcard_dl_dst = false;
+        rule.priority = 1;
+        firewall.addRule(rule);
+
+        // add TCP deny all rule
+        rule = new FirewallRule();
+        rule.nw_proto = IPv4.PROTOCOL_TCP;
+        rule.wildcard_nw_proto = false;
+        rule.priority = 2;
+        rule.action = FirewallRule.FirewallAction.DENY;
+        firewall.addRule(rule);
+
+        // simulate a packet-in event
+
+        this.setPacketIn(tcpPacket);
+        firewall.receive(sw, this.packetIn, cntx);
+        verify(sw);
+
+        IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+        assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/flowcache/FlowReconcileMgrTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/flowcache/FlowReconcileMgrTest.java
new file mode 100644
index 0000000..0427828
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/flowcache/FlowReconcileMgrTest.java
@@ -0,0 +1,500 @@
+package net.floodlightcontroller.flowcache;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.ListIterator;
+
+import net.floodlightcontroller.core.IListener.Command;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.counter.SimpleCounter;
+import net.floodlightcontroller.counter.CounterValue.CounterType;
+import net.floodlightcontroller.flowcache.IFlowReconcileListener;
+import net.floodlightcontroller.flowcache.OFMatchReconcile;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.OFType;
+
+public class FlowReconcileMgrTest extends FloodlightTestCase {
+
+    protected MockFloodlightProvider mockFloodlightProvider;
+    protected FlowReconcileManager flowReconcileMgr;
+    protected MockThreadPoolService threadPool;
+    protected ICounterStoreService counterStore;
+    protected FloodlightModuleContext fmc;
+    
+    OFStatisticsRequest ofStatsRequest;
+
+    protected int NUM_FLOWS_PER_THREAD = 100;
+    protected int NUM_THREADS = 100;
+    
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        fmc = new FloodlightModuleContext();
+        flowReconcileMgr = new FlowReconcileManager();
+        threadPool = new MockThreadPoolService();
+        counterStore = createMock(ICounterStoreService.class);
+        
+        fmc.addService(ICounterStoreService.class, counterStore);
+        fmc.addService(IThreadPoolService.class, threadPool);
+        
+        threadPool.init(fmc);
+        flowReconcileMgr.init(fmc);
+
+        threadPool.startUp(fmc);
+        flowReconcileMgr.startUp(fmc);
+    }
+
+    /** Verify pipeline listener registration and ordering
+     * 
+     * @throws Exception
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testFlowReconcilePipeLine() throws Exception {
+        flowReconcileMgr.flowReconcileEnabled = true;
+    
+        IFlowReconcileListener r1 =
+            EasyMock.createNiceMock(IFlowReconcileListener.class);
+        IFlowReconcileListener r2 =
+            EasyMock.createNiceMock(IFlowReconcileListener.class);
+        IFlowReconcileListener r3 =
+            EasyMock.createNiceMock(IFlowReconcileListener.class);
+        
+        expect(r1.getName()).andReturn("r1").anyTimes();
+        expect(r2.getName()).andReturn("r2").anyTimes();
+        expect(r3.getName()).andReturn("r3").anyTimes();
+        
+        // Set the listeners' order: r1 -> r2 -> r3
+        expect(r1.isCallbackOrderingPrereq((OFType)anyObject(),
+            (String)anyObject())).andReturn(false).anyTimes();
+        expect(r1.isCallbackOrderingPostreq((OFType)anyObject(),
+            (String)anyObject())).andReturn(false).anyTimes();
+        expect(r2.isCallbackOrderingPrereq((OFType)anyObject(),
+            eq("r1"))).andReturn(true).anyTimes();
+        expect(r2.isCallbackOrderingPrereq((OFType)anyObject(),
+            eq("r3"))).andReturn(false).anyTimes();
+        expect(r2.isCallbackOrderingPostreq((OFType)anyObject(),
+            eq("r1"))).andReturn(false).anyTimes();
+        expect(r2.isCallbackOrderingPostreq((OFType)anyObject(),
+            eq("r3"))).andReturn(true).anyTimes();
+        expect(r3.isCallbackOrderingPrereq((OFType)anyObject(),
+            eq("r1"))).andReturn(false).anyTimes();
+        expect(r3.isCallbackOrderingPrereq((OFType)anyObject(),
+            eq("r2"))).andReturn(true).anyTimes();
+        expect(r3.isCallbackOrderingPostreq((OFType)anyObject(),
+            (String)anyObject())).andReturn(false).anyTimes();
+        
+        expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject())).
+                  andThrow(new RuntimeException("This is NOT an error! " +
+                            "We are testing exception catching."));
+        
+        SimpleCounter cnt = (SimpleCounter)SimpleCounter.createCounter(
+                            new Date(),
+                            CounterType.LONG);
+        cnt.increment();
+        expect(counterStore.getCounter(
+                flowReconcileMgr.controllerPktInCounterName))
+                .andReturn(cnt)
+                .anyTimes();
+        
+        replay(r1, r2, r3, counterStore);
+        flowReconcileMgr.clearFlowReconcileListeners();
+        flowReconcileMgr.addFlowReconcileListener(r1);
+        flowReconcileMgr.addFlowReconcileListener(r2);
+        flowReconcileMgr.addFlowReconcileListener(r3);
+        
+        int pre_flowReconcileThreadRunCount =
+                flowReconcileMgr.flowReconcileThreadRunCount;
+        Date startTime = new Date();
+        OFMatchReconcile ofmRcIn = new OFMatchReconcile();
+        try {
+            flowReconcileMgr.reconcileFlow(ofmRcIn);
+            flowReconcileMgr.doReconcile();
+        } catch (RuntimeException e) {
+            assertEquals(e.getMessage()
+                .startsWith("This is NOT an error!"), true);
+        }
+        
+        verify(r1, r2, r3);
+
+        // verify STOP works
+        reset(r1, r2, r3);
+        
+        // restart reconcileThread since it exited due to previous runtime
+        // exception.
+        flowReconcileMgr.startUp(fmc);
+        expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andReturn(Command.STOP).times(1);
+        expect(r2.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()));
+        expectLastCall().andAnswer(new IAnswer<Object>() {
+            public Object answer() {
+                fail("Unexpected call");
+                return Command.STOP;
+            }
+        }).anyTimes();
+        
+        pre_flowReconcileThreadRunCount =
+            flowReconcileMgr.flowReconcileThreadRunCount;
+        startTime = new Date();
+        replay(r1, r2, r3);
+        flowReconcileMgr.reconcileFlow(ofmRcIn);
+        while (flowReconcileMgr.flowReconcileThreadRunCount <=
+                pre_flowReconcileThreadRunCount) {
+            Thread.sleep(10);
+            Date currTime = new Date();
+            assertTrue((currTime.getTime() - startTime.getTime()) < 1000);
+        }
+        verify(r1, r2, r3);
+        
+        // verify CONTINUE works
+        reset(r1, r2, r3);
+        expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andReturn(Command.CONTINUE).times(1);
+        expect(r2.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andReturn(Command.STOP).times(1);
+        expect(r3.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()));
+        expectLastCall().andAnswer(new IAnswer<Object>() {
+            public Object answer() {
+                fail("Unexpected call");
+                return Command.STOP;
+            }
+        }).anyTimes();
+        
+        pre_flowReconcileThreadRunCount =
+            flowReconcileMgr.flowReconcileThreadRunCount;
+        startTime = new Date();
+        
+        replay(r1, r2, r3);
+        flowReconcileMgr.reconcileFlow(ofmRcIn);
+        while (flowReconcileMgr.flowReconcileThreadRunCount <=
+                pre_flowReconcileThreadRunCount) {
+            Thread.sleep(10);
+            Date currTime = new Date();
+            assertTrue((currTime.getTime() - startTime.getTime()) < 1000);
+        }
+        verify(r1, r2, r3);
+        
+        // verify CONTINUE works
+        reset(r1, r2, r3);
+        expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andReturn(Command.CONTINUE).times(1);
+        expect(r2.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andReturn(Command.CONTINUE).times(1);
+        expect(r3.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andReturn(Command.STOP).times(1);
+        
+        pre_flowReconcileThreadRunCount =
+            flowReconcileMgr.flowReconcileThreadRunCount;
+        startTime = new Date();
+        
+        replay(r1, r2, r3);
+        flowReconcileMgr.reconcileFlow(ofmRcIn);
+        while (flowReconcileMgr.flowReconcileThreadRunCount <=
+                pre_flowReconcileThreadRunCount) {
+            Thread.sleep(10);
+            Date currTime = new Date();
+            assertTrue((currTime.getTime() - startTime.getTime()) < 1000);
+        }
+        verify(r1, r2, r3);
+        
+        // Verify removeFlowReconcileListener
+        flowReconcileMgr.removeFlowReconcileListener(r1);
+        reset(r1, r2, r3);
+        expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()));
+        expectLastCall().andAnswer(new IAnswer<Object>() {
+            public Object answer() {
+                fail("Unexpected call to a listener that is " +
+                        "removed from the chain.");
+                return Command.STOP;
+            }
+        }).anyTimes();
+        expect(r2.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andReturn(Command.CONTINUE).times(1);
+        expect(r3.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andReturn(Command.STOP).times(1);
+        
+        pre_flowReconcileThreadRunCount =
+            flowReconcileMgr.flowReconcileThreadRunCount;
+        startTime = new Date();
+        replay(r1, r2, r3);
+        flowReconcileMgr.reconcileFlow(ofmRcIn);
+        while (flowReconcileMgr.flowReconcileThreadRunCount <=
+                pre_flowReconcileThreadRunCount) {
+            Thread.sleep(10);
+            Date currTime = new Date();
+            assertTrue((currTime.getTime() - startTime.getTime()) < 1000);
+        }
+        verify(r1, r2, r3);
+    }
+    
+    @Test
+    public void testGetPktInRate() {
+        internalTestGetPktInRate(CounterType.LONG);
+        internalTestGetPktInRate(CounterType.DOUBLE);
+    }
+    
+    protected void internalTestGetPktInRate(CounterType type) {
+        Date currentTime = new Date();
+        SimpleCounter newCnt = (SimpleCounter)SimpleCounter.createCounter(
+                                currentTime, type);
+        newCnt.increment(currentTime, 1);
+    
+        // Set the lastCounter time in the future of the current time
+        Date lastCounterTime = new Date(currentTime.getTime() + 1000);
+        flowReconcileMgr.lastPacketInCounter =
+                (SimpleCounter)SimpleCounter.createCounter(
+                    lastCounterTime, type);
+        flowReconcileMgr.lastPacketInCounter.increment(lastCounterTime, 1);
+    
+        assertEquals(FlowReconcileManager.MAX_SYSTEM_LOAD_PER_SECOND,
+                flowReconcileMgr.getPktInRate(newCnt, new Date()));
+    
+        // Verify the rate == 0 time difference is zero.
+        lastCounterTime = new Date(currentTime.getTime() - 1000);
+        flowReconcileMgr.lastPacketInCounter.increment(lastCounterTime, 1);
+        assertEquals(0, flowReconcileMgr.getPktInRate(newCnt, lastCounterTime));
+    
+        /** verify the computation is correct.
+         *  new = 2000, old = 1000, Tdiff = 1 second.
+         *  rate should be 1000/second
+         */
+        newCnt = (SimpleCounter)SimpleCounter.createCounter(
+                currentTime, type);
+        newCnt.increment(currentTime, 2000);
+    
+        lastCounterTime = new Date(currentTime.getTime() - 1000);
+        flowReconcileMgr.lastPacketInCounter =
+                (SimpleCounter)SimpleCounter.createCounter(
+                    lastCounterTime, type);
+        flowReconcileMgr.lastPacketInCounter.increment(lastCounterTime, 1000);
+        assertEquals(1000, flowReconcileMgr.getPktInRate(newCnt, currentTime));
+    
+        /** verify the computation is correct.
+         *  new = 2,000,000, old = 1,000,000, Tdiff = 2 second.
+         *  rate should be 1000/second
+         */
+        newCnt = (SimpleCounter)SimpleCounter.createCounter(
+                currentTime, type);
+        newCnt.increment(currentTime, 2000000);
+    
+        lastCounterTime = new Date(currentTime.getTime() - 2000);
+        flowReconcileMgr.lastPacketInCounter =
+                (SimpleCounter)SimpleCounter.createCounter(
+                    lastCounterTime, type);
+        flowReconcileMgr.lastPacketInCounter.increment(lastCounterTime,
+                1000000);
+        assertEquals(500000, flowReconcileMgr.getPktInRate(newCnt,
+                    currentTime));
+    }
+    
+    @Test
+    public void testGetCurrentCapacity() throws Exception {
+        // Disable the reconcile thread.
+        flowReconcileMgr.flowReconcileEnabled = false;
+    
+        int minFlows = FlowReconcileManager.MIN_FLOW_RECONCILE_PER_SECOND *
+                FlowReconcileManager.FLOW_RECONCILE_DELAY_MILLISEC / 1000;
+    
+        /** Verify the initial state, when packetIn counter has not
+         *  been created.
+         */
+        expect(counterStore.getCounter(
+                flowReconcileMgr.controllerPktInCounterName))
+        .andReturn(null)
+        .times(1);
+    
+        replay(counterStore);
+        assertEquals(minFlows, flowReconcileMgr.getCurrentCapacity());
+        verify(counterStore);
+    
+        /** Verify the initial state, when lastPacketInCounter is null */
+        reset(counterStore);
+        Date currentTime = new Date();
+        SimpleCounter newCnt = (SimpleCounter)SimpleCounter.createCounter(
+                        currentTime, CounterType.LONG);
+    
+        expect(counterStore.getCounter(
+            flowReconcileMgr.controllerPktInCounterName))
+        .andReturn(newCnt)
+        .times(1);
+        long initPktInCount = 10000;
+        newCnt.increment(currentTime, initPktInCount);
+    
+        replay(counterStore);
+        assertEquals(minFlows, flowReconcileMgr.getCurrentCapacity());
+        verify(counterStore);
+    
+        /** Now the lastPacketInCounter has been set.
+         *  lastCounter = 100,000 and newCounter = 300,000, t = 1 second
+         *  packetInRate = 200,000/sec.
+         *  capacity should be 500k - 200k = 300k
+         */
+        reset(counterStore);
+        newCnt = (SimpleCounter)SimpleCounter.createCounter(
+                    currentTime, CounterType.LONG);
+        currentTime = new Date(currentTime.getTime() + 200);
+        long nextPktInCount = 30000;
+        newCnt.increment(currentTime, nextPktInCount);
+    
+        expect(counterStore.getCounter(
+                flowReconcileMgr.controllerPktInCounterName))
+        .andReturn(newCnt)
+        .times(1);
+    
+        replay(counterStore);
+        // Wait for 1 second so that enough elapsed time to compute capacity.
+        Thread.sleep(1000);
+        int capacity = flowReconcileMgr.getCurrentCapacity();
+        verify(counterStore);
+        long expectedCap = (FlowReconcileManager.MAX_SYSTEM_LOAD_PER_SECOND -
+                (nextPktInCount - initPktInCount)) *
+                FlowReconcileManager.FLOW_RECONCILE_DELAY_MILLISEC / 1000;
+        assertEquals(expectedCap, capacity);
+    }
+    
+    private class FlowReconcileWorker implements Runnable {
+    @Override
+        public void run() {
+            OFMatchReconcile ofmRc = new OFMatchReconcile();
+            // push large number of flows to be reconciled.
+            for (int i = 0; i < NUM_FLOWS_PER_THREAD; i++) {
+                flowReconcileMgr.reconcileFlow(ofmRc);
+            }
+        }
+    }
+    
+    /** Verify the flows are sent to the reconcile pipeline in order.
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testQueueFlowsOrder() {
+        flowReconcileMgr.flowReconcileEnabled = false;
+        
+        IFlowReconcileListener r1 =
+            EasyMock.createNiceMock(IFlowReconcileListener.class);
+        
+        expect(r1.getName()).andReturn("r1").anyTimes();
+        
+        // Set the listeners' order: r1 -> r2 -> r3
+        expect(r1.isCallbackOrderingPrereq((OFType)anyObject(),
+            (String)anyObject())).andReturn(false).anyTimes();
+        expect(r1.isCallbackOrderingPostreq((OFType)anyObject(),
+            (String)anyObject())).andReturn(false).anyTimes();
+        
+        expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andAnswer(new IAnswer<Command>() {
+            @Override
+            public Command answer() throws Throwable {
+                ArrayList<OFMatchReconcile> ofmList =
+                    (ArrayList<OFMatchReconcile>)EasyMock.
+                        getCurrentArguments()[0];
+                ListIterator<OFMatchReconcile> lit = ofmList.listIterator();
+                int index = 0;
+                while (lit.hasNext()) {
+                    OFMatchReconcile ofm = lit.next();
+                    assertEquals(index++, ofm.cookie);
+                }
+                return Command.STOP;
+            }
+        }).times(1);
+        
+        SimpleCounter cnt = (SimpleCounter)SimpleCounter.createCounter(
+                            new Date(),
+                            CounterType.LONG);
+        cnt.increment();
+        expect(counterStore.getCounter(
+                flowReconcileMgr.controllerPktInCounterName))
+                .andReturn(cnt)
+                .anyTimes();
+        
+        replay(r1, counterStore);
+        flowReconcileMgr.clearFlowReconcileListeners();
+        flowReconcileMgr.addFlowReconcileListener(r1);
+        
+        OFMatchReconcile ofmRcIn = new OFMatchReconcile();
+        int index = 0;
+        for (index = 0; index < 10; index++) {
+            ofmRcIn.cookie = index;
+            flowReconcileMgr.reconcileFlow(ofmRcIn);
+        }
+        flowReconcileMgr.flowReconcileEnabled = true;
+        flowReconcileMgr.doReconcile();
+        
+        verify(r1);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testQueueFlowsByManyThreads() {
+        // Disable the reconcile thread so that the queue won't be emptied.
+        flowQueueTest(false);
+    
+        // Enable the reconcile thread. The queue should be empty.
+        Date currentTime = new Date();
+        SimpleCounter newCnt = (SimpleCounter)SimpleCounter.createCounter(
+                    currentTime, CounterType.LONG);
+    
+        expect(counterStore.getCounter(
+                    flowReconcileMgr.controllerPktInCounterName))
+        .andReturn(newCnt)
+        .anyTimes();
+        long initPktInCount = 10000;
+        newCnt.increment(currentTime, initPktInCount);
+    
+        IFlowReconcileListener r1 =
+                EasyMock.createNiceMock(IFlowReconcileListener.class);
+        
+        expect(r1.getName()).andReturn("r1").anyTimes();
+        
+        // Set the listeners' order: r1 -> r2 -> r3
+        expect(r1.isCallbackOrderingPrereq((OFType)anyObject(),
+                (String)anyObject())).andReturn(false).anyTimes();
+        expect(r1.isCallbackOrderingPostreq((OFType)anyObject(),
+                (String)anyObject())).andReturn(false).anyTimes();
+        
+        expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+        .andReturn(Command.CONTINUE).anyTimes();
+        
+        flowReconcileMgr.clearFlowReconcileListeners();
+        replay(r1, counterStore);
+        flowQueueTest(true);
+        verify(r1, counterStore);
+    }
+    
+    protected void flowQueueTest(boolean enableReconcileThread) {
+        flowReconcileMgr.flowReconcileEnabled = enableReconcileThread;
+    
+        // Simulate flow
+        for (int i = 0; i < NUM_THREADS; i++) {
+            Runnable worker = this.new FlowReconcileWorker();
+            Thread t = new Thread(worker);
+            t.start();
+        }
+    
+        Date startTime = new Date();
+        int totalFlows = NUM_THREADS * NUM_FLOWS_PER_THREAD;
+        if (enableReconcileThread) {
+            totalFlows = 0;
+        }
+        while (flowReconcileMgr.flowQueue.size() != totalFlows) {
+            Date currTime = new Date();
+            assertTrue((currTime.getTime() - startTime.getTime()) < 2000);
+        }
+    
+        // Make sure all flows are in the queue.
+        assertEquals(totalFlows, flowReconcileMgr.flowQueue.size());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java
new file mode 100644
index 0000000..7a37589
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java
@@ -0,0 +1,520 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.forwarding;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.test.MockDeviceManager;
+import net.floodlightcontroller.counter.CounterStore;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.ITopologyListener;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.flowcache.FlowReconcileManager;
+import net.floodlightcontroller.flowcache.IFlowReconcileService;
+import net.floodlightcontroller.forwarding.Forwarding;
+
+import org.easymock.Capture;
+import org.easymock.CaptureType;
+import org.easymock.EasyMock;
+import org.junit.Test;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+
+public class ForwardingTest extends FloodlightTestCase {
+    protected MockFloodlightProvider mockFloodlightProvider;
+    protected FloodlightContext cntx;
+    protected MockDeviceManager deviceManager;
+    protected IRoutingService routingEngine;
+    protected Forwarding forwarding;
+    protected FlowReconcileManager flowReconcileMgr;
+    protected ITopologyService topology;
+    protected MockThreadPoolService threadPool;
+    protected IOFSwitch sw1, sw2;
+    protected OFFeaturesReply swFeatures;
+    protected IDevice srcDevice, dstDevice1, dstDevice2;
+    protected OFPacketIn packetIn;
+    protected OFPacketOut packetOut;
+    protected OFPacketOut packetOutFlooded;
+    protected IPacket testPacket;
+    protected byte[] testPacketSerialized;
+    protected int expected_wildcards;
+    protected Date currentDate;
+    
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        cntx = new FloodlightContext();
+        
+        // Module loader setup
+        /*
+        Collection<Class<? extends IFloodlightModule>> mods = new ArrayList<Class<? extends IFloodlightModule>>();
+        Collection<IFloodlightService> mockedServices = new ArrayList<IFloodlightService>();
+        mods.add(Forwarding.class);
+        routingEngine = createMock(IRoutingService.class);
+        topology = createMock(ITopologyService.class);
+        mockedServices.add(routingEngine);
+        mockedServices.add(topology);
+        FloodlightTestModuleLoader fml = new FloodlightTestModuleLoader();
+        fml.setupModules(mods, mockedServices);
+        mockFloodlightProvider =
+        		(MockFloodlightProvider) fml.getModuleByName(MockFloodlightProvider.class);
+        deviceManager =
+        		(MockDeviceManager) fml.getModuleByName(MockDeviceManager.class);
+        threadPool =
+        		(MockThreadPoolService) fml.getModuleByName(MockThreadPoolService.class);
+        forwarding =
+        		(Forwarding) fml.getModuleByName(Forwarding.class);
+        */
+        mockFloodlightProvider = getMockFloodlightProvider();
+        forwarding = new Forwarding();
+        threadPool = new MockThreadPoolService();
+        deviceManager = new MockDeviceManager();
+        flowReconcileMgr = new FlowReconcileManager();
+        routingEngine = createMock(IRoutingService.class);
+        topology = createMock(ITopologyService.class);
+        DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier();
+
+
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        fmc.addService(IFloodlightProviderService.class, 
+                       mockFloodlightProvider);
+        fmc.addService(IThreadPoolService.class, threadPool);
+        fmc.addService(ITopologyService.class, topology);
+        fmc.addService(IRoutingService.class, routingEngine);
+        fmc.addService(ICounterStoreService.class, new CounterStore());
+        fmc.addService(IDeviceService.class, deviceManager);
+        fmc.addService(IFlowReconcileService.class, flowReconcileMgr);
+        fmc.addService(IEntityClassifierService.class, entityClassifier);
+
+        topology.addListener(anyObject(ITopologyListener.class));
+        expectLastCall().anyTimes();
+        replay(topology);
+        threadPool.init(fmc);
+        forwarding.init(fmc);
+        deviceManager.init(fmc);
+        flowReconcileMgr.init(fmc);
+        entityClassifier.init(fmc);
+        threadPool.startUp(fmc);
+        deviceManager.startUp(fmc);
+        forwarding.startUp(fmc);
+        flowReconcileMgr.startUp(fmc);
+        entityClassifier.startUp(fmc);
+        verify(topology);
+        
+        swFeatures = new OFFeaturesReply();
+        swFeatures.setBuffers(1000);
+        // Mock switches
+        sw1 = EasyMock.createMock(IOFSwitch.class);
+        expect(sw1.getId()).andReturn(1L).anyTimes();
+        expect(sw1.getBuffers()).andReturn(swFeatures.getBuffers()).anyTimes();
+        expect(sw1.getStringId())
+                .andReturn(HexString.toHexString(1L)).anyTimes();
+
+        sw2 = EasyMock.createMock(IOFSwitch.class);  
+        expect(sw2.getId()).andReturn(2L).anyTimes();
+        expect(sw2.getBuffers()).andReturn(swFeatures.getBuffers()).anyTimes();
+        expect(sw2.getStringId())
+                .andReturn(HexString.toHexString(2L)).anyTimes();
+
+        //fastWilcards mocked as this constant
+        int fastWildcards = 
+                OFMatch.OFPFW_IN_PORT | 
+                OFMatch.OFPFW_NW_PROTO | 
+                OFMatch.OFPFW_TP_SRC | 
+                OFMatch.OFPFW_TP_DST | 
+                OFMatch.OFPFW_NW_SRC_ALL | 
+                OFMatch.OFPFW_NW_DST_ALL |
+                OFMatch.OFPFW_NW_TOS;
+
+        expect(sw1.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer)fastWildcards).anyTimes();
+        expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes();
+
+        expect(sw2.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer)fastWildcards).anyTimes();
+        expect(sw2.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes();
+
+        // Load the switch map
+        Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>();
+        switches.put(1L, sw1);
+        switches.put(2L, sw2);
+        mockFloodlightProvider.setSwitches(switches);
+
+        // Build test packet
+        testPacket = new Ethernet()
+            .setDestinationMACAddress("00:11:22:33:44:55")
+            .setSourceMACAddress("00:44:33:22:11:00")
+            .setEtherType(Ethernet.TYPE_IPv4)
+            .setPayload(
+                new IPv4()
+                .setTtl((byte) 128)
+                .setSourceAddress("192.168.1.1")
+                .setDestinationAddress("192.168.1.2")
+                .setPayload(new UDP()
+                            .setSourcePort((short) 5000)
+                            .setDestinationPort((short) 5001)
+                            .setPayload(new Data(new byte[] {0x01}))));
+
+
+
+        currentDate = new Date();
+        
+        // Mock Packet-in
+        testPacketSerialized = testPacket.serialize();
+        packetIn = 
+                ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().
+                        getMessage(OFType.PACKET_IN))
+                        .setBufferId(-1)
+                        .setInPort((short) 1)
+                        .setPacketData(testPacketSerialized)
+                        .setReason(OFPacketInReason.NO_MATCH)
+                        .setTotalLength((short) testPacketSerialized.length);
+
+        // Mock Packet-out
+        packetOut = 
+                (OFPacketOut) mockFloodlightProvider.getOFMessageFactory().
+                    getMessage(OFType.PACKET_OUT);
+        packetOut.setBufferId(this.packetIn.getBufferId())
+            .setInPort(this.packetIn.getInPort());
+        List<OFAction> poactions = new ArrayList<OFAction>();
+        poactions.add(new OFActionOutput((short) 3, (short) 0xffff));
+        packetOut.setActions(poactions)
+            .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
+            .setPacketData(testPacketSerialized)
+            .setLengthU(OFPacketOut.MINIMUM_LENGTH+
+                        packetOut.getActionsLength()+
+                        testPacketSerialized.length);
+        
+        // Mock Packet-out with OFPP_FLOOD action
+        packetOutFlooded = 
+                (OFPacketOut) mockFloodlightProvider.getOFMessageFactory().
+                    getMessage(OFType.PACKET_OUT);
+        packetOutFlooded.setBufferId(this.packetIn.getBufferId())
+            .setInPort(this.packetIn.getInPort());
+        poactions = new ArrayList<OFAction>();
+        poactions.add(new OFActionOutput(OFPort.OFPP_FLOOD.getValue(), 
+                                         (short) 0xffff));
+        packetOutFlooded.setActions(poactions)
+            .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
+            .setPacketData(testPacketSerialized)
+            .setLengthU(OFPacketOut.MINIMUM_LENGTH+
+                        packetOutFlooded.getActionsLength()+
+                        testPacketSerialized.length);
+
+        expected_wildcards = fastWildcards;
+        expected_wildcards &= ~OFMatch.OFPFW_IN_PORT & 
+                              ~OFMatch.OFPFW_DL_VLAN &
+                              ~OFMatch.OFPFW_DL_SRC & 
+                              ~OFMatch.OFPFW_DL_DST;
+        expected_wildcards &= ~OFMatch.OFPFW_NW_SRC_MASK & 
+                              ~OFMatch.OFPFW_NW_DST_MASK;
+
+        IFloodlightProviderService.bcStore.
+            put(cntx, 
+                IFloodlightProviderService.CONTEXT_PI_PAYLOAD, 
+                (Ethernet)testPacket);
+    }
+    
+    enum DestDeviceToLearn { NONE, DEVICE1 ,DEVICE2 };
+    public void learnDevices(DestDeviceToLearn destDeviceToLearn) {
+        // Build src and dest devices
+        byte[] dataLayerSource = ((Ethernet)testPacket).getSourceMACAddress();
+        byte[] dataLayerDest = 
+                ((Ethernet)testPacket).getDestinationMACAddress();
+        int networkSource =
+                ((IPv4)((Ethernet)testPacket).getPayload()).
+                    getSourceAddress();
+        int networkDest = 
+                ((IPv4)((Ethernet)testPacket).getPayload()).
+                    getDestinationAddress();
+        
+        reset(topology);
+        expect(topology.isAttachmentPointPort(1L, (short)1))
+                                              .andReturn(true)
+                                              .anyTimes();
+        expect(topology.isAttachmentPointPort(2L, (short)3))
+                                              .andReturn(true)
+                                              .anyTimes();
+        expect(topology.isAttachmentPointPort(1L, (short)3))
+                                              .andReturn(true)
+                                              .anyTimes();
+        replay(topology);
+
+        srcDevice = 
+                deviceManager.learnEntity(Ethernet.toLong(dataLayerSource), 
+                                          null, networkSource,
+                                          1L, 1);
+        IDeviceService.fcStore. put(cntx, 
+                                    IDeviceService.CONTEXT_SRC_DEVICE,
+                                    srcDevice);
+        if (destDeviceToLearn == DestDeviceToLearn.DEVICE1) {
+            dstDevice1 = 
+                    deviceManager.learnEntity(Ethernet.toLong(dataLayerDest), 
+                                              null, networkDest,
+                                              2L, 3);
+            IDeviceService.fcStore.put(cntx, 
+                                       IDeviceService.CONTEXT_DST_DEVICE, 
+                                       dstDevice1);
+        }
+        if (destDeviceToLearn == DestDeviceToLearn.DEVICE2) {
+            dstDevice2 = 
+                    deviceManager.learnEntity(Ethernet.toLong(dataLayerDest), 
+                                              null, networkDest,
+                                              1L, 3);
+            IDeviceService.fcStore.put(cntx, 
+                                       IDeviceService.CONTEXT_DST_DEVICE, 
+                                       dstDevice2);
+        }
+        verify(topology);
+    }
+
+    @Test
+    public void testForwardMultiSwitchPath() throws Exception {
+        learnDevices(DestDeviceToLearn.DEVICE1);
+        
+        Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL);
+        Capture<OFMessage> wc2 = new Capture<OFMessage>(CaptureType.ALL);
+        Capture<FloodlightContext> bc1 = 
+                new Capture<FloodlightContext>(CaptureType.ALL);
+        Capture<FloodlightContext> bc2 = 
+                new Capture<FloodlightContext>(CaptureType.ALL);
+
+
+        Route route = new Route(1L, 2L);
+        List<NodePortTuple> nptList = new ArrayList<NodePortTuple>();
+        nptList.add(new NodePortTuple(1L, (short)1));
+        nptList.add(new NodePortTuple(1L, (short)3));
+        nptList.add(new NodePortTuple(2L, (short)1));
+        nptList.add(new NodePortTuple(2L, (short)3));
+        route.setPath(nptList);
+        expect(routingEngine.getRoute(1L, (short)1, 2L, (short)3)).andReturn(route).atLeastOnce();
+
+        // Expected Flow-mods
+        OFMatch match = new OFMatch();
+        match.loadFromPacket(testPacketSerialized, (short) 1);
+        OFActionOutput action = new OFActionOutput((short)3, (short)0xffff);
+        List<OFAction> actions = new ArrayList<OFAction>();
+        actions.add(action);
+
+        OFFlowMod fm1 = 
+                (OFFlowMod) mockFloodlightProvider.getOFMessageFactory().
+                    getMessage(OFType.FLOW_MOD);
+        fm1.setIdleTimeout((short)5)
+            .setMatch(match.clone()
+                    .setWildcards(expected_wildcards))
+            .setActions(actions)
+            .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+            .setCookie(2L << 52)
+            .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
+        OFFlowMod fm2 = fm1.clone();
+        ((OFActionOutput)fm2.getActions().get(0)).setPort((short) 3);
+
+        sw1.write(capture(wc1), capture(bc1));
+        expectLastCall().anyTimes(); 
+        sw2.write(capture(wc2), capture(bc2));
+        expectLastCall().anyTimes(); 
+
+        reset(topology);
+        expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+        expect(topology.getL2DomainId(2L)).andReturn(1L).anyTimes();
+        expect(topology.isAttachmentPointPort(1L,  (short)1)).andReturn(true).anyTimes();
+        expect(topology.isAttachmentPointPort(2L,  (short)3)).andReturn(true).anyTimes();
+        expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes();
+
+        // Reset mocks, trigger the packet in, and validate results
+        replay(sw1, sw2, routingEngine, topology);
+        forwarding.receive(sw1, this.packetIn, cntx);
+        verify(sw1, sw2, routingEngine);
+        
+        assertTrue(wc1.hasCaptured());  // wc1 should get packetout + flowmod.
+        assertTrue(wc2.hasCaptured());  // wc2 should be a flowmod.
+        
+        List<OFMessage> msglist = wc1.getValues();
+        
+        for (OFMessage m: msglist) {
+            if (m instanceof OFFlowMod) 
+                assertEquals(fm1, m);
+            else if (m instanceof OFPacketOut)
+                assertEquals(packetOut, m);
+        }
+        
+        OFMessage m = wc2.getValue();
+        assert (m instanceof OFFlowMod);
+        assertTrue(m.equals(fm2));
+    }
+
+    @Test
+    public void testForwardSingleSwitchPath() throws Exception {        
+        learnDevices(DestDeviceToLearn.DEVICE2);
+        
+        Route route = new  Route(1L, 1L);
+        route.getPath().add(new NodePortTuple(1L, (short)1));
+        route.getPath().add(new NodePortTuple(1L, (short)3));
+        expect(routingEngine.getRoute(1L, (short)1, 1L, (short)3)).andReturn(route).atLeastOnce();
+
+        // Expected Flow-mods
+        OFMatch match = new OFMatch();
+        match.loadFromPacket(testPacketSerialized, (short) 1);
+        OFActionOutput action = new OFActionOutput((short)3, (short)0xffff);
+        List<OFAction> actions = new ArrayList<OFAction>();
+        actions.add(action);
+
+        OFFlowMod fm1 = 
+                (OFFlowMod) mockFloodlightProvider.getOFMessageFactory().
+                    getMessage(OFType.FLOW_MOD);
+        fm1.setIdleTimeout((short)5)
+            .setMatch(match.clone()
+                    .setWildcards(expected_wildcards))
+            .setActions(actions)
+            .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+            .setCookie(2L << 52)
+            .setLengthU(OFFlowMod.MINIMUM_LENGTH +
+                        OFActionOutput.MINIMUM_LENGTH);
+
+        // Record expected packet-outs/flow-mods
+        sw1.write(fm1, cntx);
+        sw1.write(packetOut, cntx);
+        
+        reset(topology);
+        expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes();
+        expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+        expect(topology.isAttachmentPointPort(1L,  (short)1)).andReturn(true).anyTimes();
+        expect(topology.isAttachmentPointPort(1L,  (short)3)).andReturn(true).anyTimes();
+
+        // Reset mocks, trigger the packet in, and validate results
+        replay(sw1, sw2, routingEngine, topology);
+        forwarding.receive(sw1, this.packetIn, cntx);
+        verify(sw1, sw2, routingEngine);
+    }
+
+    @Test
+    public void testFlowModDampening() throws Exception {        
+        learnDevices(DestDeviceToLearn.DEVICE2);
+    
+        reset(topology);
+        expect(topology.isAttachmentPointPort(EasyMock.anyLong(), EasyMock.anyShort()))
+        .andReturn(true).anyTimes();
+        expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+        replay(topology);
+    
+    
+        Route route = new  Route(1L, 1L);
+        route.getPath().add(new NodePortTuple(1L, (short)1));
+        route.getPath().add(new NodePortTuple(1L, (short)3));
+        expect(routingEngine.getRoute(1L, (short)1, 1L, (short)3)).andReturn(route).atLeastOnce();
+    
+        // Expected Flow-mods
+        OFMatch match = new OFMatch();
+        match.loadFromPacket(testPacketSerialized, (short) 1);
+        OFActionOutput action = new OFActionOutput((short)3, (short)0xffff);
+        List<OFAction> actions = new ArrayList<OFAction>();
+        actions.add(action);
+    
+        OFFlowMod fm1 = 
+                (OFFlowMod) mockFloodlightProvider.getOFMessageFactory().
+                    getMessage(OFType.FLOW_MOD);
+        fm1.setIdleTimeout((short)5)
+            .setMatch(match.clone()
+                    .setWildcards(expected_wildcards))
+            .setActions(actions)
+            .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+            .setCookie(2L << 52)
+            .setLengthU(OFFlowMod.MINIMUM_LENGTH +
+                        OFActionOutput.MINIMUM_LENGTH);
+    
+        // Record expected packet-outs/flow-mods
+        // We will inject the packet_in 3 times and expect 1 flow mod and
+        // 3 packet outs due to flow mod dampening
+        sw1.write(fm1, cntx);
+        expectLastCall().once();
+        sw1.write(packetOut, cntx);
+        expectLastCall().times(3);
+        
+        reset(topology);
+        expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes();
+        expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+        expect(topology.isAttachmentPointPort(1L,  (short)1)).andReturn(true).anyTimes();
+        expect(topology.isAttachmentPointPort(1L,  (short)3)).andReturn(true).anyTimes();
+    
+        // Reset mocks, trigger the packet in, and validate results
+        replay(sw1, routingEngine, topology);
+        forwarding.receive(sw1, this.packetIn, cntx);
+        forwarding.receive(sw1, this.packetIn, cntx);
+        forwarding.receive(sw1, this.packetIn, cntx);
+        verify(sw1, routingEngine);
+    }
+
+    @Test
+    public void testForwardNoPath() throws Exception {
+        learnDevices(DestDeviceToLearn.NONE);
+
+        // Set no destination attachment point or route
+        // expect no Flow-mod but expect the packet to be flooded 
+                
+        // Reset mocks, trigger the packet in, and validate results
+        reset(topology);
+        expect(topology.isIncomingBroadcastAllowed(1L, (short)1)).andReturn(true).anyTimes();
+        expect(topology.isAttachmentPointPort(EasyMock.anyLong(),
+                                              EasyMock.anyShort()))
+                                              .andReturn(true)
+                                              .anyTimes();
+        expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD))
+                .andReturn(true).anyTimes();
+        sw1.write(packetOutFlooded, cntx);
+        expectLastCall().once();
+        replay(sw1, sw2, routingEngine, topology);
+        forwarding.receive(sw1, this.packetIn, cntx);
+        verify(sw1, sw2, routingEngine);
+    }
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/hub/HubTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/hub/HubTest.java
new file mode 100644
index 0000000..b4a215c
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/hub/HubTest.java
@@ -0,0 +1,167 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.hub;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.easymock.EasyMock.capture;
+
+import java.util.Arrays;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+import org.easymock.Capture;
+import org.easymock.CaptureType;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class HubTest extends FloodlightTestCase {
+    protected OFPacketIn packetIn;
+    protected IPacket testPacket;
+    protected byte[] testPacketSerialized;
+    private   MockFloodlightProvider mockFloodlightProvider;
+    private Hub hub;
+    
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mockFloodlightProvider = getMockFloodlightProvider();
+        hub = new Hub();
+        mockFloodlightProvider.addOFMessageListener(OFType.PACKET_IN, hub);
+        hub.setFloodlightProvider(mockFloodlightProvider);
+        
+        // Build our test packet
+        this.testPacket = new Ethernet()
+            .setDestinationMACAddress("00:11:22:33:44:55")
+            .setSourceMACAddress("00:44:33:22:11:00")
+            .setEtherType(Ethernet.TYPE_IPv4)
+            .setPayload(
+                new IPv4()
+                .setTtl((byte) 128)
+                .setSourceAddress("192.168.1.1")
+                .setDestinationAddress("192.168.1.2")
+                .setPayload(new UDP()
+                            .setSourcePort((short) 5000)
+                            .setDestinationPort((short) 5001)
+                            .setPayload(new Data(new byte[] {0x01}))));
+        this.testPacketSerialized = testPacket.serialize();
+
+        // Build the PacketIn
+        this.packetIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN))
+            .setBufferId(-1)
+            .setInPort((short) 1)
+            .setPacketData(this.testPacketSerialized)
+            .setReason(OFPacketInReason.NO_MATCH)
+            .setTotalLength((short) this.testPacketSerialized.length);
+    }
+
+    @Test
+    public void testFloodNoBufferId() throws Exception {
+        // build our expected flooded packetOut
+        OFPacketOut po = ((OFPacketOut) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT))
+            .setActions(Arrays.asList(new OFAction[] {new OFActionOutput().setPort(OFPort.OFPP_FLOOD.getValue())}))
+            .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
+            .setBufferId(-1)
+            .setInPort((short) 1)
+            .setPacketData(this.testPacketSerialized);
+        po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLengthU()
+                + this.testPacketSerialized.length);
+
+        // Mock up our expected behavior
+        IOFSwitch mockSwitch = createMock(IOFSwitch.class);
+        
+        Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL);
+        Capture<FloodlightContext> bc1 = new Capture<FloodlightContext>(CaptureType.ALL);
+        
+        mockSwitch.write(capture(wc1), capture(bc1));
+
+        // Start recording the replay on the mocks
+        replay(mockSwitch);
+        // Get the listener and trigger the packet in
+        IOFMessageListener listener = mockFloodlightProvider.getListeners().get(
+                OFType.PACKET_IN).get(0);
+        listener.receive(mockSwitch, this.packetIn,
+                         parseAndAnnotate(this.packetIn));
+
+        // Verify the replay matched our expectations
+        verify(mockSwitch);
+        
+        assertTrue(wc1.hasCaptured());
+        OFMessage m = wc1.getValue();
+        assert(m.equals(po));
+    }
+
+    @Test
+    public void testFloodBufferId() throws Exception {
+        MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider();
+        this.packetIn.setBufferId(10);
+
+        // build our expected flooded packetOut
+        OFPacketOut po = ((OFPacketOut) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT))
+            .setActions(Arrays.asList(new OFAction[] {new OFActionOutput().setPort(OFPort.OFPP_FLOOD.getValue())}))
+            .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
+            .setBufferId(10)
+            .setInPort((short) 1);
+        po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLengthU());
+
+        // Mock up our expected behavior
+        IOFSwitch mockSwitch = createMock(IOFSwitch.class);
+        Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL);
+        Capture<FloodlightContext> bc1 = new Capture<FloodlightContext>(CaptureType.ALL);
+        
+        mockSwitch.write(capture(wc1), capture(bc1));
+
+        // Start recording the replay on the mocks
+        replay(mockSwitch);
+        // Get the listener and trigger the packet in
+        IOFMessageListener listener = mockFloodlightProvider.getListeners().get(
+                OFType.PACKET_IN).get(0);
+        listener.receive(mockSwitch, this.packetIn,
+                         parseAndAnnotate(this.packetIn));
+
+        // Verify the replay matched our expectations
+        verify(mockSwitch);
+        
+        assertTrue(wc1.hasCaptured());
+        OFMessage m = wc1.getValue();
+        assert(m.equals(po));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java
new file mode 100644
index 0000000..a68a1b8
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java
@@ -0,0 +1,236 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.learningswitch;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightTestModuleLoader;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class LearningSwitchTest extends FloodlightTestCase {
+    protected OFPacketIn packetIn;
+    protected IPacket testPacket;
+    protected byte[] testPacketSerialized;
+    protected IPacket broadcastPacket;
+    protected byte[] broadcastPacketSerialized;
+    protected IPacket testPacketReply;
+    protected byte[] testPacketReplySerialized;
+    private LearningSwitch learningSwitch;
+    
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        FloodlightTestModuleLoader fml = new FloodlightTestModuleLoader();
+        Collection<Class<? extends IFloodlightModule>> mods 
+        	= new ArrayList<Class<? extends IFloodlightModule>>();
+        mods.add(LearningSwitch.class);
+        fml.setupModules(mods, null);
+        learningSwitch = (LearningSwitch) fml.getModuleByName(LearningSwitch.class);
+        mockFloodlightProvider = 
+        		(MockFloodlightProvider) fml.getModuleByName(MockFloodlightProvider.class);
+       
+        // Build our test packet
+        this.testPacket = new Ethernet()
+            .setDestinationMACAddress("00:11:22:33:44:55")
+            .setSourceMACAddress("00:44:33:22:11:00")
+            .setVlanID((short) 42)
+            .setEtherType(Ethernet.TYPE_IPv4)
+            .setPayload(
+                new IPv4()
+                .setTtl((byte) 128)
+                .setSourceAddress("192.168.1.1")
+                .setDestinationAddress("192.168.1.2")
+                .setPayload(new UDP()
+                            .setSourcePort((short) 5000)
+                            .setDestinationPort((short) 5001)
+                            .setPayload(new Data(new byte[] {0x01}))));
+        this.testPacketSerialized = testPacket.serialize();
+        // Build a broadcast packet
+        this.broadcastPacket = new Ethernet()
+            .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+            .setSourceMACAddress("00:44:33:22:11:00")
+            .setVlanID((short) 42)
+            .setEtherType(Ethernet.TYPE_IPv4)
+            .setPayload(
+                new IPv4()
+                .setTtl((byte) 128)
+                .setSourceAddress("192.168.1.1")
+                .setDestinationAddress("192.168.255.255")
+                .setPayload(new UDP()
+                        .setSourcePort((short) 5000)
+                        .setDestinationPort((short) 5001)
+                        .setPayload(new Data(new byte[] {0x01}))));
+
+        this.broadcastPacketSerialized = broadcastPacket.serialize();
+        this.testPacketReply = new Ethernet()
+            .setDestinationMACAddress("00:44:33:22:11:00")
+            .setSourceMACAddress("00:11:22:33:44:55")
+            .setVlanID((short) 42)
+            .setEtherType(Ethernet.TYPE_IPv4)
+            .setPayload(
+                    new IPv4()
+                    .setTtl((byte) 128)
+                    .setSourceAddress("192.168.1.2")
+                    .setDestinationAddress("192.168.1.1")
+                    .setPayload(new UDP()
+                    .setSourcePort((short) 5001)
+                    .setDestinationPort((short) 5000)
+                    .setPayload(new Data(new byte[] {0x02}))));
+        this.testPacketReplySerialized = testPacketReply.serialize();
+
+        // Build the PacketIn
+        this.packetIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN))
+            .setBufferId(-1)
+            .setInPort((short) 1)
+            .setPacketData(this.testPacketSerialized)
+            .setReason(OFPacketInReason.NO_MATCH)
+            .setTotalLength((short) this.testPacketSerialized.length);
+    }
+
+    @Test
+    public void testFlood() throws Exception {
+        // build our expected flooded packetOut
+        OFPacketOut po = new OFPacketOut()
+            .setActions(Arrays.asList(new OFAction[] {new OFActionOutput().setPort(OFPort.OFPP_FLOOD.getValue())}))
+            .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
+            .setBufferId(-1)
+            .setInPort((short)1)
+            .setPacketData(this.testPacketSerialized);
+        po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLengthU()
+                + this.testPacketSerialized.length);
+
+        // Mock up our expected behavior
+        IOFSwitch mockSwitch = createMock(IOFSwitch.class);
+        expect(mockSwitch.getStringId()).andReturn("00:11:22:33:44:55:66:77").anyTimes();
+        mockSwitch.write(po, null);
+
+        // Start recording the replay on the mocks
+        replay(mockSwitch);
+        // Get the listener and trigger the packet in
+        IOFMessageListener listener = mockFloodlightProvider.getListeners().get(
+                OFType.PACKET_IN).get(0);
+        // Make sure it's the right listener
+        listener.receive(mockSwitch, this.packetIn, parseAndAnnotate(this.packetIn));
+
+        // Verify the replay matched our expectations      
+        short result = learningSwitch.getFromPortMap(mockSwitch, Ethernet.toLong(Ethernet.toMACAddress("00:44:33:22:11:00")), (short) 42).shortValue();
+        verify(mockSwitch);
+
+        // Verify the MAC table inside the switch
+        assertEquals(1, result);
+    }
+    
+    @Test
+    public void testFlowMod() throws Exception {
+        // tweak the test packet in since we need a bufferId
+        this.packetIn.setBufferId(50);
+
+        // build expected flow mods
+        OFMessage fm1 = ((OFFlowMod) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD))
+            .setActions(Arrays.asList(new OFAction[] {
+                    new OFActionOutput().setPort((short) 2).setMaxLength((short) -1)}))
+            .setBufferId(50)
+            .setCommand(OFFlowMod.OFPFC_ADD)
+            .setIdleTimeout((short) 5)
+            .setMatch(new OFMatch()
+                .loadFromPacket(testPacketSerialized, (short) 1)
+                .setWildcards(OFMatch.OFPFW_NW_PROTO | OFMatch.OFPFW_TP_SRC | OFMatch.OFPFW_TP_DST
+                        | OFMatch.OFPFW_NW_TOS))
+            .setOutPort(OFPort.OFPP_NONE.getValue())
+            .setCookie(1L << 52)
+            .setPriority((short) 100)
+            .setFlags((short)(1 << 0))
+            .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
+        OFMessage fm2 = ((OFFlowMod) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD))
+            .setActions(Arrays.asList(new OFAction[] {
+                    new OFActionOutput().setPort((short) 1).setMaxLength((short) -1)}))
+            .setBufferId(-1)
+            .setCommand(OFFlowMod.OFPFC_ADD)
+            .setIdleTimeout((short) 5)
+            .setMatch(new OFMatch()
+                .loadFromPacket(testPacketReplySerialized, (short) 2)
+                .setWildcards(OFMatch.OFPFW_NW_PROTO | OFMatch.OFPFW_TP_SRC | OFMatch.OFPFW_TP_DST
+                        | OFMatch.OFPFW_NW_TOS))
+            .setOutPort(OFPort.OFPP_NONE.getValue())
+            .setCookie(1L << 52)
+            .setPriority((short) 100)
+            .setFlags((short)(1 << 0))
+            .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
+
+        // Mock up our expected behavior
+        IOFSwitch mockSwitch = createMock(IOFSwitch.class);
+        expect(mockSwitch.getId()).andReturn(1L).anyTimes();
+        expect(mockSwitch.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer) (OFMatch.OFPFW_IN_PORT | OFMatch.OFPFW_NW_PROTO
+                | OFMatch.OFPFW_TP_SRC | OFMatch.OFPFW_TP_DST | OFMatch.OFPFW_NW_SRC_ALL
+                | OFMatch.OFPFW_NW_DST_ALL | OFMatch.OFPFW_NW_TOS));
+        mockSwitch.write(fm1, null);
+        mockSwitch.write(fm2, null);
+
+        // Start recording the replay on the mocks
+        replay(mockSwitch);
+
+        // Populate the MAC table
+        learningSwitch.addToPortMap(mockSwitch,
+                Ethernet.toLong(Ethernet.toMACAddress("00:11:22:33:44:55")), (short) 42, (short) 2);
+        
+        // Get the listener and trigger the packet in
+        IOFMessageListener listener = mockFloodlightProvider.getListeners().get(
+                OFType.PACKET_IN).get(0);
+        listener.receive(mockSwitch, this.packetIn, parseAndAnnotate(this.packetIn));
+        
+        // Verify the replay matched our expectations
+        short result = learningSwitch.getFromPortMap(mockSwitch, Ethernet.toLong(Ethernet.toMACAddress("00:44:33:22:11:00")), (short) 42).shortValue();
+        verify(mockSwitch);
+
+        // Verify the MAC table inside the switch
+        assertEquals(1, result);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java
new file mode 100644
index 0000000..6e99acd
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java
@@ -0,0 +1,432 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.linkdiscovery.internal;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.Collections;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.LinkInfo;
+import net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.topology.TopologyManager;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class LinkDiscoveryManagerTest extends FloodlightTestCase {
+
+    private TestLinkDiscoveryManager ldm;
+    protected static Logger log = LoggerFactory.getLogger(LinkDiscoveryManagerTest.class);
+    
+    public class TestLinkDiscoveryManager extends LinkDiscoveryManager {
+        public boolean isSendLLDPsCalled = false;
+        public boolean isClearLinksCalled = false;
+
+        @Override
+        protected void discoverOnAllPorts() {
+            isSendLLDPsCalled = true;
+            super.discoverOnAllPorts();
+        }
+
+        public void reset() {
+            isSendLLDPsCalled = false;
+            isClearLinksCalled = false;
+        }
+
+        @Override
+        protected void clearAllLinks() {
+            isClearLinksCalled = true;
+            super.clearAllLinks();
+        }
+    }
+    
+    public LinkDiscoveryManager getTopology() {
+        return ldm;
+    }
+
+    public IOFSwitch createMockSwitch(Long id) {
+        IOFSwitch mockSwitch = createNiceMock(IOFSwitch.class);
+        expect(mockSwitch.getId()).andReturn(id).anyTimes();
+        return mockSwitch;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        FloodlightModuleContext cntx = new FloodlightModuleContext();
+        ldm = new TestLinkDiscoveryManager();
+        TopologyManager routingEngine = new TopologyManager();
+        ldm.linkDiscoveryAware = new ArrayList<ILinkDiscoveryListener>();
+        MockThreadPoolService tp = new MockThreadPoolService();
+        RestApiServer restApi = new RestApiServer();
+        cntx.addService(IRestApiService.class, restApi);
+        cntx.addService(IThreadPoolService.class, tp);
+        cntx.addService(IRoutingService.class, routingEngine);
+        cntx.addService(ILinkDiscoveryService.class, ldm);
+        cntx.addService(ITopologyService.class, ldm);
+        cntx.addService(IStorageSourceService.class, new MemoryStorageSource());
+        cntx.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
+        restApi.init(cntx);
+        tp.init(cntx);
+        routingEngine.init(cntx);
+        ldm.init(cntx);
+        restApi.startUp(cntx);
+        tp.startUp(cntx);
+        routingEngine.startUp(cntx);
+        ldm.startUp(cntx);
+
+        IOFSwitch sw1 = createMockSwitch(1L);
+        IOFSwitch sw2 = createMockSwitch(2L);
+        Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>();
+        switches.put(1L, sw1);
+        switches.put(2L, sw2);
+        getMockFloodlightProvider().setSwitches(switches);
+        replay(sw1, sw2);
+    }
+
+    @Test
+    public void testAddOrUpdateLink() throws Exception {
+        LinkDiscoveryManager topology = getTopology();
+
+        Link lt = new Link(1L, 2, 2L, 1);
+        LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+                                     System.currentTimeMillis(), null,
+                                     0, 0);
+        topology.addOrUpdateLink(lt, info);
+
+
+        NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+        NodePortTuple dstNpt = new NodePortTuple(2L, 1);
+
+        // check invariants hold
+        assertNotNull(topology.switchLinks.get(lt.getSrc()));
+        assertTrue(topology.switchLinks.get(lt.getSrc()).contains(lt));
+        assertNotNull(topology.portLinks.get(srcNpt));
+        assertTrue(topology.portLinks.get(srcNpt).contains(lt));
+        assertNotNull(topology.portLinks.get(dstNpt));
+        assertTrue(topology.portLinks.get(dstNpt).contains(lt));
+        assertTrue(topology.links.containsKey(lt));
+    }
+
+    @Test
+    public void testDeleteLink() throws Exception {
+        LinkDiscoveryManager topology = getTopology();
+
+        Link lt = new Link(1L, 2, 2L, 1);
+        LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+                                     System.currentTimeMillis(), null,
+                                     0, 0);
+        topology.addOrUpdateLink(lt, info);
+        topology.deleteLinks(Collections.singletonList(lt), "Test");
+
+        // check invariants hold
+        assertNull(topology.switchLinks.get(lt.getSrc()));
+        assertNull(topology.switchLinks.get(lt.getDst()));
+        assertNull(topology.portLinks.get(lt.getSrc()));
+        assertNull(topology.portLinks.get(lt.getDst()));
+        assertTrue(topology.links.isEmpty());
+    }
+
+    @Test
+    public void testAddOrUpdateLinkToSelf() throws Exception {
+        LinkDiscoveryManager topology = getTopology();
+
+        Link lt = new Link(1L, 2, 2L, 3);
+        NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+        NodePortTuple dstNpt = new NodePortTuple(2L, 3);
+
+        LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+                                     System.currentTimeMillis(), null,
+                                     0, 0);
+        topology.addOrUpdateLink(lt, info);
+
+        // check invariants hold
+        assertNotNull(topology.switchLinks.get(lt.getSrc()));
+        assertTrue(topology.switchLinks.get(lt.getSrc()).contains(lt));
+        assertNotNull(topology.portLinks.get(srcNpt));
+        assertTrue(topology.portLinks.get(srcNpt).contains(lt));
+        assertNotNull(topology.portLinks.get(dstNpt));
+        assertTrue(topology.portLinks.get(dstNpt).contains(lt));
+        assertTrue(topology.links.containsKey(lt));
+    }
+
+    @Test
+    public void testDeleteLinkToSelf() throws Exception {
+        LinkDiscoveryManager topology = getTopology();
+
+        Link lt = new Link(1L, 2, 1L, 3);
+        NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+        NodePortTuple dstNpt = new NodePortTuple(2L, 3);
+
+        LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+                                     System.currentTimeMillis(), null,
+                                     0, 0);
+        topology.addOrUpdateLink(lt, info);
+        topology.deleteLinks(Collections.singletonList(lt), "Test to self");
+
+        // check invariants hold
+        assertNull(topology.switchLinks.get(lt.getSrc()));
+        assertNull(topology.switchLinks.get(lt.getDst()));
+        assertNull(topology.portLinks.get(srcNpt));
+        assertNull(topology.portLinks.get(dstNpt));
+        assertTrue(topology.links.isEmpty());
+    }
+
+    @Test
+    public void testRemovedSwitch() {
+        LinkDiscoveryManager topology = getTopology();
+
+        Link lt = new Link(1L, 2, 2L, 1);
+        NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+        NodePortTuple dstNpt = new NodePortTuple(2L, 1);
+        LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+                                     System.currentTimeMillis(), null,
+                                     0, 0);
+        topology.addOrUpdateLink(lt, info);
+
+        IOFSwitch sw1 = getMockFloodlightProvider().getSwitches().get(1L);
+        IOFSwitch sw2 = getMockFloodlightProvider().getSwitches().get(2L);
+        // Mock up our expected behavior
+        topology.removedSwitch(sw1);
+        verify(sw1, sw2);
+
+        // check invariants hold
+        assertNull(topology.switchLinks.get(lt.getSrc()));
+        assertNull(topology.switchLinks.get(lt.getDst()));
+        assertNull(topology.portLinks.get(srcNpt));
+        assertNull(topology.portLinks.get(dstNpt));
+        assertTrue(topology.links.isEmpty());
+    }
+
+    @Test
+    public void testRemovedSwitchSelf() {
+        LinkDiscoveryManager topology = getTopology();
+        IOFSwitch sw1 = createMockSwitch(1L);
+        replay(sw1);
+        Link lt = new Link(1L, 2, 1L, 3);
+        LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+                                     System.currentTimeMillis(), null,
+                                     0, 0);
+        topology.addOrUpdateLink(lt, info);
+
+        // Mock up our expected behavior
+        topology.removedSwitch(sw1);
+
+        verify(sw1);
+        // check invariants hold
+        assertNull(topology.switchLinks.get(lt.getSrc()));
+        assertNull(topology.portLinks.get(lt.getSrc()));
+        assertNull(topology.portLinks.get(lt.getDst()));
+        assertTrue(topology.links.isEmpty());
+    }
+
+    @Test
+    public void testAddUpdateLinks() throws Exception {
+        LinkDiscoveryManager topology = getTopology();
+
+        Link lt = new Link(1L, 1, 2L, 1);
+        NodePortTuple srcNpt = new NodePortTuple(1L, 1);
+        NodePortTuple dstNpt = new NodePortTuple(2L, 1);
+        
+        LinkInfo info;
+
+        info = new LinkInfo(System.currentTimeMillis() - 40000,
+                            System.currentTimeMillis() - 40000, null,
+                                     0, 0);
+        topology.addOrUpdateLink(lt, info);
+
+        // check invariants hold
+        assertNotNull(topology.switchLinks.get(lt.getSrc()));
+        assertTrue(topology.switchLinks.get(lt.getSrc()).contains(lt));
+        assertNotNull(topology.portLinks.get(srcNpt));
+        assertTrue(topology.portLinks.get(srcNpt).contains(lt));
+        assertNotNull(topology.portLinks.get(dstNpt));
+        assertTrue(topology.portLinks.get(dstNpt).contains(lt));
+        assertTrue(topology.links.containsKey(lt));
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt) == null ||
+                topology.portBroadcastDomainLinks.get(srcNpt).contains(lt) == false);
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt) == null ||
+                topology.portBroadcastDomainLinks.get(dstNpt).contains(lt) == false);
+
+        topology.timeoutLinks();
+
+
+        info = new LinkInfo(System.currentTimeMillis(),/* firstseen */
+                            null,/* unicast */
+                            System.currentTimeMillis(), 0, 0);
+        topology.addOrUpdateLink(lt, info);
+        assertTrue(topology.links.get(lt).getUnicastValidTime() == null);
+        assertTrue(topology.links.get(lt).getMulticastValidTime() != null);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+
+        // Add a link info based on info that woudld be obtained from unicast LLDP
+        // Setting the unicast LLDP reception time to be 40 seconds old, so we can use
+        // this to test timeout after this test.  Although the info is initialized
+        // with LT_OPENFLOW_LINK, the link property should be changed to LT_NON_OPENFLOW
+        // by the addOrUpdateLink method.
+        info = new LinkInfo(System.currentTimeMillis() - 40000,
+                            System.currentTimeMillis() - 40000, null, 0, 0);
+        topology.addOrUpdateLink(lt, info);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt) == null ||
+                topology.portBroadcastDomainLinks.get(srcNpt).contains(lt) == false);
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt) == null ||
+                topology.portBroadcastDomainLinks.get(dstNpt).contains(lt) == false);
+
+        // Expect to timeout the unicast Valid Time, but not the multicast Valid time
+        // So the link type should go back to non-openflow link.
+        topology.timeoutLinks();
+        assertTrue(topology.links.get(lt).getUnicastValidTime() == null);
+        assertTrue(topology.links.get(lt).getMulticastValidTime() != null);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+        // Set the multicastValidTime to be old and see if that also times out.
+        info = new LinkInfo(System.currentTimeMillis() - 40000,
+                            null, System.currentTimeMillis() - 40000, 0, 0);
+        topology.addOrUpdateLink(lt, info);
+        topology.timeoutLinks();
+        assertTrue(topology.links.get(lt) == null);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt) == null ||
+                topology.portBroadcastDomainLinks.get(srcNpt).contains(lt) == false);
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt) == null ||
+                topology.portBroadcastDomainLinks.get(dstNpt).contains(lt) == false);
+
+
+        // Test again only with multicast LLDP
+        info = new LinkInfo(System.currentTimeMillis() - 40000,
+                            null, System.currentTimeMillis() - 40000, 0, 0);
+        topology.addOrUpdateLink(lt, info);
+        assertTrue(topology.links.get(lt).getUnicastValidTime() == null);
+        assertTrue(topology.links.get(lt).getMulticastValidTime() != null);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+        // Call timeout and check if link is no longer present.
+        topology.timeoutLinks();
+        assertTrue(topology.links.get(lt) == null);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt) == null ||
+                topology.portBroadcastDomainLinks.get(srcNpt).contains(lt) == false);
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt) == null ||
+                topology.portBroadcastDomainLinks.get(dstNpt).contains(lt) == false);
+
+        // Start clean and see if loops are also added.
+        lt = new Link(1L, 1, 1L, 2);
+        srcNpt = new NodePortTuple(1L, 1);
+        dstNpt = new NodePortTuple(1L, 2);
+        info = new LinkInfo(System.currentTimeMillis() - 40000,
+                            null, System.currentTimeMillis() - 40000, 0, 0);
+        topology.addOrUpdateLink(lt, info);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+
+        // Start clean and see if loops are also added.
+        lt = new Link(1L, 1, 1L, 3);
+        srcNpt = new NodePortTuple(1L, 1);
+        dstNpt = new NodePortTuple(1L, 3);
+        info = new LinkInfo(System.currentTimeMillis() - 40000,
+                            null, System.currentTimeMillis() - 40000, 0, 0);
+        topology.addOrUpdateLink(lt, info);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+
+        // Start clean and see if loops are also added.
+        lt = new Link(1L, 4, 1L, 5);
+        srcNpt = new NodePortTuple(1L, 4);
+        dstNpt = new NodePortTuple(1L, 5);
+        info = new LinkInfo(System.currentTimeMillis() - 40000,
+                            null, System.currentTimeMillis() - 40000, 0, 0);
+        topology.addOrUpdateLink(lt, info);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+
+        // Start clean and see if loops are also added.
+        lt = new Link(1L, 3, 1L, 5);
+        srcNpt = new NodePortTuple(1L, 3);
+        dstNpt = new NodePortTuple(1L, 5);
+        info = new LinkInfo(System.currentTimeMillis() - 40000,
+                            null, System.currentTimeMillis() - 40000, 0, 0);
+        topology.addOrUpdateLink(lt, info);
+        assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+        assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+    }
+
+    @Test
+    public void testHARoleChange() throws Exception {
+        LinkDiscoveryManager topology = getTopology();
+        IOFSwitch sw1 = createMockSwitch(1L);
+        IOFSwitch sw2 = createMockSwitch(2L);
+        replay(sw1, sw2);
+        Link lt = new Link(1L, 2, 2L, 1);
+        NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+        NodePortTuple dstNpt = new NodePortTuple(2L, 1);
+        LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+                                     System.currentTimeMillis(), null,
+                                     0, 0);
+        topology.addOrUpdateLink(lt, info);
+
+        // check invariants hold
+        assertNotNull(topology.switchLinks.get(lt.getSrc()));
+        assertTrue(topology.switchLinks.get(lt.getSrc()).contains(lt));
+        assertNotNull(topology.portLinks.get(srcNpt));
+        assertTrue(topology.portLinks.get(srcNpt).contains(lt));
+        assertNotNull(topology.portLinks.get(dstNpt));
+        assertTrue(topology.portLinks.get(dstNpt).contains(lt));
+        assertTrue(topology.links.containsKey(lt));
+        
+        // check that it clears from memory
+        getMockFloodlightProvider().dispatchRoleChanged(null, Role.SLAVE);
+        assertTrue(topology.switchLinks.isEmpty());
+        getMockFloodlightProvider().dispatchRoleChanged(Role.SLAVE, Role.MASTER);
+        // check that lldps were sent
+        assertTrue(ldm.isSendLLDPsCalled);
+        assertTrue(ldm.isClearLinksCalled);
+        ldm.reset();
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/BSNTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/BSNTest.java
new file mode 100644
index 0000000..d672fdf
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/BSNTest.java
@@ -0,0 +1,80 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ *
+ */
+package net.floodlightcontroller.packet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class BSNTest {       
+    protected byte[] probePkt = {
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // src mac
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x04, // dst mac
+		(byte) 0x89, 0x42, // BSN type
+        0x20, 0x00, 0x06, 0x04, 0x00, 0x01, 0x00, 0x00, // BSN header
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // controller id
+		0x00, 0x00, 0x00, 0x03, // sequence id
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // src mac
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x04, // dst mac
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, // switch dpid
+		0x00, 0x00, 0x00, 0x01 // port number
+    };
+    
+    protected Ethernet getProbePacket() {
+        return (Ethernet) new Ethernet()
+            .setSourceMACAddress("00:00:00:00:00:04")
+            .setDestinationMACAddress("00:00:00:00:00:01")
+            .setEtherType(Ethernet.TYPE_BSN)
+            .setPayload(new BSN(BSN.BSN_TYPE_PROBE)
+	            .setPayload(new BSNPROBE()
+		            .setSequenceId(3)
+		            .setSrcMac(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x01})
+		            .setDstMac(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x04})
+		            .setSrcSwDpid(0x06)
+		            .setSrcPortNo(0x01)
+		            )
+	            );
+    }
+
+    @Test
+    public void testSerialize() throws Exception {
+        Ethernet pkt = getProbePacket();
+        byte[] serialized = pkt.serialize();
+        assertTrue(Arrays.equals(probePkt, serialized));
+    }
+
+    @Test
+    public void testDeserialize() throws Exception {
+        Ethernet pkt = (Ethernet) new Ethernet().deserialize(probePkt, 0, probePkt.length);
+        assertTrue(Arrays.equals(probePkt, pkt.serialize()));
+
+        Ethernet expected = getProbePacket();
+        assertEquals(expected, pkt);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/DHCPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/DHCPTest.java
new file mode 100644
index 0000000..b83ffa8
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/DHCPTest.java
@@ -0,0 +1,595 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+
+
+import java.util.Arrays;
+import java.util.ListIterator;
+
+import junit.framework.TestCase;
+
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class DHCPTest extends TestCase {
+    public byte[] dhcpPacket = new byte[] {
+            (byte) 0x01, (byte) 0x01,
+            (byte) 0x06, (byte) 0x00, (byte) 0x66, (byte) 0xf2, (byte) 0x8a,
+            (byte) 0x11, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x9f, (byte) 0x9f,
+            (byte) 0xfe, (byte) 0xd8, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x63,
+            (byte) 0x82, (byte) 0x53, (byte) 0x63, (byte) 0x35, (byte) 0x01,
+            (byte) 0x01, (byte) 0x37, (byte) 0x0a, (byte) 0x01, (byte) 0x1c,
+            (byte) 0x02, (byte) 0x03, (byte) 0x0f, (byte) 0x06, (byte) 0x0c,
+            (byte) 0x28, (byte) 0x29, (byte) 0x2a, (byte) 0xff, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00
+    };
+
+    public byte[] dhcpPacket2 = new byte[] { (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+            (byte) 0xc0, (byte) 0x9f, (byte) 0x9e, (byte) 0x11, (byte) 0x84,
+            (byte) 0x08, (byte) 0x00, (byte) 0x45, (byte) 0x00, (byte) 0x02,
+            (byte) 0x40, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00,
+            (byte) 0x14, (byte) 0x11, (byte) 0xa4, (byte) 0xaa, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x44, (byte) 0x00,
+            (byte) 0x43, (byte) 0x02, (byte) 0x2c, (byte) 0xdd, (byte) 0x9d,
+            (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00, (byte) 0xa4,
+            (byte) 0x9e, (byte) 0x11, (byte) 0x84, (byte) 0x00, (byte) 0x40,
+            (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xc0,
+            (byte) 0x9f, (byte) 0x9e, (byte) 0x11, (byte) 0x84, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
+            (byte) 0x35, (byte) 0x01, (byte) 0x01, (byte) 0x37, (byte) 0x18,
+            (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x05, (byte) 0x06,
+            (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0f, (byte) 0x10,
+            (byte) 0x11, (byte) 0x12, (byte) 0x2b, (byte) 0x36, (byte) 0x3c,
+            (byte) 0x43, (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83,
+            (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x39,
+            (byte) 0x02, (byte) 0x04, (byte) 0xec, (byte) 0x61, (byte) 0x11,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x5d, (byte) 0x02, (byte) 0x00,
+            (byte) 0x00, (byte) 0x5e, (byte) 0x03, (byte) 0x01, (byte) 0x02,
+            (byte) 0x01, (byte) 0x3c, (byte) 0x20, (byte) 0x50, (byte) 0x58,
+            (byte) 0x45, (byte) 0x43, (byte) 0x6c, (byte) 0x69, (byte) 0x65,
+            (byte) 0x6e, (byte) 0x74, (byte) 0x3a, (byte) 0x41, (byte) 0x72,
+            (byte) 0x63, (byte) 0x68, (byte) 0x3a, (byte) 0x30, (byte) 0x30,
+            (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x3a, (byte) 0x55,
+            (byte) 0x4e, (byte) 0x44, (byte) 0x49, (byte) 0x3a, (byte) 0x30,
+            (byte) 0x30, (byte) 0x32, (byte) 0x30, (byte) 0x30, (byte) 0x31,
+            (byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00 };
+    
+    public byte[] dhcpPacket3 = new byte[] { 
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0x74, (byte) 0x44, (byte) 0x01, (byte) 0x72, 
+            (byte) 0xd8, (byte) 0x41, (byte) 0x08, (byte) 0x00, (byte) 0x45, 
+            (byte) 0x00, (byte) 0x01, (byte) 0x1f, (byte) 0x48, (byte) 0xcd, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x11, (byte) 0x6f, 
+            (byte) 0x6a, (byte) 0xc0, (byte) 0xa8, (byte) 0x00, (byte) 0xef, 
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, 
+            (byte) 0x44, (byte) 0x00, (byte) 0x43, (byte) 0x01, (byte) 0x0b, 
+            (byte) 0xb3, (byte) 0x0f, (byte) 0x01, (byte) 0x01, (byte) 0x06, 
+            (byte) 0x00, (byte) 0x82, (byte) 0x88, (byte) 0xa6, (byte) 0xc9, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x74, (byte) 0x44, (byte) 0x01, (byte) 0x72, (byte) 0xd8, 
+            (byte) 0x41, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x63, (byte) 0x82, 
+            (byte) 0x53, (byte) 0x63, (byte) 0x35, (byte) 0x01, (byte) 0x01, 
+            (byte) 0x32, (byte) 0x04, (byte) 0xc0, (byte) 0xa8, (byte) 0x0a, 
+            (byte) 0xa9, (byte) 0x39, (byte) 0x02, (byte) 0x02, (byte) 0x40, 
+            (byte) 0x37, (byte) 0x03, (byte) 0x01, (byte) 0x03, (byte) 0x06, 
+            (byte) 0xff                               
+    };
+    
+    public byte[] dhcpPacketPXE = new byte[] { (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+            (byte) 0x19, (byte) 0xb9, (byte) 0xb0, (byte) 0x01, (byte) 0x44,
+            (byte) 0x08, (byte) 0x00, (byte) 0x45, (byte) 0x10, (byte) 0x01,
+            (byte) 0x48, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x80, (byte) 0x11, (byte) 0x2c, (byte) 0x98, (byte) 0x0a,
+            (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x43, (byte) 0x00,
+            (byte) 0x44, (byte) 0x01, (byte) 0x34, (byte) 0xa6, (byte) 0xf0,
+            (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00, (byte) 0xa0,
+            (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00, (byte) 0x04,
+            (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0x14,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xc0,
+            (byte) 0x9f, (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x65,
+            (byte) 0x6e, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
+            (byte) 0x65, (byte) 0x72, (byte) 0x5f, (byte) 0x35, (byte) 0x2e,
+            (byte) 0x36, (byte) 0x2f, (byte) 0x70, (byte) 0x78, (byte) 0x65,
+            (byte) 0x6c, (byte) 0x69, (byte) 0x6e, (byte) 0x75, (byte) 0x78,
+            (byte) 0x2e, (byte) 0x30, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
+            (byte) 0x35, (byte) 0x01, (byte) 0x02, (byte) 0x36, (byte) 0x04,
+            (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0x33,
+            (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0xa8, (byte) 0xc0,
+            (byte) 0x01, (byte) 0x04, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            (byte) 0x00, (byte) 0x03, (byte) 0x04, (byte) 0x0a, (byte) 0x00,
+            (byte) 0x02, (byte) 0xfe, (byte) 0x0c, (byte) 0x0d, (byte) 0x64,
+            (byte) 0x6e, (byte) 0x72, (byte) 0x63, (byte) 0x2d, (byte) 0x68,
+            (byte) 0x6f, (byte) 0x73, (byte) 0x74, (byte) 0x30, (byte) 0x30,
+            (byte) 0x32, (byte) 0x30, (byte) 0xff, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+    };
+
+    public byte[] dhcpPacketBadOption1 = new byte[] { (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+            (byte) 0x19, (byte) 0xb9, (byte) 0xb0, (byte) 0x01, (byte) 0x44,
+            (byte) 0x08, (byte) 0x00, (byte) 0x45, (byte) 0x10, (byte) 0x01,
+            (byte) 0x48, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x80, (byte) 0x11, (byte) 0x2c, (byte) 0x98, (byte) 0x0a,
+            (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x44, (byte) 0x00,
+            (byte) 0x43, (byte) 0x01, (byte) 0x34, (byte) 0xa6, (byte) 0xf0,
+            (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00, (byte) 0xa0,
+            (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00, (byte) 0x04,
+            (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0x14,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xc0,
+            (byte) 0x9f, (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x65,
+            (byte) 0x6e, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
+            (byte) 0x65, (byte) 0x72, (byte) 0x5f, (byte) 0x35, (byte) 0x2e,
+            (byte) 0x36, (byte) 0x2f, (byte) 0x70, (byte) 0x78, (byte) 0x65,
+            (byte) 0x6c, (byte) 0x69, (byte) 0x6e, (byte) 0x75, (byte) 0x78,
+            (byte) 0x2e, (byte) 0x30, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
+            (byte) 0x35, (byte) 0x01, (byte) 0x01, (byte) 0x36, (byte) 0x04,
+            (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0x33,
+            (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0xa8, (byte) 0xc0,
+            (byte) 0x01, (byte) 0x04, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            (byte) 0x00, (byte) 0x03, (byte) 0x04, (byte) 0x0a, (byte) 0x00,
+            (byte) 0x02, (byte) 0xfe, (byte) 0x0c, (byte) 0x30, (byte) 0x64,
+            (byte) 0x6e, (byte) 0x72, (byte) 0x63, (byte) 0x2d, (byte) 0x68,
+            (byte) 0x6f, (byte) 0x73, (byte) 0x74, (byte) 0x30, (byte) 0x30,
+            (byte) 0x32, (byte) 0x30, (byte) 0xff, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+    };
+    
+    public byte[] dhcpPacketBadHeader = new byte[] { (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+            (byte) 0x19, (byte) 0xb9, (byte) 0xb0, (byte) 0x01, (byte) 0x44,
+            (byte) 0x08, (byte) 0x00, (byte) 0x45, (byte) 0x10, (byte) 0x01,
+            (byte) 0x48, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x80, (byte) 0x11, (byte) 0x2c, (byte) 0x98, (byte) 0x0a,
+            (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x44, (byte) 0x00,
+            (byte) 0x43, (byte) 0x01, (byte) 0x34, (byte) 0xa6, (byte) 0xf0,
+            (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00, (byte) 0xa0,
+            (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00, (byte) 0x04,
+            (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0x14,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xc0,
+            (byte) 0x9f, (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x65,
+            (byte) 0x6e, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
+            (byte) 0x65, (byte) 0x72, (byte) 0x5f, (byte) 0x35, (byte) 0x2e,
+            (byte) 0x36, (byte) 0x2f, (byte) 0x70, (byte) 0x78, (byte) 0x65,
+            (byte) 0x6c, (byte) 0x69, (byte) 0x6e, (byte) 0x75, (byte) 0x78,
+            (byte) 0x2e, (byte) 0x30, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+    };
+    
+    private void resetChecksumsAndLengths(IPv4 ipv4, UDP udp) {
+        ipv4.setChecksum((short)0);
+        udp.setChecksum((short)0);
+    }
+    
+    public void testSerialize() {
+        DHCP dhcp = new DHCP();
+        dhcp.deserialize(dhcpPacket, 0, dhcpPacket.length);
+        byte[] result = dhcp.serialize();
+        assertTrue(Arrays.equals(this.dhcpPacket, result));
+    }
+
+    public void testDeSerialize() {
+        Ethernet eth = new Ethernet();
+        eth.deserialize(dhcpPacket2, 0, dhcpPacket2.length);
+        assertTrue(eth.getPayload() instanceof IPv4);
+        IPv4 ipv4 = (IPv4) eth.getPayload();
+        assertTrue(ipv4.getPayload() instanceof UDP);
+        UDP udp = (UDP) ipv4.getPayload();
+        assertTrue(udp.getPayload() instanceof DHCP);
+        DHCP dhcp = (DHCP) udp.getPayload();
+        /** The invalid option in DHCP packet is dropped. Reset checksums and
+         *  length field so that the serialize() function can re-compute them
+         */
+        resetChecksumsAndLengths(ipv4, udp);
+        assertEquals(DHCP.OPCODE_REQUEST, dhcp.getOpCode());
+    }
+    
+    public void testDeSerializeReSerialize() {
+        Ethernet eth = new Ethernet();
+        eth.deserialize(dhcpPacket3, 0, dhcpPacket3.length);
+        assertTrue(eth.getPayload() instanceof IPv4);
+        IPv4 ipv4 = (IPv4) eth.getPayload();
+        assertTrue(ipv4.getPayload() instanceof UDP);
+        
+        byte[] serializedPacket = eth.serialize();
+        Ethernet eth2 = new Ethernet();
+        eth2.deserialize(serializedPacket, 0, serializedPacket.length);
+        IPv4 ipv42 = (IPv4) eth2.getPayload();
+
+        short ipchecksum = ipv42.getChecksum();
+        ipv42.setChecksum((short) 0);
+        eth2.serialize();        
+        assertEquals(ipchecksum, ipv42.getChecksum());
+    }
+
+    public void testDeSerializePXE() {
+        Ethernet eth = new Ethernet();
+        eth.deserialize(dhcpPacketPXE, 0, dhcpPacketPXE.length);
+        assertTrue(eth.getPayload() instanceof IPv4);
+        IPv4 ipv4 = (IPv4) eth.getPayload();
+        assertTrue(ipv4.getPayload() instanceof UDP);
+        UDP udp = (UDP) ipv4.getPayload();
+        assertTrue(udp.getPayload() instanceof DHCP);
+        DHCP dhcp = (DHCP) udp.getPayload();
+        /** The invalid option in DHCP packet is dropped. Reset checksums and
+         *  length field so that the serialize() function can re-compute them
+         */
+        resetChecksumsAndLengths(ipv4, udp);
+        
+        assertEquals(DHCP.OPCODE_REPLY, dhcp.getOpCode());
+        assertEquals("xenserver_5.6/pxelinux.0", dhcp.getBootFileName());
+
+        byte[] result = eth.serialize();
+        assertTrue(Arrays.equals(this.dhcpPacketPXE, result));
+    }
+    
+    public void testDeSerializeBad1() {
+        Ethernet eth = new Ethernet();
+        eth.deserialize(dhcpPacketBadOption1, 0, dhcpPacketBadOption1.length);
+        assertTrue(eth.getPayload() instanceof IPv4);
+        IPv4 ipv4 = (IPv4) eth.getPayload();
+        assertTrue(ipv4.getPayload() instanceof UDP);
+        UDP udp = (UDP) ipv4.getPayload();
+        assertTrue(udp.getPayload() instanceof DHCP);
+        DHCP dhcp = (DHCP) udp.getPayload();
+        /** The invalid option in DHCP packet is dropped. Reset checksums and
+         *  length field so that the serialize() function can re-compute them
+         */
+        resetChecksumsAndLengths(ipv4, udp);
+        
+        assertEquals(DHCP.OPCODE_REPLY, dhcp.getOpCode());
+        ListIterator<DHCPOption> lit = dhcp.getOptions().listIterator();
+        // Expect 5 correct options and an END option.
+        assertEquals(dhcp.getOptions().size(), 6);
+        while (lit.hasNext()) {
+            DHCPOption option = lit.next();
+            assertFalse(option.code == (byte)0x0c);
+        }
+
+        byte[] result = eth.serialize();
+        // Since one option is badly formated, the result is different.
+        assertFalse(Arrays.equals(this.dhcpPacketPXE, result));
+    }
+    
+    public void testDeSerializeBadHeader() {
+        Ethernet eth = new Ethernet();
+        eth.deserialize(dhcpPacketBadHeader, 0, dhcpPacketBadHeader.length);
+        assertTrue(eth.getPayload() instanceof IPv4);
+        IPv4 ipv4 = (IPv4) eth.getPayload();
+        assertTrue(ipv4.getPayload() instanceof UDP);
+        UDP udp = (UDP) ipv4.getPayload();
+        assertTrue(udp.getPayload() instanceof DHCP);
+        DHCP dhcp = (DHCP) udp.getPayload();
+
+        assertEquals(UDP.DHCP_CLIENT_PORT, udp.getSourcePort());
+        assertEquals(UDP.DHCP_SERVER_PORT, udp.getDestinationPort());
+        
+        // should get invalid opCode of 0
+        assertEquals(0, dhcp.getOpCode());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/EthernetTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/EthernetTest.java
new file mode 100644
index 0000000..f742f21
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/EthernetTest.java
@@ -0,0 +1,75 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class EthernetTest {
+    @Test
+    public void testToMACAddress() {
+        byte[] address = new byte[] { 0x0, 0x11, 0x22, (byte) 0xff,
+                (byte) 0xee, (byte) 0xdd};
+        assertTrue(Arrays.equals(address, Ethernet
+                .toMACAddress("00:11:22:ff:ee:dd")));
+        assertTrue(Arrays.equals(address, Ethernet
+                .toMACAddress("00:11:22:FF:EE:DD")));
+    }
+
+    @Test
+    public void testSerialize() {
+        Ethernet ethernet = new Ethernet()
+            .setDestinationMACAddress("de:ad:be:ef:de:ad")
+            .setSourceMACAddress("be:ef:de:ad:be:ef")
+            .setEtherType((short) 0);
+        byte[] expected = new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xbe,
+                (byte) 0xef, (byte) 0xde, (byte) 0xad, (byte) 0xbe,
+                (byte) 0xef, (byte) 0xde, (byte) 0xad, (byte) 0xbe,
+                (byte) 0xef, 0x0, 0x0 };
+        assertTrue(Arrays.equals(expected, ethernet.serialize()));
+    }
+
+    @Test
+    public void testToLong() {
+        assertEquals(
+                281474976710655L,
+                Ethernet.toLong(new byte[] { (byte) 0xff, (byte) 0xff,
+                        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }));
+
+        assertEquals(
+                1103823438081L,
+                Ethernet.toLong(new byte[] { (byte) 0x01, (byte) 0x01,
+                        (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01 }));
+
+        assertEquals(
+                141289400074368L,
+                Ethernet.toLong(new byte[] { (byte) 0x80, (byte) 0x80,
+                        (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80 }));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/ICMPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/ICMPTest.java
new file mode 100644
index 0000000..e554c4e
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/ICMPTest.java
@@ -0,0 +1,67 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * @author shudong.zhou@bigswitch.com
+ */
+public class ICMPTest {
+    private byte[] pktSerialized = new byte[] {
+            // (byte) 0xc8, 0x2a, 0x14, 0x2d, 0x35, (byte) 0xf1,
+            // 0x00, 0x0c, 0x29, 0x3b, (byte) 0x95, (byte) 0xf2, 0x08, 0x0,
+            0x45, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x40, 0x00, 0x40, 0x01,
+            (byte) 0xa3, (byte) 0xcb,
+            (byte) 0xc0, (byte) 0xa8, (byte) 0x0a, (byte) 0xe7,
+            (byte) 0xc0, (byte) 0xa8, (byte) 0x0a, (byte) 0xdb,
+            0x08, 0x00, 0x7f, 0x0a, 0x76, (byte) 0xf2, 0x00, 0x02,
+            0x01, 0x01, 0x01 };
+    @Test
+    public void testSerialize() {
+        IPacket packet = new IPv4()
+            .setIdentification((short) 0)
+            .setFlags((byte) 0x02)
+            .setTtl((byte) 64)
+            .setSourceAddress("192.168.10.231")
+            .setDestinationAddress("192.168.10.219")
+            .setPayload(new ICMP()
+                            .setIcmpType((byte) 8)
+                            .setIcmpCode((byte) 0)
+                            .setPayload(new Data(new byte[]
+                                        {0x76, (byte) 0xf2, 0x0, 0x2, 0x1, 0x1, 0x1}))
+                       );
+        byte[] actual = packet.serialize();
+        assertTrue(Arrays.equals(pktSerialized, actual));
+    }
+    
+    @Test
+    public void testDeserialize() {
+        IPacket packet = new IPv4();
+        packet.deserialize(pktSerialized, 0, pktSerialized.length);
+        byte[] pktSerialized1 = packet.serialize();
+        assertTrue(Arrays.equals(pktSerialized, pktSerialized1));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/IPv4Test.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/IPv4Test.java
new file mode 100644
index 0000000..928a2de
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/IPv4Test.java
@@ -0,0 +1,88 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class IPv4Test {
+    @Test
+    public void testToIPv4Address() {
+        int intIp = 0xc0a80001;
+        String stringIp = "192.168.0.1";
+        byte[] byteIp = new byte[] {(byte)192, (byte)168, (byte)0, (byte)1};
+        assertEquals(intIp, IPv4.toIPv4Address(stringIp));
+        assertEquals(intIp, IPv4.toIPv4Address(byteIp));
+        assertTrue(Arrays.equals(byteIp, IPv4.toIPv4AddressBytes(intIp)));
+        assertTrue(Arrays.equals(byteIp, IPv4.toIPv4AddressBytes(stringIp)));
+    }
+
+    @Test
+    public void testToIPv4AddressBytes() {
+        byte[] expected = new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+        Assert.assertArrayEquals(expected, IPv4.toIPv4AddressBytes("255.255.255.255"));
+        expected = new byte[] {(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80};
+        Assert.assertArrayEquals(expected, IPv4.toIPv4AddressBytes("128.128.128.128"));
+        expected = new byte[] {0x7f,0x7f,0x7f,0x7f};
+        Assert.assertArrayEquals(expected, IPv4.toIPv4AddressBytes("127.127.127.127"));
+    }
+
+    @Test
+    public void testSerialize() {
+        byte[] expected = new byte[] { 0x45, 0x00, 0x00, 0x14, 0x5e, 0x4e,
+                0x00, 0x00, 0x3f, 0x06, 0x31, 0x2e, (byte) 0xac, 0x18,
+                0x4a, (byte) 0xdf, (byte) 0xab, 0x40, 0x4a, 0x30 };
+        IPv4 packet = new IPv4()
+            .setIdentification((short) 24142)
+            .setTtl((byte) 63)
+            .setProtocol((byte) 0x06)
+            .setSourceAddress("172.24.74.223")
+            .setDestinationAddress("171.64.74.48");
+        byte[] actual = packet.serialize();
+        assertTrue(Arrays.equals(expected, actual));
+    }
+    
+    @Test
+    public void testDeserialize() {
+        // A real TLSv1 packet
+        byte[] pktSerialized = new byte[] { 0x45, 0x00,
+                0x00, 0x2e, 0x41, (byte) 0xbe, 0x40, 0x00, 0x40, 0x06,
+                (byte) 0xd4, (byte) 0xf0, (byte) 0xc0, (byte) 0xa8, 0x02, (byte) 0xdb, (byte) 0xd0, 0x55,
+                (byte) 0x90, 0x42, (byte) 0xd5, 0x48, 0x01, (byte) 0xbb, (byte) 0xe3, 0x50,
+                (byte) 0xb2, 0x2f, (byte) 0xfc, (byte) 0xf8, (byte) 0xa8, 0x2c, 0x50, 0x18,
+                (byte) 0xff, (byte) 0xff, 0x24, 0x3c, 0x00, 0x00, 0x14, 0x03,
+                0x01, 0x00, 0x01, 0x01
+        };
+        IPv4 packet = new IPv4();
+        packet.deserialize(pktSerialized, 0, pktSerialized.length);
+        byte[] pktSerialized1 = packet.serialize();
+        assertTrue(Arrays.equals(pktSerialized, pktSerialized1));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPOrganizationalTLVTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPOrganizationalTLVTest.java
new file mode 100644
index 0000000..88fb26f
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPOrganizationalTLVTest.java
@@ -0,0 +1,95 @@
+/**
+ *    Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *    not use this file except in compliance with the License. You may obtain
+ *    a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ *    License for the specific language governing permissions and limitations
+ *    under the License.
+ **/
+
+package net.floodlightcontroller.packet;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class LLDPOrganizationalTLVTest {
+    private final byte[] expected = new byte[] {
+        //  Type: 127, Length: 13
+        (byte) 254, 13,
+        // OpenFlow OUI: 00-26-E1
+        0x0, 0x26, (byte)0xe1,
+        //  SubType: 12
+        0xc,
+        //  Bytes in "ExtraInfo"
+        0x45, 0x78, 0x74, 0x72, 0x61, 0x49, 0x6e, 0x66, 0x6f
+    };
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testShortOUI() {
+        LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+        tlv.setOUI(new byte[2]);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLongOUI() {
+        LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+        tlv.setOUI(new byte[4]);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLongInfoString() {
+        LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+        tlv.setInfoString(new byte[LLDPOrganizationalTLV.MAX_INFOSTRING_LENGTH + 1]);
+    }
+
+    @Test
+    public void testMaxInfoString() {
+        LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+        tlv.setInfoString(new byte[LLDPOrganizationalTLV.MAX_INFOSTRING_LENGTH]);
+    }
+
+    @Test
+    public void testInfoString() {
+        LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+        tlv.setInfoString("ExtraInfo");
+        assertThat(tlv.getInfoString(), is("ExtraInfo".getBytes(Charset.forName("UTF-8"))));
+    }
+
+    @Test
+    public void testSerialize() {
+        LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+        tlv.setLength((short) 13);
+        // OpenFlow OUI is 00-26-E1
+        tlv.setOUI(new byte[] {0x0, 0x26, (byte) 0xe1});
+        tlv.setSubType((byte) 12);
+        tlv.setInfoString("ExtraInfo".getBytes(Charset.forName("UTF-8")));
+
+        assertThat(tlv.getType(), is((byte)127));
+        assertThat(tlv.getLength(), is((short)13));
+        assertThat(tlv.getOUI(), is(new byte[] {0x0, 0x26, (byte) 0xe1}));
+        assertThat(tlv.getSubType(), is((byte)12));
+        assertThat(tlv.serialize(), is(expected));
+    }
+
+    @Test
+    public void testDeserialize() {
+        LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+        tlv.deserialize(ByteBuffer.wrap(expected));
+
+        assertThat(tlv.getType(), is((byte)127));
+        assertThat(tlv.getLength(), is((short)13));
+        assertThat(tlv.getOUI(), is(new byte[] {0x0, 0x26, (byte) 0xe1}));
+        assertThat(tlv.getSubType(), is((byte)12));
+        assertThat(tlv.getInfoString(), is("ExtraInfo".getBytes(Charset.forName("UTF-8"))));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPTest.java
new file mode 100755
index 0000000..930c66a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPTest.java
@@ -0,0 +1,71 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ *
+ */
+package net.floodlightcontroller.packet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class LLDPTest {
+    protected byte[] pkt = {0x01,0x23,0x20,0x00,0x00,0x01,0x00,0x12,(byte) 0xe2,0x78,0x67,0x78,(byte) 0x88,(byte) 0xcc,0x02,0x07,
+            0x04,0x00,0x12,(byte) 0xe2,0x78,0x67,0x64,0x04,0x03,0x02,0x00,0x06,0x06,0x02,0x00,0x78,
+            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+
+    protected IPacket getPacket() {
+        return new Ethernet()
+        .setPad(true)
+        .setDestinationMACAddress("01:23:20:00:00:01")
+        .setSourceMACAddress("00:12:e2:78:67:78")
+        .setEtherType(Ethernet.TYPE_LLDP)
+        .setPayload(
+                new LLDP()
+                .setChassisId(new LLDPTLV().setType((byte) 1).setLength((short) 7).setValue(new byte[] {0x04, 0x00, 0x12, (byte) 0xe2, 0x78, 0x67, 0x64}))
+                .setPortId(new LLDPTLV().setType((byte) 2).setLength((short) 3).setValue(new byte[] {0x02, 0x00, 0x06}))
+                .setTtl(new LLDPTLV().setType((byte) 3).setLength((short) 2).setValue(new byte[] {0x00, 0x78}))
+            
+        );
+    }
+
+    @Test
+    public void testSerialize() throws Exception {
+        IPacket ethernet = getPacket();
+        assertTrue(Arrays.equals(pkt, ethernet.serialize()));
+    }
+
+    @Test
+    public void testDeserialize() throws Exception {
+        Ethernet ethernet = (Ethernet) new Ethernet().deserialize(pkt, 0, pkt.length);
+        ethernet.setPad(true);
+        assertTrue(Arrays.equals(pkt, ethernet.serialize()));
+
+        IPacket expected = getPacket();
+        assertEquals(expected, ethernet);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/PacketTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/PacketTest.java
new file mode 100644
index 0000000..67bed71
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/PacketTest.java
@@ -0,0 +1,126 @@
+package net.floodlightcontroller.packet;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PacketTest {
+    protected IPacket pkt1, pkt2, pkt3, pkt4;
+    protected IPacket dummyPkt;
+    protected IPacket[] packets;
+    
+    @Before
+    public void setUp() {
+        this.pkt1 = new Ethernet()
+        .setDestinationMACAddress("00:11:22:33:44:55")
+        .setSourceMACAddress("00:44:33:22:11:00")
+        .setEtherType(Ethernet.TYPE_IPv4)
+        .setPayload(
+                    new IPv4()
+                    .setTtl((byte) 128)
+                    .setSourceAddress("192.168.1.1")
+                    .setDestinationAddress("192.168.1.2")
+                    .setPayload(new UDP()
+                    .setSourcePort((short) 5000)
+                    .setDestinationPort((short) 5001)
+                    .setPayload(new Data(new byte[] {0x01}))));
+        
+        this.pkt2 = new Ethernet()
+        .setSourceMACAddress("00:44:33:22:11:01")
+        .setDestinationMACAddress("00:11:22:33:44:55")
+        .setEtherType(Ethernet.TYPE_ARP)
+        .setVlanID((short)5)
+        .setPayload(
+                    new ARP()
+                    .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                    .setProtocolType(ARP.PROTO_TYPE_IP)
+                    .setHardwareAddressLength((byte) 6)
+                    .setProtocolAddressLength((byte) 4)
+                    .setOpCode(ARP.OP_REPLY)
+                    .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:01"))
+                    .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+                    .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+                    .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+        
+        
+        this.pkt3 = new Ethernet()
+        .setSourceMACAddress("00:44:33:22:11:01")
+        .setDestinationMACAddress("00:11:22:33:44:55")
+        .setEtherType(Ethernet.TYPE_ARP)
+        .setPayload(
+                    new ARP()
+                    .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                    .setProtocolType(ARP.PROTO_TYPE_IP)
+                    .setHardwareAddressLength((byte) 6)
+                    .setProtocolAddressLength((byte) 4)
+                    .setOpCode(ARP.OP_REPLY)
+                    .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:01"))
+                    .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+                    .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+                    .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+        
+        this.pkt4 = new Ethernet()
+        .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+        .setSourceMACAddress("00:11:33:55:77:01")
+        .setEtherType(Ethernet.TYPE_IPv4)
+        .setPayload(
+                    new IPv4()
+                    .setTtl((byte) 128)
+                    .setSourceAddress("192.168.10.1")
+                    .setDestinationAddress("192.168.255.255")
+                    .setPayload(new UDP()
+                    .setSourcePort((short) 5000)
+                    .setDestinationPort((short) 5001)
+                    .setPayload(new Data(new byte[] {0x01}))));
+        
+        this.dummyPkt =  new IPv4()
+        .setTtl((byte) 32)
+        .setSourceAddress("1.2.3.4")
+        .setDestinationAddress("5.6.7.8");
+        
+        this.packets = new IPacket[] { pkt1, pkt2, pkt3, pkt4 };
+    }
+    
+    protected void doTestClone(IPacket pkt) {
+        if (pkt.getPayload() != null)
+            doTestClone(pkt.getPayload());
+        IPacket newPkt = (IPacket)pkt.clone();
+        assertSame(pkt.getClass(), newPkt.getClass());
+        assertNotSame(pkt, newPkt);
+        assertSame(pkt.getParent(), newPkt.getParent());
+        assertEquals(pkt, newPkt);
+        assertEquals(pkt.getPayload(), newPkt.getPayload());
+        if (pkt.getPayload() != null)
+            assertNotSame(pkt.getPayload(), newPkt.getPayload());
+        
+        if (pkt instanceof Ethernet) {
+            Ethernet eth = (Ethernet)pkt;
+            Ethernet newEth = (Ethernet)newPkt;
+            newEth.setDestinationMACAddress(new byte[] { 1,2,3,4,5,6});
+            assertEquals(false, newEth.getDestinationMAC()
+                                .equals(eth.getDestinationMAC()));
+            assertEquals(false, newPkt.equals(pkt));
+        }
+        if (pkt instanceof ARP) {
+            ARP arp = (ARP)pkt;
+            ARP newArp = (ARP)newPkt;
+            newArp.setSenderProtocolAddress(new byte[] {1,2,3,4});
+            assertEquals(false, newArp.getSenderProtocolAddress()
+                                .equals(arp.getSenderProtocolAddress()));
+            assertEquals(false, newPkt.equals(pkt));
+        }
+        
+        byte[] dummyData = dummyPkt.serialize().clone();
+        newPkt = (IPacket)pkt.clone();
+        newPkt.deserialize(dummyData, 0, dummyData.length);
+        assertEquals(false, newPkt.equals(pkt));
+    }
+    
+    @Test
+    public void testClone() {
+        for (IPacket pkt: packets) {
+            doTestClone(pkt);
+        }
+    }
+    
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/TCPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/TCPTest.java
new file mode 100644
index 0000000..fcd5422
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/TCPTest.java
@@ -0,0 +1,74 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * @author shudong.zhou@bigswitch.com
+ */
+public class TCPTest {
+    private byte[] pktSerialized = new byte[] { 0x45, 0x20,
+            0x00, 0x34, 0x1d, (byte) 0x85, 0x00, 0x00, 0x32, 0x06,
+            0x31, 0x1e, 0x4a, 0x7d, 0x2d, 0x6d, (byte) 0xc0, (byte) 0xa8,
+            0x01, 0x6f, 0x03, (byte) 0xe1, (byte) 0xc0, 0x32, (byte) 0xe3, (byte) 0xad,
+            (byte) 0xee, (byte) 0x88, (byte) 0xb7, (byte) 0xda, (byte) 0xd8, 0x24, (byte) 0x80, 0x10,
+            0x01, 0x0b, 0x59, 0x33, 0x00, 0x00, 0x01, 0x01,
+            0x08, 0x0a, 0x20, (byte) 0x9a, 0x41, 0x04, 0x07, 0x76,
+            0x53, 0x1f};
+    
+    @Test
+    public void testSerialize() {
+        IPacket packet = new IPv4()
+        .setDiffServ((byte) 0x20)
+        .setIdentification((short) 0x1d85)
+        .setFlags((byte) 0x00)
+        .setTtl((byte) 50)
+        .setSourceAddress("74.125.45.109")
+        .setDestinationAddress("192.168.1.111")
+        .setPayload(new TCP()
+                        .setSourcePort((short) 993)
+                        .setDestinationPort((short) 49202)
+                        .setSequence(0xe3adee88)
+                        .setAcknowledge(0xb7dad824)
+                        .setDataOffset((byte) 8)
+                        .setFlags((short) 0x10)
+                        .setWindowSize((short) 267)
+                        .setOptions(new byte[] {0x01, 0x01, 0x08, 0x0a, 0x20, (byte) 0x9a,
+                                                0x41, 0x04, 0x07, 0x76, 0x53, 0x1f})
+                        .setPayload(null)
+                   );
+        byte[] actual = packet.serialize();
+        assertTrue(Arrays.equals(pktSerialized, actual));
+    }
+    
+    @Test
+    public void testDeserialize() {
+        IPacket packet = new IPv4();
+        packet.deserialize(pktSerialized, 0, pktSerialized.length);
+        byte[] pktSerialized1 = packet.serialize();
+        assertTrue(Arrays.equals(pktSerialized, pktSerialized1));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/UDPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/UDPTest.java
new file mode 100644
index 0000000..bed42a2
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/UDPTest.java
@@ -0,0 +1,55 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+/**
+ * 
+ */
+package net.floodlightcontroller.packet;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class UDPTest {
+
+    @Test
+    public void testSerialize() {
+        byte[] expected = new byte[] { 0x45, 0x00, 0x00, 0x1d, 0x56, 0x23,
+                0x00, 0x00, (byte) 0x80, 0x11, 0x48, 0x7f, (byte) 0xc0,
+                (byte) 0xa8, 0x01, 0x02, 0x0c, (byte) 0x81, (byte) 0xce, 0x02,
+                0x17, (byte) 0xe1, 0x04, 0x5f, 0x00, 0x09, 0x46, 0x6e,
+                0x01 };
+        IPacket packet = new IPv4()
+            .setIdentification((short) 22051)
+            .setTtl((byte) 128)
+            .setSourceAddress("192.168.1.2")
+            .setDestinationAddress("12.129.206.2")
+            .setPayload(new UDP()
+                            .setSourcePort((short) 6113)
+                            .setDestinationPort((short) 1119)
+                            .setPayload(new Data(new byte[] {0x01}))
+                       );
+        byte[] actual = packet.serialize();
+        assertTrue(Arrays.equals(expected, actual));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/routing/RouteTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/routing/RouteTest.java
new file mode 100644
index 0000000..3bd0398
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/routing/RouteTest.java
@@ -0,0 +1,63 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.routing;
+
+import org.junit.Test;
+
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class RouteTest extends FloodlightTestCase {
+    @Test
+    public void testCloneable() throws Exception {
+        Route r1 = new Route(1L, 2L);
+        Route r2 = new Route(1L, 3L);
+
+        assertNotSame(r1, r2);
+        assertNotSame(r1.getId(), r2.getId());
+
+        r1 = new Route(1L, 3L);
+        r1.getPath().add(new NodePortTuple(1L, (short)1));
+        r1.getPath().add(new NodePortTuple(2L, (short)1));
+        r1.getPath().add(new NodePortTuple(2L, (short)2));
+        r1.getPath().add(new NodePortTuple(3L, (short)1));
+
+        r2.getPath().add(new NodePortTuple(1L, (short)1));
+        r2.getPath().add(new NodePortTuple(2L, (short)1));
+        r2.getPath().add(new NodePortTuple(2L, (short)2));
+        r2.getPath().add(new NodePortTuple(3L, (short)1));
+
+        assertEquals(r1, r2);
+
+        NodePortTuple temp = r2.getPath().remove(0);
+        assertNotSame(r1, r2);
+
+        r2.getPath().add(0, temp);
+        assertEquals(r1, r2);
+
+        r2.getPath().remove(1);
+        temp = new NodePortTuple(2L, (short)5);
+        r2.getPath().add(1, temp);
+        assertNotSame(r1, r2);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java
new file mode 100644
index 0000000..186fd69
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java
@@ -0,0 +1,334 @@
+package net.floodlightcontroller.staticflowentry;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+import org.easymock.Capture;
+import org.easymock.CaptureType;
+import org.junit.Test;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import static net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher.*;
+import static org.easymock.EasyMock.*;
+
+public class StaticFlowTests extends FloodlightTestCase {    
+    
+    static String TestSwitch1DPID = "00:00:00:00:00:00:00:01";
+    static int TotalTestRules = 3;
+    
+    /***
+     * Create TestRuleXXX and the corresponding FlowModXXX
+     * for X = 1..3
+     */
+    static Map<String,Object> TestRule1;
+    static OFFlowMod FlowMod1;
+    static {
+        FlowMod1 = new OFFlowMod();
+        TestRule1 = new HashMap<String,Object>();
+        TestRule1.put(COLUMN_NAME, "TestRule1");
+        TestRule1.put(COLUMN_SWITCH, TestSwitch1DPID);
+        // setup match
+        OFMatch match = new OFMatch();
+        TestRule1.put(COLUMN_DL_DST, "00:20:30:40:50:60");
+        match.fromString("dl_dst=00:20:30:40:50:60");
+        // setup actions
+        List<OFAction> actions = new LinkedList<OFAction>();
+        TestRule1.put(COLUMN_ACTIONS, "output=1");
+        actions.add(new OFActionOutput((short)1, (short) Short.MAX_VALUE));
+        // done
+        FlowMod1.setMatch(match);
+        FlowMod1.setActions(actions);
+        FlowMod1.setBufferId(-1);
+        FlowMod1.setOutPort(OFPort.OFPP_NONE.getValue());
+        FlowMod1.setPriority(Short.MAX_VALUE);
+        FlowMod1.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8);  // 8 bytes of actions
+    }
+    
+    static Map<String,Object> TestRule2;
+    static OFFlowMod FlowMod2;
+
+    static {
+        FlowMod2 = new OFFlowMod();
+        TestRule2 = new HashMap<String,Object>();
+        TestRule2.put(COLUMN_NAME, "TestRule2");
+        TestRule2.put(COLUMN_SWITCH, TestSwitch1DPID);
+        // setup match
+        OFMatch match = new OFMatch();
+        TestRule2.put(COLUMN_NW_DST, "192.168.1.0/24");
+        match.fromString("nw_dst=192.168.1.0/24");
+        // setup actions
+        List<OFAction> actions = new LinkedList<OFAction>();
+        TestRule2.put(COLUMN_ACTIONS, "output=1");
+        actions.add(new OFActionOutput((short)1, (short) Short.MAX_VALUE));
+        // done
+        FlowMod2.setMatch(match);
+        FlowMod2.setActions(actions);
+        FlowMod2.setBufferId(-1);
+        FlowMod2.setOutPort(OFPort.OFPP_NONE.getValue());
+        FlowMod2.setPriority(Short.MAX_VALUE);
+        FlowMod2.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8);  // 8 bytes of actions
+
+    }
+    
+   
+    static Map<String,Object> TestRule3;
+    static OFFlowMod FlowMod3;
+    static {
+        FlowMod3 = new OFFlowMod();
+        TestRule3 = new HashMap<String,Object>();
+        TestRule3.put(COLUMN_NAME, "TestRule3");
+        TestRule3.put(COLUMN_SWITCH, TestSwitch1DPID);
+        // setup match
+        OFMatch match = new OFMatch();
+        TestRule3.put(COLUMN_DL_DST, "00:20:30:40:50:60");
+        TestRule3.put(COLUMN_DL_VLAN, 4096);
+        match.fromString("dl_dst=00:20:30:40:50:60,dl_vlan=4096");
+        // setup actions
+        TestRule3.put(COLUMN_ACTIONS, "output=controller");
+        List<OFAction> actions = new LinkedList<OFAction>();
+        actions.add(new OFActionOutput(OFPort.OFPP_CONTROLLER.getValue(), (short) Short.MAX_VALUE));
+        // done
+        FlowMod3.setMatch(match);
+        FlowMod3.setActions(actions);
+        FlowMod3.setBufferId(-1);
+        FlowMod3.setOutPort(OFPort.OFPP_NONE.getValue());
+        FlowMod3.setPriority(Short.MAX_VALUE);
+        FlowMod3.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8);  // 8 bytes of actions
+
+    }
+    
+    private void verifyFlowMod(OFFlowMod testFlowMod,
+            OFFlowMod goodFlowMod) {
+        verifyMatch(testFlowMod, goodFlowMod);
+        verifyActions(testFlowMod, goodFlowMod);
+        // dont' bother testing the cookie; just copy it over
+        goodFlowMod.setCookie(testFlowMod.getCookie());
+        // .. so we can continue to use .equals()
+        assertEquals(goodFlowMod, testFlowMod);
+    }
+    
+
+    private void verifyMatch(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) {
+        assertEquals(goodFlowMod.getMatch(), testFlowMod.getMatch());
+    }
+
+
+    private void verifyActions(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) {
+        List<OFAction> goodActions = goodFlowMod.getActions();
+        List<OFAction> testActions = testFlowMod.getActions();
+        assertNotNull(goodActions);
+        assertNotNull(testActions);
+        assertEquals(goodActions.size(), testActions.size());
+        // assumes actions are marshalled in same order; should be safe
+        for(int i = 0; i < goodActions.size(); i++) {
+            assertEquals(goodActions.get(i), testActions.get(i));
+        }
+
+    }
+
+
+    @Override 
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+    
+    @Test
+    public void testStaticFlowPush() throws IOException {        
+        StaticFlowEntryPusher staticFlowEntryPusher = new StaticFlowEntryPusher();
+        IStorageSourceService storage = createStorageWithFlowEntries();
+        long dpid = HexString.toLong(TestSwitch1DPID);
+
+        // Create a Switch and attach a switch
+        IOFSwitch mockSwitch = createNiceMock(IOFSwitch.class);
+        Capture<OFMessage> writeCapture = new Capture<OFMessage>(CaptureType.ALL);
+        Capture<FloodlightContext> contextCapture = new Capture<FloodlightContext>(CaptureType.ALL);
+        Capture<List<OFMessage>> writeCaptureList = new Capture<List<OFMessage>>(CaptureType.ALL);
+
+        //OFMessageSafeOutStream mockOutStream = createNiceMock(OFMessageSafeOutStream.class);
+        mockSwitch.write(capture(writeCapture), capture(contextCapture));
+        expectLastCall().anyTimes();
+        mockSwitch.write(capture(writeCaptureList), capture(contextCapture));
+        expectLastCall().anyTimes();
+        mockSwitch.flush();
+        expectLastCall().anyTimes();
+        
+        staticFlowEntryPusher.setStorageSource(storage);
+        
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        
+        MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider();
+        Map<Long, IOFSwitch> switchMap = new HashMap<Long, IOFSwitch>();
+        switchMap.put(dpid, mockSwitch);
+        // NO ! expect(mockFloodlightProvider.getSwitches()).andReturn(switchMap).anyTimes();
+        mockFloodlightProvider.setSwitches(switchMap);
+        staticFlowEntryPusher.setFloodlightProvider(mockFloodlightProvider);
+        RestApiServer restApi = new RestApiServer();
+        try {
+            restApi.init(fmc);
+        } catch (FloodlightModuleException e) {
+            e.printStackTrace();
+        }
+        staticFlowEntryPusher.restApi = restApi;
+        staticFlowEntryPusher.startUp(null);    // again, to hack unittest
+
+        // verify that flowpusher read all three entries from storage
+        assertEquals(TotalTestRules, staticFlowEntryPusher.countEntries());
+        
+        // if someone calls mockSwitch.getOutputStream(), return mockOutStream instead
+        //expect(mockSwitch.getOutputStream()).andReturn(mockOutStream).anyTimes();    
+
+        // if someone calls getId(), return this dpid instead
+        expect(mockSwitch.getId()).andReturn(dpid).anyTimes();
+        expect(mockSwitch.getStringId()).andReturn(TestSwitch1DPID).anyTimes();
+        replay(mockSwitch);
+        
+        // hook the static pusher up to the fake switch
+        staticFlowEntryPusher.addedSwitch(mockSwitch);
+        
+        verify(mockSwitch);
+        
+        // Verify that the switch has gotten some flow_mods
+        assertEquals(true, writeCapture.hasCaptured());
+        assertEquals(TotalTestRules, writeCapture.getValues().size());
+        
+        // Order assumes how things are stored in hash bucket; 
+        // should be fixed because OFMessage.hashCode() is deterministic
+        OFFlowMod firstFlowMod = (OFFlowMod) writeCapture.getValues().get(2);
+        verifyFlowMod(firstFlowMod, FlowMod1);
+        OFFlowMod secondFlowMod = (OFFlowMod) writeCapture.getValues().get(1);
+        verifyFlowMod(secondFlowMod, FlowMod2);
+        OFFlowMod thirdFlowMod = (OFFlowMod) writeCapture.getValues().get(0);
+        verifyFlowMod(thirdFlowMod, FlowMod3);
+        
+        writeCapture.reset();
+        contextCapture.reset();
+        
+        
+        // delete two rules and verify they've been removed
+        // this should invoke staticFlowPusher.rowsDeleted()
+        storage.deleteRow(StaticFlowEntryPusher.TABLE_NAME, "TestRule1");
+        storage.deleteRow(StaticFlowEntryPusher.TABLE_NAME, "TestRule2");
+
+        assertEquals(1, staticFlowEntryPusher.countEntries());
+        assertEquals(2, writeCapture.getValues().size());
+        
+        OFFlowMod firstDelete = (OFFlowMod) writeCapture.getValues().get(0);
+        FlowMod1.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
+        verifyFlowMod(firstDelete, FlowMod1);
+
+        OFFlowMod secondDelete = (OFFlowMod) writeCapture.getValues().get(1);
+        FlowMod2.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
+        verifyFlowMod(secondDelete, FlowMod2);
+        
+        // add rules back to make sure that staticFlowPusher.rowsInserted() works
+        writeCapture.reset();
+        FlowMod2.setCommand(OFFlowMod.OFPFC_ADD);
+        storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule2);
+        assertEquals(2, staticFlowEntryPusher.countEntries());
+        assertEquals(1, writeCaptureList.getValues().size());
+        List<OFMessage> outList = 
+            (List<OFMessage>) writeCaptureList.getValues().get(0);
+        assertEquals(1, outList.size());
+        OFFlowMod firstAdd = (OFFlowMod) outList.get(0);
+        verifyFlowMod(firstAdd, FlowMod2);
+        writeCapture.reset();
+        contextCapture.reset();
+        writeCaptureList.reset();
+        
+        // now try an update, calling staticFlowPusher.rowUpdated()
+        TestRule3.put(COLUMN_DL_VLAN, 333);
+        storage.updateRow(StaticFlowEntryPusher.TABLE_NAME, TestRule3);
+        assertEquals(2, staticFlowEntryPusher.countEntries());
+        assertEquals(1, writeCaptureList.getValues().size());
+
+        outList = (List<OFMessage>) writeCaptureList.getValues().get(0);
+        assertEquals(2, outList.size());
+        OFFlowMod removeFlowMod = (OFFlowMod) outList.get(0);
+        FlowMod3.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
+        verifyFlowMod(removeFlowMod, FlowMod3);
+        FlowMod3.setCommand(OFFlowMod.OFPFC_ADD);
+        FlowMod3.getMatch().fromString("dl_dst=00:20:30:40:50:60,dl_vlan=333");
+        OFFlowMod updateFlowMod = (OFFlowMod) outList.get(1);
+        verifyFlowMod(updateFlowMod, FlowMod3);
+
+    }
+
+
+    IStorageSourceService createStorageWithFlowEntries() {
+        return populateStorageWithFlowEntries(new MemoryStorageSource());
+    }
+    
+    IStorageSourceService populateStorageWithFlowEntries(IStorageSourceService storage) {
+        Set<String> indexedColumns = new HashSet<String>();
+        indexedColumns.add(COLUMN_NAME);
+        storage.createTable(StaticFlowEntryPusher.TABLE_NAME, indexedColumns);
+        storage.setTablePrimaryKeyName(StaticFlowEntryPusher.TABLE_NAME, COLUMN_NAME);
+
+        storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule1);
+        storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule2);
+        storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule3);
+
+        return storage;
+    }
+    
+    @Test 
+    public void testHARoleChanged() throws IOException {
+        StaticFlowEntryPusher staticFlowEntryPusher = new StaticFlowEntryPusher();
+        IStorageSourceService storage = createStorageWithFlowEntries();
+        MockFloodlightProvider mfp = getMockFloodlightProvider();
+        staticFlowEntryPusher.setFloodlightProvider(mfp);
+        staticFlowEntryPusher.setStorageSource(storage);
+        RestApiServer restApi = new RestApiServer();
+        try {
+            FloodlightModuleContext fmc = new FloodlightModuleContext();
+            restApi.init(fmc);
+        } catch (FloodlightModuleException e) {
+            e.printStackTrace();
+        }
+        staticFlowEntryPusher.restApi = restApi;
+        staticFlowEntryPusher.startUp(null);    // again, to hack unittest
+        
+        assert(staticFlowEntryPusher.entry2dpid.containsValue(TestSwitch1DPID));
+        assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod1));
+        assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod2));
+        assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod3));
+        
+        // Send a notification that we've changed to slave
+        mfp.dispatchRoleChanged(null, Role.SLAVE);
+        // Make sure we've removed all our entries
+        assert(staticFlowEntryPusher.entry2dpid.isEmpty());
+        assert(staticFlowEntryPusher.entriesFromStorage.isEmpty());
+        
+        // Send a notification that we've changed to master
+        mfp.dispatchRoleChanged(Role.SLAVE, Role.MASTER);  
+        // Make sure we've learned the entries
+        assert(staticFlowEntryPusher.entry2dpid.containsValue(TestSwitch1DPID));
+        assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod1));
+        assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod2));
+        assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod3));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/memory/tests/MemoryStorageTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/memory/tests/MemoryStorageTest.java
new file mode 100644
index 0000000..c250066
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/memory/tests/MemoryStorageTest.java
@@ -0,0 +1,41 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage.memory.tests;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import net.floodlightcontroller.storage.tests.StorageTest;
+import org.junit.Before;
+
+public class MemoryStorageTest extends StorageTest {
+
+    @Before
+    public void setUp() throws Exception {
+        storageSource = new MemoryStorageSource();
+        restApi = new RestApiServer();
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        fmc.addService(IRestApiService.class, restApi);
+        restApi.init(fmc);
+        storageSource.init(fmc);
+        restApi.startUp(fmc);
+        storageSource.startUp(fmc);
+        super.setUp();
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java
new file mode 100644
index 0000000..29cc15b
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java
@@ -0,0 +1,743 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.storage.tests;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.storage.CompoundPredicate;
+import net.floodlightcontroller.storage.IStorageExceptionHandler;
+import net.floodlightcontroller.storage.IPredicate;
+import net.floodlightcontroller.storage.IQuery;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IRowMapper;
+import net.floodlightcontroller.storage.IStorageSourceListener;
+import net.floodlightcontroller.storage.NullValueStorageException;
+import net.floodlightcontroller.storage.OperatorPredicate;
+import net.floodlightcontroller.storage.RowOrdering;
+import net.floodlightcontroller.storage.nosql.NoSqlStorageSource;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+import org.junit.Test;
+
+public abstract class StorageTest extends FloodlightTestCase {
+    
+    protected NoSqlStorageSource storageSource;
+    protected RestApiServer restApi;
+    
+    protected String PERSON_TABLE_NAME = "Person";
+    
+    protected String PERSON_SSN = "SSN";
+    protected String PERSON_FIRST_NAME = "FirstName";
+    protected String PERSON_LAST_NAME = "LastName";
+    protected String PERSON_AGE = "Age";
+    protected String PERSON_REGISTERED = "Registered";
+    
+    protected String[] PERSON_COLUMN_LIST = {PERSON_SSN, PERSON_FIRST_NAME, PERSON_LAST_NAME, PERSON_AGE, PERSON_REGISTERED};
+    
+    class Person {
+        private String ssn;
+        private String firstName;
+        private String lastName;
+        int age;
+        boolean registered;
+        
+        public Person(String ssn, String firstName, String lastName, int age, boolean registered) {
+            this.ssn = ssn;
+            this.firstName = firstName;
+            this.lastName = lastName;
+            this.age = age;
+            this.registered = registered;
+        }
+        
+        public String getSSN() {
+            return ssn;
+        }
+        
+        public String getFirstName() {
+            return firstName;
+        }
+        
+        public String getLastName() {
+            return lastName;
+            
+        }
+        
+        public int getAge() {
+            return age;
+        }
+        
+        public boolean isRegistered() {
+            return registered;
+        }
+    }
+    
+    class PersonRowMapper implements IRowMapper {
+        public Object mapRow(IResultSet resultSet) {
+            String ssn = resultSet.getString(PERSON_SSN);
+            String firstName = resultSet.getString(PERSON_FIRST_NAME);
+            String lastName = resultSet.getString(PERSON_LAST_NAME);
+            int age = resultSet.getInt(PERSON_AGE);
+            boolean registered = resultSet.getBoolean(PERSON_REGISTERED);
+            return new Person(ssn, firstName, lastName, age, registered);
+        }
+    }
+    
+    Object[][] PERSON_INIT_DATA = {
+            {"111-11-1111", "John", "Smith", 40, true},
+            {"222-22-2222", "Jim", "White", 24, false},
+            {"333-33-3333", "Lisa", "Jones", 27, true},
+            {"444-44-4444", "Susan", "Jones", 14, false},
+            {"555-55-5555", "Jose", "Garcia", 31, true},
+            {"666-66-6666", "Abigail", "Johnson", 35, false},
+            {"777-77-7777", "Bjorn", "Borg", 55, true},
+            {"888-88-8888", "John", "McEnroe", 53, false}
+    };
+
+    Map<String,Object> createPersonRowValues(Object[] personData) {
+        Map<String,Object> rowValues = new HashMap<String,Object>();
+        for (int i = 0; i < PERSON_COLUMN_LIST.length; i++) {
+            rowValues.put(PERSON_COLUMN_LIST[i], personData[i]);
+        }
+        return rowValues;
+    }
+    
+    public void insertPerson(Object[] personData) {
+        Map<String,Object> rowValues = createPersonRowValues(personData);
+        storageSource.insertRow(PERSON_TABLE_NAME, rowValues);
+    }
+    
+    public void initPersons() {
+        for (Object[] row: PERSON_INIT_DATA) {
+            insertPerson(row);
+        }
+    }
+    
+    public void setUp() throws Exception {
+        super.setUp();
+        Set<String> indexedColumnNames = new HashSet<String>();
+        indexedColumnNames.add(PERSON_LAST_NAME);
+        storageSource.setExceptionHandler(null);
+        storageSource.createTable(PERSON_TABLE_NAME, indexedColumnNames);
+        storageSource.setTablePrimaryKeyName(PERSON_TABLE_NAME, PERSON_SSN);        
+        initPersons();
+    }
+
+    public void checkExpectedResults(IResultSet resultSet, String[] columnNameList, Object[][] expectedRowList) {
+        boolean nextResult;
+        for (Object[] expectedRow: expectedRowList) {
+            nextResult = resultSet.next();
+            assertEquals(nextResult,true);
+            assertEquals(expectedRow.length, columnNameList.length);
+            for (int i = 0; i < expectedRow.length; i++) {
+                Object expectedObject = expectedRow[i];
+                String columnName = columnNameList[i];
+                if (expectedObject instanceof Boolean)
+                    assertEquals(((Boolean)expectedObject).booleanValue(), resultSet.getBoolean(columnName));
+                else if (expectedObject instanceof Byte)
+                    assertEquals(((Byte)expectedObject).byteValue(), resultSet.getByte(columnName));
+                else if (expectedObject instanceof Short)
+                    assertEquals(((Short)expectedObject).shortValue(), resultSet.getShort(columnName));
+                else if (expectedObject instanceof Integer)
+                    assertEquals(((Integer)expectedObject).intValue(), resultSet.getInt(columnName));
+                else if (expectedObject instanceof Long)
+                    assertEquals(((Long)expectedObject).longValue(), resultSet.getLong(columnName));
+                else if (expectedObject instanceof Float)
+                    assertEquals(((Float)expectedObject).floatValue(), resultSet.getFloat(columnName), 0.00001);
+                else if (expectedObject instanceof Double)
+                    assertEquals(((Double)expectedObject).doubleValue(), resultSet.getDouble(columnName), 0.00001);
+                else if (expectedObject instanceof byte[])
+                    assertEquals((byte[])expectedObject, resultSet.getByteArray(columnName));
+                else if (expectedObject instanceof String)
+                    assertEquals((String)expectedObject, resultSet.getString(columnName));
+                else
+                    assertTrue("Unexpected column value type", false);
+            }
+        }
+        nextResult = resultSet.next();
+        assertEquals(nextResult,false);
+        resultSet.close();
+    }
+    
+    @Test
+    public void testInsertRows() {
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, PERSON_COLUMN_LIST, PERSON_INIT_DATA);
+    }
+    
+    @Test
+    public void testOperatorQuery() {
+        Object[][] expectedResults = {
+                {"John", "Smith", 40},
+                {"Jim", "White", 24},
+        };
+        String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME,PERSON_AGE};
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, columnList,
+                new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.GTE, "Sm"),
+                new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, columnList, expectedResults);
+    }
+    
+    @Test
+    public void testAndQuery() {
+        String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME};        
+        Object[][] expectedResults = {
+                {"Lisa", "Jones"},
+                {"Susan", "Jones"},
+                {"Jose", "Garcia"},
+                {"Abigail", "Johnson"},
+                {"John", "McEnroe"}
+        };
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, columnList,
+                new CompoundPredicate(CompoundPredicate.Operator.AND, false,
+                        new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.GTE, "G"),
+                        new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.LT, "N")
+                ),
+                new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, columnList, expectedResults);
+    }
+    
+    @Test
+    public void testOrQuery() {
+        String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME, PERSON_AGE};        
+        Object[][] expectedResults = {
+                {"John", "Smith", 40},
+                {"Lisa", "Jones", 27},
+                {"Abigail", "Johnson", 35},
+                {"Bjorn", "Borg", 55},
+                {"John", "McEnroe", 53}
+        };
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, columnList,
+                new CompoundPredicate(CompoundPredicate.Operator.OR, false,
+                        new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.GTE, 35),
+                        new OperatorPredicate(PERSON_FIRST_NAME, OperatorPredicate.Operator.EQ, "Lisa")
+                ),
+                new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, columnList, expectedResults);
+}
+    
+    @Test
+    public void testCreateQuery() {
+        String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME};
+        Object[][] expectedResults = {
+                {"Lisa", "Jones"},
+                {"Susan", "Jones"}
+        };
+        IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.EQ, "Jones");
+        IQuery query = storageSource.createQuery(PERSON_TABLE_NAME, columnList, predicate, new RowOrdering(PERSON_SSN));
+        IResultSet resultSet = storageSource.executeQuery(query);
+        checkExpectedResults(resultSet, columnList, expectedResults);
+    }
+    
+    @Test
+    public void testQueryParameters() {
+        String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME, PERSON_AGE};        
+        Object[][] expectedResults = {
+                {"John", "Smith", 40},
+                {"Bjorn", "Borg", 55},
+                {"John", "McEnroe", 53}
+        };
+        IPredicate predicate = new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.GTE, "?MinimumAge?");
+        IQuery query = storageSource.createQuery(PERSON_TABLE_NAME, columnList, predicate, new RowOrdering(PERSON_SSN));
+        query.setParameter("MinimumAge", 40);
+        IResultSet resultSet = storageSource.executeQuery(query);
+        checkExpectedResults(resultSet, columnList, expectedResults);
+    }
+    
+    private void checkPerson(Person person, Object[] expectedValues) {
+        assertEquals(person.getSSN(), expectedValues[0]);
+        assertEquals(person.getFirstName(), expectedValues[1]);
+        assertEquals(person.getLastName(), expectedValues[2]);
+        assertEquals(person.getAge(), expectedValues[3]);
+        assertEquals(person.isRegistered(), expectedValues[4]);
+    }
+    
+    @Test
+    public void testRowMapper() {
+        Object[][] expectedResults = {
+                PERSON_INIT_DATA[2],
+                PERSON_INIT_DATA[3]
+        };
+        IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.EQ, "Jones");
+        IRowMapper rowMapper = new PersonRowMapper();
+        Object[] personList = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN), rowMapper);
+        assertEquals(personList.length, 2);
+        for (int i = 0; i < personList.length; i++)
+            checkPerson((Person)personList[i], expectedResults[i]);
+    }
+    
+    @Test
+    public void testDeleteRowsDirect() {
+        
+        storageSource.deleteRow(PERSON_TABLE_NAME, "111-11-1111");
+        storageSource.deleteRow(PERSON_TABLE_NAME, "222-22-2222");
+        storageSource.deleteRow(PERSON_TABLE_NAME, "333-33-3333");
+        storageSource.deleteRow(PERSON_TABLE_NAME, "444-44-4444");
+        
+        Object[][] expectedResults = {
+                {"555-55-5555", "Jose", "Garcia", 31, true},
+                {"666-66-6666", "Abigail", "Johnson", 35, false},
+                {"777-77-7777", "Bjorn", "Borg", 55, true},
+                {"888-88-8888", "John", "McEnroe", 53, false}
+        };
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+    }
+    
+    @Test
+    public void testDeleteRowsFromResultSet() {
+        Object[][] expectedResults = {
+                {"555-55-5555", "Jose", "Garcia", 31, true},
+                {"666-66-6666", "Abigail", "Johnson", 35, false},
+                {"777-77-7777", "Bjorn", "Borg", 55, true},
+                {"888-88-8888", "John", "McEnroe", 53, false}
+        };
+        
+        // Query once to delete the rows
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+        for (int i = 0; i < 4; i++) {
+            resultSet.next();
+            resultSet.deleteRow();
+        }
+        resultSet.save();
+        resultSet.close();
+        
+        // Now query again to verify that the rows were deleted
+        resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+    }
+    
+    @Test
+    public void testDeleteMatchingRows() {
+        Object[][] expectedResults = {
+                {"111-11-1111", "John", "Smith", 40, true},
+                {"777-77-7777", "Bjorn", "Borg", 55, true},
+                {"888-88-8888", "John", "McEnroe", 53, false}
+        };
+        storageSource.deleteMatchingRows(PERSON_TABLE_NAME, new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.LT, 40));
+        
+        // Now query again to verify that the rows were deleted
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+        
+        storageSource.deleteMatchingRows(PERSON_TABLE_NAME, null);
+
+        // Now query again to verify that all rows were deleted
+        resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, PERSON_COLUMN_LIST, new Object[0][]);
+    }
+    
+    @Test
+    public void testUpdateRowsDirect() {
+        
+        Object[][] expectedResults = {
+                {"777-77-7777", "Tennis", "Borg", 60, true},
+                {"888-88-8888", "Tennis", "McEnroe", 60, false}
+        };
+        Map<String,Object> updateValues = new HashMap<String,Object>();
+        updateValues.put(PERSON_FIRST_NAME, "Tennis");
+        updateValues.put(PERSON_AGE, 60);
+        
+        IPredicate predicate = new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.GT, 50);
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN));
+        while (resultSet.next()) {
+            String key = resultSet.getString(PERSON_SSN);
+            storageSource.updateRow(PERSON_TABLE_NAME, key, updateValues);
+        }
+        resultSet.close();
+        
+        resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, predicate, new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+    }
+    
+    @Test
+    public void testUpdateRowsFromResultSet() {
+        
+        Object[][] expectedResults = {
+                {"777-77-7777", "Tennis", "Borg", 60, true},
+                {"888-88-8888", "Tennis", "McEnroe", 60, false}
+        };
+        
+        IPredicate predicate = new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.GT, 50);
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, null);
+        while (resultSet.next()) {
+            resultSet.setString(PERSON_FIRST_NAME, "Tennis");
+            resultSet.setInt(PERSON_AGE, 60);
+        }
+        resultSet.save();
+        resultSet.close();
+        
+        resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, predicate, new RowOrdering(PERSON_SSN));
+        checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+    }
+    
+    @Test
+    public void testNullValues() {
+        
+        IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.EQ, "Jones");
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN));
+        while (resultSet.next()) {
+            resultSet.setNull(PERSON_FIRST_NAME);
+            resultSet.setIntegerObject(PERSON_AGE, null);
+        }
+        resultSet.save();
+        resultSet.close();
+
+        resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN));
+        int count = 0;
+        while (resultSet.next()) {
+            boolean checkNull = resultSet.isNull(PERSON_FIRST_NAME);
+            assertTrue(checkNull);
+            String s = resultSet.getString(PERSON_FIRST_NAME);
+            assertEquals(s, null);
+            checkNull = resultSet.isNull(PERSON_AGE);
+            assertTrue(checkNull);
+            Integer intObj = resultSet.getIntegerObject(PERSON_AGE);
+            assertEquals(intObj, null);
+            Short shortObj = resultSet.getShortObject(PERSON_AGE);
+            assertEquals(shortObj, null);
+            boolean excThrown = false;
+            try {
+                resultSet.getInt(PERSON_AGE);
+            }
+            catch (NullValueStorageException exc) {
+                excThrown = true;
+            }
+            assertTrue(excThrown);
+            count++;
+        }
+        resultSet.close();
+        assertEquals(count, 2);
+        
+        predicate = new OperatorPredicate(PERSON_FIRST_NAME, OperatorPredicate.Operator.EQ, null);
+        resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN));
+        count = 0;
+        while (resultSet.next()) {
+            boolean checkNull = resultSet.isNull(PERSON_FIRST_NAME);
+            assertTrue(checkNull);
+            count++;
+            checkNull = resultSet.isNull(PERSON_AGE);
+            assertTrue(checkNull);
+        }
+        resultSet.close();
+        assertEquals(count, 2);
+    }
+    
+    @Test
+    public void testInsertNotification() {
+        // Set up the listener and record the expected notification
+        IStorageSourceListener mockListener = createNiceMock(IStorageSourceListener.class);
+        Set<Object> expectedKeys = new HashSet<Object>();
+        expectedKeys.add("999-99-9999");
+        mockListener.rowsModified(PERSON_TABLE_NAME, expectedKeys);
+        
+        replay(mockListener);
+
+        // Now try it for real
+        storageSource.addListener(PERSON_TABLE_NAME, mockListener);
+
+        // Create a new person, which should trigger the listener
+        Object[] newPerson = {"999-99-9999", "Serena", "Williams", 22, true};
+        insertPerson(newPerson);
+        
+        verify(mockListener);
+    }
+    
+    @Test
+    public void testUpdateNotification() {
+        // Set up the listener and record the expected notification
+        IStorageSourceListener mockListener = createNiceMock(IStorageSourceListener.class);
+        Set<Object> expectedKeys = new HashSet<Object>();
+        expectedKeys.add("111-11-1111");
+        mockListener.rowsModified(PERSON_TABLE_NAME, expectedKeys);
+        
+        replay(mockListener);
+
+        // Now try it for real
+        storageSource.addListener(PERSON_TABLE_NAME, mockListener);
+
+        // Create a new person, which should trigger the listener
+        Map<String,Object> updateValues = new HashMap<String,Object>();
+        updateValues.put(PERSON_FIRST_NAME, "Tennis");
+        storageSource.updateRow(PERSON_TABLE_NAME, "111-11-1111", updateValues);
+        
+        verify(mockListener);
+    }
+    
+    @Test
+    public void testDeleteNotification() {
+        IStorageSourceListener mockListener = createNiceMock(IStorageSourceListener.class);
+        Set<Object> expectedKeys = new HashSet<Object>();
+        expectedKeys.add("111-11-1111");
+        mockListener.rowsDeleted(PERSON_TABLE_NAME, expectedKeys);
+        
+        replay(mockListener);
+
+        // Now try it for real
+        storageSource.addListener(PERSON_TABLE_NAME, mockListener);
+
+        // Create a new person, which should trigger the listener
+        storageSource.deleteRow(PERSON_TABLE_NAME, "111-11-1111");
+        
+        verify(mockListener);
+    }
+    
+    public void waitForFuture(Future<?> future) {
+        try
+        {
+            future.get(10, TimeUnit.SECONDS);
+        }
+        catch (InterruptedException exc)
+        {
+            fail("Async storage operation interrupted");
+        }
+        catch (ExecutionException exc) {
+            fail("Async storage operation failed");
+        }
+        catch (TimeoutException exc) {
+            fail("Async storage operation timed out");
+        }
+    }
+    
+    @Test
+    public void testAsyncQuery1() {
+        Object[][] expectedResults = {
+                {"John", "Smith", 40},
+                {"Jim", "White", 24},
+        };
+        String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME,PERSON_AGE};
+        IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.GTE, "Sm");
+        IQuery query = storageSource.createQuery(PERSON_TABLE_NAME, columnList, predicate, new RowOrdering(PERSON_SSN));
+        Future<IResultSet> future = storageSource.executeQueryAsync(query);
+        waitForFuture(future);
+        try {
+            IResultSet resultSet = future.get();
+            checkExpectedResults(resultSet, columnList, expectedResults);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+    }
+    
+    @Test
+    public void testAsyncQuery2() {
+        Object[][] expectedResults = {
+                {"John", "Smith", 40},
+                {"Jim", "White", 24},
+        };
+        String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME,PERSON_AGE};
+        IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.GTE, "Sm");
+        Future<IResultSet> future = storageSource.executeQueryAsync(PERSON_TABLE_NAME,
+                columnList, predicate, new RowOrdering(PERSON_SSN));
+        waitForFuture(future);
+        try {
+            IResultSet resultSet = future.get();
+            checkExpectedResults(resultSet, columnList, expectedResults);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+    }
+    
+    @Test
+    public void testAsyncQuery3() {
+        Object[][] expectedResults = {
+                PERSON_INIT_DATA[2],
+                PERSON_INIT_DATA[3]
+        };
+        IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.EQ, "Jones");
+        IRowMapper rowMapper = new PersonRowMapper();
+        Future<Object[]> future = storageSource.executeQueryAsync(PERSON_TABLE_NAME,
+                null, predicate, new RowOrdering(PERSON_SSN), rowMapper);
+        waitForFuture(future);
+        try {
+            Object[] personList = future.get();
+            assertEquals(personList.length, 2);
+            for (int i = 0; i < personList.length; i++)
+                checkPerson((Person)personList[i], expectedResults[i]);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+    }
+    
+    @Test
+    public void testAsyncException() {
+        class TestExceptionHandler implements IStorageExceptionHandler {
+            public int exceptionCount = 0;
+            @Override
+            public void handleException(Exception exception) {
+                exceptionCount++;
+            }
+        }
+        TestExceptionHandler exceptionHandler = new TestExceptionHandler();
+        storageSource.setExceptionHandler(exceptionHandler);
+        
+        // Use an invalid table name, which should cause the storage API call to throw
+        // an exception, which should then be converted to an ExecutionException.
+        Future<IResultSet> future = storageSource.executeQueryAsync("InvalidTableName",
+                null, null, null);
+        try {
+            future.get(10, TimeUnit.SECONDS);
+            fail("Expected ExecutionException was not thrown");
+        }
+        catch (ExecutionException e) {
+            assertTrue(true);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+        assertEquals(exceptionHandler.exceptionCount, 1);
+    }
+    
+    @Test
+    public void testAsyncInsertRow() {
+        Object[][] newPersonInfo = {{"999-99-9999", "Ellen", "Wilson", 40, true}};
+        Map<String,Object> rowValues = createPersonRowValues(newPersonInfo[0]);
+        Future<?> future = storageSource.insertRowAsync(PERSON_TABLE_NAME, rowValues);
+        waitForFuture(future);
+        try {
+            IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+            Object[][] expectedPersons = Arrays.copyOf(PERSON_INIT_DATA, PERSON_INIT_DATA.length + newPersonInfo.length);
+            System.arraycopy(newPersonInfo, 0, expectedPersons, PERSON_INIT_DATA.length, newPersonInfo.length);
+            checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+    }
+    
+    @Test
+    public void testAsyncUpdateRow() {
+        Map<String,Object> updateValues = new HashMap<String,Object>();
+        updateValues.put(PERSON_SSN, "777-77-7777");
+        updateValues.put(PERSON_FIRST_NAME, "Tennis");
+        updateValues.put(PERSON_AGE, 60);
+
+        Future<?> future = storageSource.updateRowAsync(PERSON_TABLE_NAME, updateValues);
+        waitForFuture(future);
+
+        try {
+            IResultSet resultSet = storageSource.getRow(PERSON_TABLE_NAME, "777-77-7777");
+            Object[][] expectedPersons = {{"777-77-7777", "Tennis", "Borg", 60, true}};
+            checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+    }
+    
+    @Test
+    public void testAsyncUpdateRow2() {
+        Map<String,Object> updateValues = new HashMap<String,Object>();
+        updateValues.put(PERSON_FIRST_NAME, "Tennis");
+        updateValues.put(PERSON_AGE, 60);
+
+        Future<?> future = storageSource.updateRowAsync(PERSON_TABLE_NAME, "777-77-7777", updateValues);
+        waitForFuture(future);
+
+        try {
+            IResultSet resultSet = storageSource.getRow(PERSON_TABLE_NAME, "777-77-7777");
+            Object[][] expectedPersons = {{"777-77-7777", "Tennis", "Borg", 60, true}};
+            checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+    }
+    
+    @Test
+    public void testAsyncUpdateMatchingRows() {
+        Map<String,Object> updateValues = new HashMap<String,Object>();
+        updateValues.put(PERSON_FIRST_NAME, "Tennis");
+        updateValues.put(PERSON_AGE, 60);
+
+        IPredicate predicate = new OperatorPredicate(PERSON_SSN, OperatorPredicate.Operator.EQ, "777-77-7777");
+        Future<?> future = storageSource.updateMatchingRowsAsync(PERSON_TABLE_NAME, predicate, updateValues);
+        waitForFuture(future);
+        try {
+            IResultSet resultSet = storageSource.getRow(PERSON_TABLE_NAME, "777-77-7777");
+            Object[][] expectedPersons = {{"777-77-7777", "Tennis", "Borg", 60, true}};
+            checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+    }
+    
+    @Test
+    public void testAsyncDeleteRow() {
+        Future<?> future = storageSource.deleteRowAsync(PERSON_TABLE_NAME, "111-11-1111");
+        waitForFuture(future);
+        try {
+            IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+            Object[][] expectedPersons = Arrays.copyOfRange(PERSON_INIT_DATA, 1, PERSON_INIT_DATA.length);
+            checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+    }
+    
+    @Test
+    public void testAsyncDeleteMatchingRows() {
+        Future<?> future = storageSource.deleteMatchingRowsAsync(PERSON_TABLE_NAME, null);
+        waitForFuture(future);
+        try {
+            IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+            checkExpectedResults(resultSet, PERSON_COLUMN_LIST, new Object[0][]);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+    }
+    
+    @Test
+    public void testAsyncSave() {
+        // Get a result set and make some changes to it
+        IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+        resultSet.next();
+        resultSet.deleteRow();
+        resultSet.next();
+        resultSet.setString(PERSON_FIRST_NAME, "John");
+        
+        Future<?> future = storageSource.saveAsync(resultSet);
+        waitForFuture(future);
+        try {
+            resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+            Object[][] expectedPersons = Arrays.copyOfRange(PERSON_INIT_DATA, 1, PERSON_INIT_DATA.length);
+            expectedPersons[0][1] = "John";
+            checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+        }
+        catch (Exception e) {
+            fail("Exception thrown in async storage operation: " + e.toString());
+        }
+        
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java
new file mode 100644
index 0000000..b0e83cc
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java
@@ -0,0 +1,95 @@
+/**
+*    Copyright 2011, Big Switch Networks, Inc. 
+*    Originally created by David Erickson, Stanford University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package net.floodlightcontroller.test;
+
+import junit.framework.TestCase;
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.packet.Ethernet;
+
+import org.junit.Test;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFType;
+
+/**
+ * This class gets a handle on the application context which is used to
+ * retrieve Spring beans from during tests
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class FloodlightTestCase extends TestCase {
+    protected MockFloodlightProvider mockFloodlightProvider;
+
+    public MockFloodlightProvider getMockFloodlightProvider() {
+        return mockFloodlightProvider;
+    }
+
+    public void setMockFloodlightProvider(MockFloodlightProvider mockFloodlightProvider) {
+        this.mockFloodlightProvider = mockFloodlightProvider;
+    }
+
+    public FloodlightContext parseAndAnnotate(OFMessage m,
+                                              IDevice srcDevice,
+                                              IDevice dstDevice) {
+        FloodlightContext bc = new FloodlightContext();
+        return parseAndAnnotate(bc, m, srcDevice, dstDevice);
+    }
+
+    public FloodlightContext parseAndAnnotate(OFMessage m) {
+        return parseAndAnnotate(m, null, null);
+    }
+
+    public FloodlightContext parseAndAnnotate(FloodlightContext bc,
+                                              OFMessage m,
+                                              IDevice srcDevice,
+                                              IDevice dstDevice) {
+        if (OFType.PACKET_IN.equals(m.getType())) {
+            OFPacketIn pi = (OFPacketIn)m;
+            Ethernet eth = new Ethernet();
+            eth.deserialize(pi.getPacketData(), 0, pi.getPacketData().length);
+            IFloodlightProviderService.bcStore.put(bc, 
+                    IFloodlightProviderService.CONTEXT_PI_PAYLOAD, 
+                    eth);
+        }
+        if (srcDevice != null) {
+            IDeviceService.fcStore.put(bc, 
+                    IDeviceService.CONTEXT_SRC_DEVICE, 
+                    srcDevice);
+        }
+        if (dstDevice != null) {
+            IDeviceService.fcStore.put(bc, 
+                    IDeviceService.CONTEXT_DST_DEVICE, 
+                    dstDevice);
+        }
+        return bc;
+    }
+    
+    @Override
+    public void setUp() throws Exception {
+        mockFloodlightProvider = new MockFloodlightProvider();
+    }
+    
+    @Test
+    public void testSanity() throws Exception {
+    	assertTrue(true);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyInstanceTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyInstanceTest.java
new file mode 100644
index 0000000..e72179d
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyInstanceTest.java
@@ -0,0 +1,518 @@
+package net.floodlightcontroller.topology;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import static org.junit.Assert.*;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.topology.TopologyInstance;
+import net.floodlightcontroller.topology.TopologyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TopologyInstanceTest {
+    protected static Logger log = LoggerFactory.getLogger(TopologyInstanceTest.class);
+    protected TopologyManager topologyManager;
+    protected FloodlightModuleContext fmc;
+    protected MockFloodlightProvider mockFloodlightProvider;
+
+    protected int DIRECT_LINK = 1;
+    protected int MULTIHOP_LINK = 2;
+    protected int TUNNEL_LINK = 3;
+
+    @Before 
+    public void SetUp() throws Exception {
+        fmc = new FloodlightModuleContext();
+        mockFloodlightProvider = new MockFloodlightProvider();
+        fmc.addService(IFloodlightProviderService.class, mockFloodlightProvider);
+        MockThreadPoolService tp = new MockThreadPoolService();
+        topologyManager  = new TopologyManager();
+        fmc.addService(IThreadPoolService.class, tp);
+        topologyManager.init(fmc);
+        tp.init(fmc);
+        tp.startUp(fmc);
+    }
+
+    protected void verifyClusters(int[][] clusters) {
+        verifyClusters(clusters, true);
+    }
+
+    protected void verifyClusters(int[][] clusters, boolean tunnelsEnabled) {
+        List<Long> verifiedSwitches = new ArrayList<Long>();
+
+        // Make sure the expected cluster arrays are sorted so we can
+        // use binarySearch to test for membership
+        for (int i = 0; i < clusters.length; i++)
+            Arrays.sort(clusters[i]);
+
+        TopologyInstance ti = 
+                topologyManager.getCurrentInstance(tunnelsEnabled);
+        Set<Long> switches = ti.getSwitches();
+
+        for (long sw: switches) {
+            if (!verifiedSwitches.contains(sw)) {
+
+                int[] expectedCluster = null;
+
+                for (int j = 0; j < clusters.length; j++) {
+                    if (Arrays.binarySearch(clusters[j], (int) sw) >= 0) {
+                        expectedCluster = clusters[j];
+                        break;
+                    }
+                }
+                if (expectedCluster != null) {
+                    Set<Long> cluster = ti.getSwitchesInOpenflowDomain(sw);
+                    assertEquals(expectedCluster.length, cluster.size());
+                    for (long sw2: cluster) {
+                        assertTrue(Arrays.binarySearch(expectedCluster, (int)sw2) >= 0);
+                        verifiedSwitches.add(sw2);
+                    }
+                }
+            }
+        }
+    }
+
+    protected void 
+    verifyExpectedBroadcastPortsInClusters(int [][][] ebp) {
+        verifyExpectedBroadcastPortsInClusters(ebp, true);
+    }
+
+    protected void 
+    verifyExpectedBroadcastPortsInClusters(int [][][] ebp, 
+                                           boolean tunnelsEnabled) {
+        NodePortTuple npt = null;
+        Set<NodePortTuple> expected = new HashSet<NodePortTuple>();
+        for(int i=0; i<ebp.length; ++i) {
+            int [][] nptList = ebp[i];
+            expected.clear();
+            for(int j=0; j<nptList.length; ++j) {
+                npt = new NodePortTuple((long)nptList[j][0], (short)nptList[j][1]);
+                expected.add(npt);
+            }
+            TopologyInstance ti = topologyManager.getCurrentInstance(tunnelsEnabled);
+            Set<NodePortTuple> computed = ti.getBroadcastNodePortsInCluster(npt.nodeId);
+            if (computed != null)
+                assertTrue(computed.equals(expected));
+            else if (computed == null)
+                assertTrue(expected.isEmpty());
+        }
+    }
+
+    public void createTopologyFromLinks(int [][] linkArray) throws Exception {
+        ILinkDiscovery.LinkType type = ILinkDiscovery.LinkType.DIRECT_LINK;
+
+        // Use topologymanager to write this test, it will make it a lot easier.
+        for (int i = 0; i < linkArray.length; i++) {
+            int [] r = linkArray[i];
+            if (r[4] == DIRECT_LINK)
+                type= ILinkDiscovery.LinkType.DIRECT_LINK;
+            else if (r[4] == MULTIHOP_LINK)
+                type= ILinkDiscovery.LinkType.MULTIHOP_LINK;
+            else if (r[4] == TUNNEL_LINK)
+                type = ILinkDiscovery.LinkType.TUNNEL;
+
+            topologyManager.addOrUpdateLink((long)r[0], (short)r[1], (long)r[2], (short)r[3], type);
+        }
+        topologyManager.createNewInstance();
+    }
+
+    public TopologyManager getTopologyManager() {
+        return topologyManager;
+    }
+
+    @Test
+    public void testClusters() throws Exception {
+        TopologyManager tm = getTopologyManager();
+        {
+            int [][] linkArray = { 
+                                  {1, 1, 2, 1, DIRECT_LINK}, 
+                                  {2, 2, 3, 2, DIRECT_LINK},
+                                  {3, 1, 1, 2, DIRECT_LINK},
+                                  {2, 3, 4, 2, DIRECT_LINK},
+                                  {3, 3, 4, 1, DIRECT_LINK}
+            };
+            int [][] expectedClusters = {
+                                         {1,2,3}, 
+                                         {4}
+            };
+            //tm.recompute();
+            createTopologyFromLinks(linkArray);
+            verifyClusters(expectedClusters);
+        }
+
+        {
+            int [][] linkArray = { 
+                                  {5, 3, 6, 1, DIRECT_LINK} 
+            };
+            int [][] expectedClusters = {
+                                         {1,2,3}, 
+                                         {4},
+                                         {5},
+                                         {6}
+            };
+            createTopologyFromLinks(linkArray);
+
+            verifyClusters(expectedClusters);
+        }
+
+        {
+            int [][] linkArray = { 
+                                  {6, 1, 5, 3, DIRECT_LINK} 
+            };
+            int [][] expectedClusters = {
+                                         {1,2,3}, 
+                                         {4},
+                                         {5,6}
+            };
+            createTopologyFromLinks(linkArray);
+
+            verifyClusters(expectedClusters);
+        }
+
+        {
+            int [][] linkArray = { 
+                                  {4, 2, 2, 3, DIRECT_LINK} 
+            };
+            int [][] expectedClusters = {
+                                         {1,2,3,4},
+                                         {5,6}
+            };
+            createTopologyFromLinks(linkArray);
+
+            verifyClusters(expectedClusters);
+        }
+        {
+            int [][] linkArray = { 
+                                  {4, 3, 5, 1, DIRECT_LINK} 
+            };
+            int [][] expectedClusters = {
+                                         {1,2,3,4},
+                                         {5,6}
+            };
+            createTopologyFromLinks(linkArray);
+
+            verifyClusters(expectedClusters);
+        }
+        {
+            int [][] linkArray = { 
+                                  {5, 2, 2, 4, DIRECT_LINK} 
+            };
+            int [][] expectedClusters = {
+                                         {1,2,3,4,5,6}
+            };
+            createTopologyFromLinks(linkArray);
+
+            verifyClusters(expectedClusters);
+        }
+
+        //Test 2.
+        {
+            int [][] linkArray = { 
+                                  {3, 2, 2, 2, DIRECT_LINK}, 
+                                  {2, 1, 1, 1, DIRECT_LINK},
+                                  {1, 2, 3, 1, DIRECT_LINK},
+                                  {4, 1, 3, 3, DIRECT_LINK},
+                                  {5, 1, 4, 3, DIRECT_LINK},
+                                  {2, 4, 5, 2, DIRECT_LINK}
+            };
+            int [][] expectedClusters = {
+                                         {1,2,3,4,5,6}
+            };
+            createTopologyFromLinks(linkArray);
+            verifyClusters(expectedClusters);
+        }
+
+        // Test 3. Remove links
+        {
+            tm.removeLink((long)5,(short)3,(long)6,(short)1);
+            tm.removeLink((long)6,(short)1,(long)5,(short)3);
+
+            int [][] expectedClusters = {
+                                         {1,2,3,4,5},
+            };
+            topologyManager.createNewInstance();
+            verifyClusters(expectedClusters);
+        }
+
+        // Remove Switch
+        {
+            tm.removeSwitch(4);
+            int [][] expectedClusters = {
+                                         {1,2,3,5},
+            };
+            topologyManager.createNewInstance();
+            verifyClusters(expectedClusters);
+        }
+    }
+
+    @Test
+    public void testLoopDetectionInSingleIsland() throws Exception {
+
+        int [][] linkArray = {
+                              {1, 1, 2, 1, DIRECT_LINK},
+                              {2, 1, 1, 1, DIRECT_LINK},
+                              {1, 2, 3, 1, DIRECT_LINK},
+                              {3, 1, 1, 2, DIRECT_LINK},
+                              {2, 2, 3, 2, DIRECT_LINK},
+                              {3, 2, 2, 2, DIRECT_LINK},
+                              {3, 3, 4, 1, DIRECT_LINK},
+                              {4, 1, 3, 3, DIRECT_LINK},
+                              {4, 2, 6, 2, DIRECT_LINK},
+                              {6, 2, 4, 2, DIRECT_LINK},
+                              {4, 3, 5, 1, DIRECT_LINK},
+                              {5, 1, 4, 3, DIRECT_LINK},
+                              {5, 2, 6, 1, DIRECT_LINK},
+                              {6, 1, 5, 2, DIRECT_LINK},
+
+        };
+        int [][] expectedClusters = {
+                                     {1, 2, 3, 4, 5, 6}
+        };
+        int [][][] expectedBroadcastPorts = {
+                                             {{1,1}, {2,1}, {1,2}, {3,1}, {3,3}, {4,1}, {4,3}, {5,1}, {4,2}, {6,2}},
+        };
+
+        createTopologyFromLinks(linkArray);
+        topologyManager.createNewInstance();
+        verifyClusters(expectedClusters);
+        verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts);
+    }
+
+    @Test
+    public void testTunnelLinkDeletion() throws Exception {
+
+        //      +-------+             +-------+
+        //      |       |             |       |
+        //      |   1  1|-------------|1  2   |
+        //      |   2   |             |   2   |
+        //      +-------+             +-------+
+        //          |                     |
+        //          |                     |
+        //      +-------+                 |
+        //      |   1   |                 |
+        //      |   3  2|-----------------+
+        //      |   3   |
+        //      +-------+
+        //
+        //
+        //      +-------+
+        //      |   1   |
+        //      |   4  2|----------------+
+        //      |   3   |                |
+        //      +-------+                |
+        //          |                    |
+        //          |                    |
+        //      +-------+             +-------+
+        //      |   1   |             |   2   |
+        //      |   5  2|-------------|1  6   |
+        //      |       |             |       |
+        //      +-------+             +-------+
+        {
+            int [][] linkArray = {
+                                  {1, 1, 2, 1, DIRECT_LINK},
+                                  {2, 1, 1, 1, DIRECT_LINK},
+                                  {1, 2, 3, 1, TUNNEL_LINK},
+                                  {3, 1, 1, 2, TUNNEL_LINK},
+                                  {2, 2, 3, 2, TUNNEL_LINK},
+                                  {3, 2, 2, 2, TUNNEL_LINK},
+
+                                  {4, 2, 6, 2, DIRECT_LINK},
+                                  {6, 2, 4, 2, DIRECT_LINK},
+                                  {4, 3, 5, 1, TUNNEL_LINK},
+                                  {5, 1, 4, 3, TUNNEL_LINK},
+                                  {5, 2, 6, 1, TUNNEL_LINK},
+                                  {6, 1, 5, 2, TUNNEL_LINK},
+
+            };
+
+            int [][] expectedClusters = {
+                                         {1, 2},
+                                         {4, 6},
+            };
+            int [][][] expectedBroadcastPorts = {
+                                                 {{1,1}, {2,1}},
+                                                 {{4,2}, {6,2}}
+            };
+
+            createTopologyFromLinks(linkArray);
+            topologyManager.createNewInstance();
+            verifyClusters(expectedClusters, false);
+            verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts, false);
+        }
+
+        //      +-------+             +-------+
+        //      |       |    TUNNEL   |       |
+        //      |   1  1|-------------|1  2   |
+        //      |   2   |             |   2   |
+        //      +-------+             +-------+
+        //          |                     |
+        //          |                     |
+        //      +-------+                 |
+        //      |   1   |                 |
+        //      |   3  2|-----------------+
+        //      |   3   |
+        //      +-------+
+        //          | 
+        //          |   TUNNEL
+        //          |
+        //      +-------+
+        //      |   1   |    TUNNEL
+        //      |   4  2|----------------+
+        //      |   3   |                |
+        //      +-------+                |
+        //          |                    |
+        //          |                    |
+        //      +-------+             +-------+
+        //      |   1   |             |   2   |
+        //      |   5  2|-------------|1  6   |
+        //      |       |             |       |
+        //      +-------+             +-------+
+
+        {
+            int [][] linkArray = {
+                                  {3, 3, 4, 1, TUNNEL_LINK},
+                                  {4, 1, 3, 3, TUNNEL_LINK},
+
+            };
+            int [][] expectedClusters = {
+                                         {1, 2},
+                                         {4, 6},
+                                         {3},
+                                         {5},
+            };
+            int [][][] expectedBroadcastPorts = {
+                                                 {{1,1}, {2,1}},
+                                                 {{4,2}, {6,2}}
+            };
+
+            createTopologyFromLinks(linkArray);
+            topologyManager.createNewInstance();
+            verifyClusters(expectedClusters, false);
+            verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts, false);
+        }
+    }
+
+    @Test
+    public void testLoopDetectionWithIslands() throws Exception {
+
+        //      +-------+             +-------+
+        //      |       |   TUNNEL    |       |
+        //      |   1  1|-------------|1  2   |
+        //      |   2   |             |   2   |
+        //      +-------+             +-------+
+        //          |                     |
+        //          |                     |
+        //      +-------+                 |
+        //      |   1   |                 |
+        //      |   3  2|-----------------+
+        //      |   3   |
+        //      +-------+
+        //
+        //
+        //      +-------+
+        //      |   1   |   TUNNEL
+        //      |   4  2|----------------+
+        //      |   3   |                |
+        //      +-------+                |
+        //          |                    |
+        //          |                    |
+        //      +-------+             +-------+
+        //      |   1   |             |   2   |
+        //      |   5  2|-------------|1  6   |
+        //      |       |             |       |
+        //      +-------+             +-------+
+        {
+            int [][] linkArray = {
+                                  {1, 1, 2, 1, TUNNEL_LINK},
+                                  {2, 1, 1, 1, TUNNEL_LINK},
+                                  {1, 2, 3, 1, DIRECT_LINK},
+                                  {3, 1, 1, 2, DIRECT_LINK},
+                                  {2, 2, 3, 2, DIRECT_LINK},
+                                  {3, 2, 2, 2, DIRECT_LINK},
+
+                                  {4, 2, 6, 2, TUNNEL_LINK},
+                                  {6, 2, 4, 2, TUNNEL_LINK},
+                                  {4, 3, 5, 1, DIRECT_LINK},
+                                  {5, 1, 4, 3, DIRECT_LINK},
+                                  {5, 2, 6, 1, DIRECT_LINK},
+                                  {6, 1, 5, 2, DIRECT_LINK},
+
+            };
+
+            int [][] expectedClusters = {
+                                         {1, 2, 3}, 
+                                         {4, 5, 6}
+            };
+            int [][][] expectedBroadcastPorts = {
+                                                 {{1,2}, {3,1}, {2,2}, {3,2}},
+                                                 {{4,3}, {5,1}, {5,2}, {6,1}},
+            };
+
+            createTopologyFromLinks(linkArray);
+            topologyManager.createNewInstance();
+            verifyClusters(expectedClusters);
+            verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts);
+        }
+
+        //      +-------+             +-------+
+        //      |       |    TUNNEL   |       |
+        //      |   1  1|-------------|1  2   |
+        //      |   2   |             |   2   |
+        //      +-------+             +-------+
+        //          |                     |
+        //          |                     |
+        //      +-------+                 |
+        //      |   1   |                 |
+        //      |   3  2|-----------------+
+        //      |   3   |
+        //      +-------+
+        //          | 
+        //          |   TUNNEL
+        //          |
+        //      +-------+
+        //      |   1   |    TUNNEL
+        //      |   4  2|----------------+
+        //      |   3   |                |
+        //      +-------+                |
+        //          |                    |
+        //          |                    |
+        //      +-------+             +-------+
+        //      |   1   |             |   2   |
+        //      |   5  2|-------------|1  6   |
+        //      |       |             |       |
+        //      +-------+             +-------+
+
+        {
+            int [][] linkArray = {
+                                  {3, 3, 4, 1, TUNNEL_LINK},
+                                  {4, 1, 3, 3, TUNNEL_LINK},
+
+            };
+            int [][] expectedClusters = {
+                                         {1, 2, 3}, 
+                                         {4, 5, 6}
+            };
+            int [][][] expectedBroadcastPorts = {
+                                                 {{1,2}, {3,1}, {2,2}, {3,2}},
+                                                 {{4,3}, {5,1}, {5,2}, {6,1}},
+            };
+
+            createTopologyFromLinks(linkArray);
+            topologyManager.createNewInstance();
+            verifyClusters(expectedClusters, false);
+            verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts);
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyManagerTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyManagerTest.java
new file mode 100644
index 0000000..113cecb
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyManagerTest.java
@@ -0,0 +1,142 @@
+package net.floodlightcontroller.topology;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.TopologyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TopologyManagerTest extends FloodlightTestCase {
+    protected static Logger log = LoggerFactory.getLogger(TopologyManagerTest.class);
+    TopologyManager tm;
+    FloodlightModuleContext fmc;
+    
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        fmc = new FloodlightModuleContext();
+        fmc.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
+        MockThreadPoolService tp = new MockThreadPoolService();
+        fmc.addService(IThreadPoolService.class, tp);
+        tm  = new TopologyManager();
+        tp.init(fmc);
+        tm.init(fmc);
+        tp.startUp(fmc);
+    }
+
+    @Test
+    public void testBasic1() throws Exception {
+        tm.addOrUpdateLink((long)1, (short)1, (long)2, (short)1, ILinkDiscovery.LinkType.DIRECT_LINK);
+        assertTrue(tm.getSwitchPorts().size() == 2);  // for two nodes.
+        assertTrue(tm.getSwitchPorts().get((long)1).size()==1);
+        assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+        assertTrue(tm.getSwitchPortLinks().size()==2);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+        assertTrue(tm.getTunnelPorts().size()==0);
+
+        tm.addOrUpdateLink((long)1, (short)2, (long)2, (short)2, ILinkDiscovery.LinkType.MULTIHOP_LINK);
+        assertTrue(tm.getSwitchPorts().size() == 2);  // for two nodes.
+        assertTrue(tm.getSwitchPorts().get((long)1).size()==2);
+        assertTrue(tm.getSwitchPorts().get((long)2).size()==2);
+        assertTrue(tm.getSwitchPortLinks().size()==4);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+        assertTrue(tm.getTunnelPorts().size()==0);
+
+        tm.addOrUpdateLink((long)1, (short)3, (long)2, (short)3, ILinkDiscovery.LinkType.TUNNEL);
+        assertTrue(tm.getSwitchPorts().size() == 2);  // for two nodes.
+        assertTrue(tm.getSwitchPorts().get((long)1).size()==3);
+        assertTrue(tm.getSwitchPorts().get((long)2).size()==3);
+        assertTrue(tm.getSwitchPortLinks().size()==6);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+        assertTrue(tm.getTunnelPorts().size()==2);
+
+        tm.removeLink((long)1, (short)2, (long)2, (short)2);
+        log.info("# of switchports. {}", tm.getSwitchPorts().get((long)1).size());
+        assertTrue(tm.getSwitchPorts().get((long)1).size()==2);
+        assertTrue(tm.getSwitchPorts().get((long)2).size()==2);
+        assertTrue(tm.getSwitchPorts().size() == 2);  // for two nodes.
+        assertTrue(tm.getSwitchPortLinks().size()==4);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+        assertTrue(tm.getTunnelPorts().size()==2);
+
+        tm.removeLink((long)1, (short)1, (long)2, (short)1);
+        assertTrue(tm.getSwitchPorts().size() == 2);  // for two nodes.
+        assertTrue(tm.getSwitchPorts().get((long)1).size()==1);
+        assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+        assertTrue(tm.getSwitchPortLinks().size()==2);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+        assertTrue(tm.getTunnelPorts().size()==2);
+
+        tm.removeLink((long)1, (short)3, (long)2, (short)3);
+        assertTrue(tm.getSwitchPorts().size() == 0); 
+        assertTrue(tm.getSwitchPortLinks().size()==0);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+        assertTrue(tm.getTunnelPorts().size()==0);
+    }
+
+    @Test
+    public void testBasic2() throws Exception {
+        tm.addOrUpdateLink((long)1, (short)1, (long)2, (short)1, ILinkDiscovery.LinkType.DIRECT_LINK);
+        tm.addOrUpdateLink((long)2, (short)2, (long)3, (short)1, ILinkDiscovery.LinkType.MULTIHOP_LINK);
+        tm.addOrUpdateLink((long)3, (short)2, (long)1, (short)2, ILinkDiscovery.LinkType.TUNNEL);
+        assertTrue(tm.getSwitchPorts().size() == 3);  // for two nodes.
+        assertTrue(tm.getSwitchPorts().get((long)1).size()==2);
+        assertTrue(tm.getSwitchPorts().get((long)2).size()==2);
+        assertTrue(tm.getSwitchPorts().get((long)3).size()==2);
+        assertTrue(tm.getSwitchPortLinks().size()==6);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+        assertTrue(tm.getTunnelPorts().size()==2);
+
+        tm.removeLink((long)1, (short)1, (long)2, (short)1);
+        assertTrue(tm.getSwitchPorts().size() == 3);  // for two nodes.
+        assertTrue(tm.getSwitchPorts().get((long)1).size()==1);
+        assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+        assertTrue(tm.getSwitchPorts().get((long)3).size()==2);
+        assertTrue(tm.getSwitchPortLinks().size()==4);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+        assertTrue(tm.getTunnelPorts().size()==2);
+
+        // nonexistent link // no null pointer exceptions.
+        tm.removeLink((long)3, (short)1, (long)2, (short)2);
+        assertTrue(tm.getSwitchPorts().size() == 3);  // for two nodes.
+        assertTrue(tm.getSwitchPorts().get((long)1).size()==1);
+        assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+        assertTrue(tm.getSwitchPorts().get((long)3).size()==2);
+        assertTrue(tm.getSwitchPortLinks().size()==4);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+        assertTrue(tm.getTunnelPorts().size()==2);
+
+        tm.removeLink((long)3, (short)2, (long)1, (short)2);
+        assertTrue(tm.getSwitchPorts().size() == 2);  // for two nodes.
+        assertTrue(tm.getSwitchPorts().get((long)1)==null);
+        assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+        assertTrue(tm.getSwitchPorts().get((long)3).size()==1);
+        assertTrue(tm.getSwitchPortLinks().size()==2);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+        assertTrue(tm.getTunnelPorts().size()==0);
+
+        tm.removeLink((long)2, (short)2, (long)3, (short)1);
+        assertTrue(tm.getSwitchPorts().size() == 0);  // for two nodes.
+        assertTrue(tm.getSwitchPortLinks().size()==0);
+        assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+        assertTrue(tm.getTunnelPorts().size()==0);
+    }
+
+    @Test
+    public void testHARoleChange() throws Exception {
+        testBasic2();
+        getMockFloodlightProvider().dispatchRoleChanged(null, Role.SLAVE);
+        assert(tm.switchPorts.isEmpty());
+        assert(tm.switchPortLinks.isEmpty());
+        assert(tm.portBroadcastDomainLinks.isEmpty());
+        assert(tm.tunnelLinks.isEmpty());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/MACAddressTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/MACAddressTest.java
new file mode 100644
index 0000000..5669614
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/MACAddressTest.java
@@ -0,0 +1,85 @@
+package net.floodlightcontroller.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sho Shimizu (sho.shimizu@gmail.com)
+ */
+public class MACAddressTest {
+    @Test
+    public void testValueOf() {
+        MACAddress address = MACAddress.valueOf("00:01:02:03:04:05");
+        assertEquals(address,
+                MACAddress.valueOf(new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}));
+        assertEquals("00:01:02:03:04:05", address.toString());
+
+        address = MACAddress.valueOf("FF:FE:FD:10:20:30");
+        assertEquals(address,
+                MACAddress.valueOf(new byte[]{(byte) 0xFF, (byte) 0xFE, (byte) 0xFD, 0x10, 0x20, 0x30}));
+        assertEquals("FF:FE:FD:10:20:30", address.toString());
+        
+        address = MACAddress.valueOf("00:11:22:aa:bb:cc");
+        assertEquals(address,
+                MACAddress.valueOf(new byte[]{0x00, 0x11, 0x22, (byte)0xaa, (byte)0xbb, (byte)0xcc}));
+    }
+
+    @Test(expected=NumberFormatException.class)
+    public void testIllegalFormat() {
+        MACAddress.valueOf("0T:00:01:02:03:04");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testLongStringFields() {
+        MACAddress.valueOf("00:01:02:03:04:05:06");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testShortStringFields() {
+        MACAddress.valueOf("00:01:02:03:04");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testLongByteFields() {
+        MACAddress.valueOf(new byte[]{0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testShortByteField() {
+        MACAddress.valueOf(new byte[]{0x01, 0x01, 0x02, 0x03, 0x04});
+    }
+
+    //  Test data is imported from net.floodlightcontroller.packet.EthernetTest
+    @Test
+    public void testToLong() {
+        assertEquals(
+                281474976710655L,
+                MACAddress.valueOf(new byte[]{(byte) 0xff, (byte) 0xff,
+                        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}).toLong());
+
+        assertEquals(
+                1103823438081L,
+                MACAddress.valueOf(new byte[] { (byte) 0x01, (byte) 0x01,
+                        (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01 }).toLong());
+
+        assertEquals(
+                141289400074368L,
+                MACAddress.valueOf(new byte[] { (byte) 0x80, (byte) 0x80,
+                        (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80 }).toLong());
+
+    }
+    
+    @Test
+    public void testIsBroadcast() {
+        assertTrue(MACAddress.valueOf("FF:FF:FF:FF:FF:FF").isBroadcast());
+        assertFalse(MACAddress.valueOf("11:22:33:44:55:66").isBroadcast());
+    }
+
+    @Test
+    public void testIsMulticast() {
+        assertTrue(MACAddress.valueOf("01:80:C2:00:00:00").isMulticast());
+        assertFalse(MACAddress.valueOf("FF:FF:FF:FF:FF:FF").isMulticast());
+        assertFalse(MACAddress.valueOf("11:22:33:44:55:66").isBroadcast());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java
new file mode 100644
index 0000000..59e0a51
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java
@@ -0,0 +1,349 @@
+package net.floodlightcontroller.util;
+
+import static org.junit.Assert.*;
+import java.io.IOException;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+
+import org.jboss.netty.channel.Channel;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFStatistics;
+
+
+/**
+ * A mock implementation of IFOSwitch we use for {@link OFMessageDamper}
+ * 
+ * We need to mock equals() and hashCode() but alas, EasyMock doesn't support
+ * this. Sigh. And of course this happens to be the interface with the most
+ * methods. 
+ * @author gregor
+ *
+ */
+public class OFMessageDamperMockSwitch implements IOFSwitch {
+    OFMessage writtenMessage;
+    FloodlightContext writtenContext;
+    
+    public OFMessageDamperMockSwitch() {
+        reset();
+    }
+    
+    /* reset this mock. I.e., clear the stored message previously written */
+    public void reset() {
+        writtenMessage = null;
+        writtenContext = null;
+    }
+    
+    /* assert that a message was written to this switch and that the 
+     * written message and context matches the expected values 
+     * @param expected
+     * @param expectedContext
+     */
+    public void assertMessageWasWritten(OFMessage expected, 
+                                        FloodlightContext expectedContext) {
+        assertNotNull("No OFMessage was written", writtenMessage);
+        assertEquals(expected, writtenMessage);
+        assertEquals(expectedContext, writtenContext);
+    }
+    
+    /*
+     * assert that no message was written 
+     */
+    public void assertNoMessageWritten() {
+        assertNull("OFMessage was written but didn't expect one", 
+                      writtenMessage);
+        assertNull("There was a context but didn't expect one", 
+                      writtenContext);
+    }
+    
+    /*
+     * use hashCode() and equals() from Object
+     */
+    
+    
+    //-------------------------------------------------------
+    // IOFSwitch: mocked methods
+    @Override
+    public void write(OFMessage m, FloodlightContext bc) throws IOException {
+        assertNull("write() called but already have message", writtenMessage);
+        assertNull("write() called but already have context", writtenContext);
+        writtenContext = bc;
+        writtenMessage = m;
+    }
+    
+    //-------------------------------------------------------
+    // IOFSwitch: not-implemented methods
+    @Override
+    public void write(List<OFMessage> msglist, FloodlightContext bc) 
+            throws IOException {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public void disconnectOutputStream() {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public Channel getChannel() {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public void setFeaturesReply(OFFeaturesReply featuresReply) {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public void setSwitchProperties(OFDescriptionStatistics description) {
+        assertTrue("Unexpected method call", false);
+        // TODO Auto-generated method stub
+    }
+    
+    @Override
+    public Collection<OFPhysicalPort> getEnabledPorts() {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public Collection<Short> getEnabledPortNumbers() {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public OFPhysicalPort getPort(short portNumber) {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public OFPhysicalPort getPort(String portName) {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public void setPort(OFPhysicalPort port) {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public void deletePort(short portNumber) {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public void deletePort(String portName) {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public Collection<OFPhysicalPort> getPorts() {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public boolean portEnabled(short portName) {
+        assertTrue("Unexpected method call", false);
+        return false;
+    }
+    
+    @Override
+    public boolean portEnabled(String portName) {
+        assertTrue("Unexpected method call", false);
+        return false;
+    }
+    
+    @Override
+    public boolean portEnabled(OFPhysicalPort port) {
+        assertTrue("Unexpected method call", false);
+        return false;
+    }
+    
+    @Override
+    public long getId() {
+        assertTrue("Unexpected method call", false);
+        return 0;
+    }
+    
+    @Override
+    public String getStringId() {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public Map<Object, Object> getAttributes() {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public Date getConnectedSince() {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public int getNextTransactionId() {
+        assertTrue("Unexpected method call", false);
+        return 0;
+    }
+    
+    @Override
+    public Future<List<OFStatistics>>
+            getStatistics(OFStatisticsRequest request) throws IOException {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public boolean isConnected() {
+        assertTrue("Unexpected method call", false);
+        return false;
+    }
+    
+    @Override
+    public void setConnected(boolean connected) {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public Role getRole() {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public boolean isActive() {
+        assertTrue("Unexpected method call", false);
+        return false;
+    }
+    
+    @Override
+    public void deliverStatisticsReply(OFMessage reply) {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public void cancelStatisticsReply(int transactionId) {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public void cancelAllStatisticsReplies() {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public boolean hasAttribute(String name) {
+        assertTrue("Unexpected method call", false);
+        return false;
+    }
+    
+    @Override
+    public Object getAttribute(String name) {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public void setAttribute(String name, Object value) {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public Object removeAttribute(String name) {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public void clearAllFlowMods() {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public boolean updateBroadcastCache(Long entry, Short port) {
+        assertTrue("Unexpected method call", false);
+        return false;
+    }
+    
+    @Override
+    public Map<Short, Long> getPortBroadcastHits() {
+        assertTrue("Unexpected method call", false);
+        return null;
+    }
+    
+    @Override
+    public void sendStatsQuery(OFStatisticsRequest request, int xid,
+                               IOFMessageListener caller)
+                                                         throws IOException {
+        assertTrue("Unexpected method call", false);
+    }
+    
+    @Override
+    public void flush() {
+        assertTrue("Unexpected method call", false);
+    }
+
+    @Override
+    public Future<OFFeaturesReply> getFeaturesReplyFromSwitch()
+            throws IOException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void deliverOFFeaturesReply(OFMessage reply) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void cancelFeaturesReply(int transactionId) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public int getBuffers() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public int getActions() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public int getCapabilities() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public byte getTables() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java
new file mode 100644
index 0000000..8cad756
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java
@@ -0,0 +1,150 @@
+package net.floodlightcontroller.util;
+
+import static org.junit.Assert.*;
+
+import net.floodlightcontroller.core.FloodlightContext;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFEchoRequest;
+import org.openflow.protocol.OFHello;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.OFMessageFactory;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+public class OFMessageDamperTest {
+    OFMessageFactory factory;
+    OFMessageDamper damper;
+    FloodlightContext cntx;
+    
+    OFMessageDamperMockSwitch sw1;
+    OFMessageDamperMockSwitch sw2;
+    
+    OFEchoRequest echoRequst1;
+    OFEchoRequest echoRequst1Clone;
+    OFEchoRequest echoRequst2;
+    OFHello hello1;
+    OFHello hello2;
+    
+    
+    
+    @Before
+    public void setUp() throws IOException {
+        factory = new BasicFactory();
+        cntx = new FloodlightContext();
+        
+        sw1 = new OFMessageDamperMockSwitch();
+        sw2 = new OFMessageDamperMockSwitch();
+        
+        echoRequst1 = (OFEchoRequest)factory.getMessage(OFType.ECHO_REQUEST);
+        echoRequst1.setPayload(new byte[] { 1 });
+        echoRequst1Clone = (OFEchoRequest)
+                factory.getMessage(OFType.ECHO_REQUEST);
+        echoRequst1Clone.setPayload(new byte[] { 1 });
+        echoRequst2 = (OFEchoRequest)factory.getMessage(OFType.ECHO_REQUEST);
+        echoRequst2.setPayload(new byte[] { 2 });
+        
+        hello1 = (OFHello)factory.getMessage(OFType.HELLO);
+        hello1.setXid(1);
+        hello2 = (OFHello)factory.getMessage(OFType.HELLO);
+        hello2.setXid(2);
+        
+    }
+    
+    protected void doWrite(boolean expectWrite, 
+                           OFMessageDamperMockSwitch sw, 
+                           OFMessage msg,
+                           FloodlightContext cntx) throws IOException {
+        
+        boolean result;
+        sw.reset();
+        result = damper.write(sw, msg, cntx);
+        
+        if (expectWrite) {
+            assertEquals(true, result);
+            sw.assertMessageWasWritten(msg, cntx);
+        } else {
+            assertEquals(false, result);
+            sw.assertNoMessageWritten();
+        }
+    }
+    
+    
+    @Test
+    public void testOneMessageType() throws IOException, InterruptedException {
+        int timeout = 50;
+        int sleepTime = 60; 
+        damper = new OFMessageDamper(100, 
+                                     EnumSet.of(OFType.ECHO_REQUEST),
+                                     timeout);
+        
+        
+        
+        // echo requests should be dampened 
+        doWrite(true, sw1, echoRequst1, cntx);
+        doWrite(false, sw1, echoRequst1, cntx);
+        doWrite(false, sw1, echoRequst1Clone, cntx);
+        doWrite(true, sw1, echoRequst2, cntx);
+        doWrite(false, sw1, echoRequst2, cntx);
+        
+        // we don't dampen hellos. All should succeed 
+        doWrite(true, sw1, hello1, cntx);
+        doWrite(true, sw1, hello1, cntx);
+        doWrite(true, sw1, hello1, cntx);
+        
+        // echo request should also be dampened on sw2
+        doWrite(true, sw2, echoRequst1, cntx);
+        doWrite(false, sw2, echoRequst1, cntx);
+        doWrite(true, sw2, echoRequst2, cntx);
+        
+        
+        Thread.sleep(sleepTime);
+        doWrite(true, sw1, echoRequst1, cntx);
+        doWrite(true, sw2, echoRequst1, cntx);
+        
+    }
+    
+    @Test
+    public void testTwoMessageTypes() throws IOException, InterruptedException {
+        int timeout = 50;
+        int sleepTime = 60; 
+        damper = new OFMessageDamper(100, 
+                                     EnumSet.of(OFType.ECHO_REQUEST, 
+                                                OFType.HELLO),
+                                     timeout);
+        
+        
+        
+        // echo requests should be dampened 
+        doWrite(true, sw1, echoRequst1, cntx);
+        doWrite(false, sw1, echoRequst1, cntx);
+        doWrite(false, sw1, echoRequst1Clone, cntx);
+        doWrite(true, sw1, echoRequst2, cntx);
+        doWrite(false, sw1, echoRequst2, cntx);
+        
+        // hello should be dampened as well
+        doWrite(true, sw1, hello1, cntx);
+        doWrite(false, sw1, hello1, cntx);
+        doWrite(false, sw1, hello1, cntx);
+        
+        doWrite(true, sw1, hello2, cntx);
+        doWrite(false, sw1, hello2, cntx);
+        doWrite(false, sw1, hello2, cntx);
+        
+        // echo request should also be dampened on sw2
+        doWrite(true, sw2, echoRequst1, cntx);
+        doWrite(false, sw2, echoRequst1, cntx);
+        doWrite(true, sw2, echoRequst2, cntx);
+        
+        Thread.sleep(sleepTime);
+        doWrite(true, sw1, echoRequst1, cntx);
+        doWrite(true, sw2, echoRequst1, cntx);
+        doWrite(true, sw1, hello1, cntx);
+        doWrite(true, sw1, hello2, cntx);
+    }
+    
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/TimedCacheTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/TimedCacheTest.java
new file mode 100644
index 0000000..5e70641
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/TimedCacheTest.java
@@ -0,0 +1,90 @@
+package net.floodlightcontroller.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TimedCacheTest {
+    public static class CacheEntry {
+        public int key;
+        
+        public CacheEntry(int key) {
+            this.key = key;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + key;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            CacheEntry other = (CacheEntry) obj;
+            if (key != other.key)
+                return false;
+            return true;
+        }
+    }
+    
+    protected TimedCache<CacheEntry> cache;
+    
+    @Before
+    public void setUp() {
+        // 
+    }
+    
+    
+    @Test
+    public void testCaching() throws InterruptedException {
+        int timeout = 50;
+        int timeToSleep = 60;
+        cache = new TimedCache<TimedCacheTest.CacheEntry>(100, timeout);
+        
+        CacheEntry e1a = new CacheEntry(1);
+        CacheEntry e1b = new CacheEntry(1);
+        CacheEntry e1c = new CacheEntry(1);
+        CacheEntry e2 = new CacheEntry(2);
+        
+        assertEquals(false, cache.update(e1a));
+        assertEquals(true, cache.update(e1a));
+        assertEquals(true, cache.update(e1b));
+        assertEquals(true, cache.update(e1c));
+        assertEquals(false, cache.update(e2));
+        assertEquals(true, cache.update(e2));
+        
+        Thread.sleep(timeToSleep);
+        assertEquals(false, cache.update(e1a));
+        assertEquals(false, cache.update(e2));
+    }
+    
+    @Test
+    public void testCapacity() throws InterruptedException {
+        int timeout = 5000;
+        cache = new TimedCache<TimedCacheTest.CacheEntry>(2, timeout);
+        
+        // Testing the capacity is tricky since the capacity can be 
+        // exceeded for short amounts of time, so we try to flood the cache
+        // to make sure the first entry is expired
+        CacheEntry e1 = new CacheEntry(1);
+        for (int i=0; i < 100; i++) {
+            CacheEntry e = new CacheEntry(i);
+            cache.update(e);
+        }
+        
+        // entry 1 should have been expired due to capacity limits 
+        assertEquals(false, cache.update(e1));
+    }
+    
+    
+    
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java
new file mode 100644
index 0000000..32afe4e
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java
@@ -0,0 +1,375 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.List;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IListener.Command;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.core.test.PacketFactory;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.test.MockDeviceManager;
+import net.floodlightcontroller.flowcache.FlowReconcileManager;
+import net.floodlightcontroller.flowcache.IFlowReconcileService;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.util.MACAddress;
+import net.floodlightcontroller.virtualnetwork.VirtualNetworkFilter;
+
+public class VirtualNetworkFilterTest extends FloodlightTestCase {
+    protected VirtualNetworkFilter vns;
+    protected MockDeviceManager deviceService;
+    
+    protected static String guid1 = "guid1";
+    protected static String net1 = "net1";
+    protected static String gw1 = "1.1.1.1";
+    protected static String guid2 = "guid2";
+    protected static String net2 = "net2";
+    protected static String guid3 = "guid3";
+    protected static String net3 = "net3";
+    protected static String gw2 = "2.2.2.2";
+    
+    protected static MACAddress mac1 = 
+            new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:55"));
+    protected static MACAddress mac2 = 
+            new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:66"));
+    protected static MACAddress mac3 = 
+            new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:77"));
+    protected static MACAddress mac4 = 
+            new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:88"));
+    protected static String hostPort1 = "port1";
+    protected static String hostPort2 = "port2";
+    protected static String hostPort3 = "port3";
+    protected static String hostPort4 = "port4";
+    
+    // For testing forwarding behavior
+    protected IOFSwitch sw1;
+    protected FloodlightContext cntx;
+    protected OFPacketIn mac1ToMac2PacketIn;
+    protected IPacket mac1ToMac2PacketIntestPacket;
+    protected byte[] mac1ToMac2PacketIntestPacketSerialized;
+    protected OFPacketIn mac1ToMac4PacketIn;
+    protected IPacket mac1ToMac4PacketIntestPacket;
+    protected byte[] mac1ToMac4PacketIntestPacketSerialized;
+    protected OFPacketIn mac1ToGwPacketIn;
+    protected IPacket mac1ToGwPacketIntestPacket;
+    protected byte[] mac1ToGwPacketIntestPacketSerialized;
+    protected OFPacketIn packetInDHCPDiscoveryRequest;
+    
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Module loading stuff
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        RestApiServer restApi = new RestApiServer();
+        deviceService = new MockDeviceManager();
+        FlowReconcileManager frm = new FlowReconcileManager();
+        MockThreadPoolService tps = new MockThreadPoolService();
+        ITopologyService topology = createMock(ITopologyService.class);
+        vns = new VirtualNetworkFilter();
+        DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier();
+        fmc.addService(IRestApiService.class, restApi);
+        fmc.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
+        fmc.addService(IDeviceService.class, deviceService);
+        fmc.addService(IFlowReconcileService.class, frm);
+        fmc.addService(IThreadPoolService.class, tps);
+        fmc.addService(IEntityClassifierService.class, entityClassifier);
+        fmc.addService(ITopologyService.class, topology);
+        tps.init(fmc);
+        frm.init(fmc);
+        deviceService.init(fmc);
+        restApi.init(fmc);
+        getMockFloodlightProvider().init(fmc);
+        entityClassifier.init(fmc);
+        tps.startUp(fmc);
+        vns.init(fmc);
+        frm.startUp(fmc);
+        deviceService.startUp(fmc);
+        restApi.startUp(fmc);
+        getMockFloodlightProvider().startUp(fmc);
+        vns.startUp(fmc);
+        entityClassifier.startUp(fmc);
+
+        topology.addListener(deviceService);
+        expectLastCall().times(1);
+        replay(topology);
+        // Mock switches
+        //fastWilcards mocked as this constant
+        int fastWildcards = 
+                OFMatch.OFPFW_IN_PORT | 
+                OFMatch.OFPFW_NW_PROTO | 
+                OFMatch.OFPFW_TP_SRC | 
+                OFMatch.OFPFW_TP_DST | 
+                OFMatch.OFPFW_NW_SRC_ALL | 
+                OFMatch.OFPFW_NW_DST_ALL |
+                OFMatch.OFPFW_NW_TOS;
+        sw1 = EasyMock.createNiceMock(IOFSwitch.class);
+        expect(sw1.getId()).andReturn(1L).anyTimes();
+        expect(sw1.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer)fastWildcards).anyTimes();
+        expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes();
+        replay(sw1);
+        
+        // Mock packets
+        // Mock from MAC1 -> MAC2
+        mac1ToMac2PacketIntestPacket = new Ethernet()
+            .setDestinationMACAddress(mac2.toBytes())
+            .setSourceMACAddress(mac1.toBytes())
+            .setEtherType(Ethernet.TYPE_IPv4)
+            .setPayload(
+                new IPv4()
+                .setTtl((byte) 128)
+                .setSourceAddress("192.168.1.1")
+                .setDestinationAddress("192.168.1.2")
+                .setPayload(new UDP()
+                            .setSourcePort((short) 5000)
+                            .setDestinationPort((short) 5001)
+                            .setPayload(new Data(new byte[] {0x01}))));
+        mac1ToMac2PacketIntestPacketSerialized = mac1ToMac2PacketIntestPacket.serialize();
+        mac1ToMac2PacketIn = 
+                ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().
+                        getMessage(OFType.PACKET_IN))
+                        .setBufferId(-1)
+                        .setInPort((short) 1)
+                        .setPacketData(mac1ToMac2PacketIntestPacketSerialized)
+                        .setReason(OFPacketInReason.NO_MATCH)
+                        .setTotalLength((short) mac1ToMac2PacketIntestPacketSerialized.length);
+        
+        // Mock from MAC1 -> MAC4
+        mac1ToMac4PacketIntestPacket = new Ethernet()
+        .setDestinationMACAddress(mac4.toBytes())
+        .setSourceMACAddress(mac1.toBytes())
+        .setEtherType(Ethernet.TYPE_IPv4)
+        .setPayload(
+            new IPv4()
+            .setTtl((byte) 128)
+            .setSourceAddress("192.168.1.1")
+            .setDestinationAddress("192.168.1.2")
+            .setPayload(new UDP()
+                        .setSourcePort((short) 5000)
+                        .setDestinationPort((short) 5001)
+                        .setPayload(new Data(new byte[] {0x01}))));
+        mac1ToMac4PacketIntestPacketSerialized = mac1ToMac4PacketIntestPacket.serialize();
+        mac1ToMac4PacketIn = 
+            ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().
+                    getMessage(OFType.PACKET_IN))
+                    .setBufferId(-1)
+                    .setInPort((short) 1)
+                    .setPacketData(mac1ToMac4PacketIntestPacketSerialized)
+                    .setReason(OFPacketInReason.NO_MATCH)
+                    .setTotalLength((short) mac1ToMac4PacketIntestPacketSerialized.length);
+        
+        // Mock from MAC1 to gateway1
+        mac1ToGwPacketIntestPacket = new Ethernet()
+        .setDestinationMACAddress("00:11:33:33:44:55") // mac shouldn't matter, can't be other host
+        .setSourceMACAddress(mac1.toBytes())
+        .setEtherType(Ethernet.TYPE_IPv4)
+        .setPayload(
+            new IPv4()
+            .setTtl((byte) 128)
+            .setSourceAddress("192.168.1.1")
+            .setDestinationAddress(gw1)
+            .setPayload(new UDP()
+                        .setSourcePort((short) 5000)
+                        .setDestinationPort((short) 5001)
+                        .setPayload(new Data(new byte[] {0x01}))));
+        mac1ToGwPacketIntestPacketSerialized = mac1ToGwPacketIntestPacket.serialize();
+        mac1ToGwPacketIn = 
+            ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().
+                    getMessage(OFType.PACKET_IN))
+                    .setBufferId(-1)
+                    .setInPort((short) 1)
+                    .setPacketData(mac1ToGwPacketIntestPacketSerialized)
+                    .setReason(OFPacketInReason.NO_MATCH)
+                    .setTotalLength((short) mac1ToGwPacketIntestPacketSerialized.length);
+    }
+    
+    @Test
+    public void testCreateNetwork() {
+        // Test creating a network with all parameters
+        vns.createNetwork(guid1, net1, IPv4.toIPv4Address(gw1));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1));
+        assertTrue(vns.nameToGuid.get(net1).equals(guid1));
+        assertTrue(vns.guidToGateway.get(guid1).equals(IPv4.toIPv4Address(gw1)));
+        assertTrue(vns.vNetsByGuid.get(guid1).name.equals(net1));
+        assertTrue(vns.vNetsByGuid.get(guid1).guid.equals(guid1));
+        assertTrue(vns.vNetsByGuid.get(guid1).gateway.equals(gw1));
+        assertTrue(vns.vNetsByGuid.get(guid1).hosts.size()==0);
+
+        // Test creating network without a gateway
+        vns.createNetwork(guid2, net2, null);
+        assertTrue(vns.nameToGuid.get(net2).equals(guid2));
+        assertTrue(vns.guidToGateway.get(guid2) == null);
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 1);
+        assertTrue(vns.vNetsByGuid.get(guid2).name.equals(net2));
+        assertTrue(vns.vNetsByGuid.get(guid2).guid.equals(guid2));
+        assertTrue(vns.vNetsByGuid.get(guid2).gateway == null);
+        assertTrue(vns.vNetsByGuid.get(guid2).hosts.size()==0);
+        
+        // Test creating a network that shares the gateway with net1
+        vns.createNetwork(guid3, net3, IPv4.toIPv4Address(gw1));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid3));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 2);
+        assertTrue(vns.nameToGuid.get(net3).equals(guid3));
+        assertTrue(vns.guidToGateway.get(guid3).equals(IPv4.toIPv4Address(gw1)));
+        assertTrue(vns.vNetsByGuid.get(guid3).name.equals(net3));
+        assertTrue(vns.vNetsByGuid.get(guid3).guid.equals(guid3));
+        assertTrue(vns.vNetsByGuid.get(guid3).gateway.equals(gw1));
+        assertTrue(vns.vNetsByGuid.get(guid3).hosts.size()==0);
+
+    }
+    
+    @Test
+    public void testModifyNetwork() {
+        // Create some networks
+        
+        testCreateNetwork();
+        // Modify net2 to add a gateway
+        vns.createNetwork(guid2, net2, IPv4.toIPv4Address(gw1));
+        assertTrue(vns.nameToGuid.get(net2).equals(guid2));
+        assertTrue(vns.guidToGateway.get(guid2).equals(IPv4.toIPv4Address(gw1)));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid2));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid3));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 3);
+        // Modify net2 to change it's name
+        vns.createNetwork(guid2, "newnet2", null);
+        // Make sure the gateway is still there
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid2));
+        assertTrue(vns.vNetsByGuid.get(guid2).gateway.equals(gw1));
+        // make sure the new name mapping was learned
+        assertTrue(vns.nameToGuid.get("newnet2").equals(guid2));
+        assertTrue(vns.vNetsByGuid.get(guid2).name.equals("newnet2"));
+        // and the old one was deleted
+        assertFalse(vns.nameToGuid.containsKey(net2));
+    }
+    
+    @Test
+    public void testDeleteNetwork() {
+        testModifyNetwork();
+        // Delete newnet2
+        vns.deleteNetwork(guid2);
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid3));
+        assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 2);
+        assertFalse(vns.nameToGuid.containsKey(net2));
+        assertFalse(vns.guidToGateway.containsKey(net2));
+        assertTrue(vns.vNetsByGuid.get(guid2)==null);
+    }
+    
+    @Test
+    public void testAddHost() {
+        testModifyNetwork();
+        vns.addHost(mac1, guid1, hostPort1);
+        assertTrue(vns.macToGuid.get(mac1).equals(guid1));
+        assertTrue(vns.portToMac.get(hostPort1).equals(mac1));
+        assertTrue(vns.vNetsByGuid.get(guid1).hosts.contains(mac1));
+        vns.addHost(mac2, guid1, hostPort2);
+        assertTrue(vns.macToGuid.get(mac2).equals(guid1));
+        assertTrue(vns.portToMac.get(hostPort2).equals(mac2));
+        assertTrue(vns.vNetsByGuid.get(guid1).hosts.contains(mac2));
+        vns.addHost(mac3, guid3, hostPort3);
+        vns.addHost(mac4, guid3, hostPort4);
+        assertTrue(vns.vNetsByGuid.get(guid3).hosts.contains(mac4));
+    }
+    
+    @Test
+    public void testDeleteHost() {
+        testAddHost();
+
+        String host1Guid = vns.macToGuid.get(mac1);
+        vns.deleteHost(mac1, null);
+        assertFalse(vns.macToGuid.containsKey(mac1));
+        assertFalse(vns.portToMac.containsKey(hostPort1));
+        assertFalse(vns.vNetsByGuid.get(host1Guid).hosts.contains(mac1));
+        
+        String host2Guid = vns.macToGuid.get(vns.portToMac.get(hostPort2));
+        vns.deleteHost(null, hostPort2);
+        assertFalse(vns.macToGuid.containsKey(mac2));
+        assertFalse(vns.portToMac.containsKey(hostPort2));
+        assertFalse(vns.vNetsByGuid.get(host2Guid).hosts.contains(mac2));
+
+        String host3Guid = vns.macToGuid.get(mac3);        
+        vns.deleteHost(mac3, hostPort3);
+        assertFalse(vns.macToGuid.containsKey(mac3));
+        assertFalse(vns.portToMac.containsKey(hostPort3));
+        assertFalse(vns.vNetsByGuid.get(host3Guid).hosts.contains(mac3));
+        
+    }
+    
+    @Test
+    public void testForwarding() {
+        testAddHost();
+        // make sure mac1 can communicate with mac2
+        IOFMessageListener listener = getVirtualNetworkListener();
+        cntx = new FloodlightContext();
+        IFloodlightProviderService.bcStore.put(cntx, 
+                           IFloodlightProviderService.CONTEXT_PI_PAYLOAD, 
+                               (Ethernet)mac1ToMac2PacketIntestPacket);
+        Command ret = listener.receive(sw1, mac1ToMac2PacketIn, cntx);
+        assertTrue(ret == Command.CONTINUE);
+        // make sure mac1 can't communicate with mac4
+        cntx = new FloodlightContext();
+        IFloodlightProviderService.bcStore.put(cntx,
+                           IFloodlightProviderService.CONTEXT_PI_PAYLOAD, 
+                               (Ethernet)mac1ToMac4PacketIntestPacket);
+        ret = listener.receive(sw1, mac1ToMac4PacketIn, cntx);
+        assertTrue(ret == Command.STOP);
+    }
+
+    @Test
+    public void testDefaultGateway() {
+        testAddHost();
+        IOFMessageListener listener = getVirtualNetworkListener();
+        cntx = new FloodlightContext();
+        IFloodlightProviderService.bcStore.put(cntx, 
+                           IFloodlightProviderService.CONTEXT_PI_PAYLOAD, 
+                               (Ethernet)mac1ToGwPacketIntestPacket);
+        deviceService.learnEntity(((Ethernet)mac1ToGwPacketIntestPacket).getDestinationMAC().toLong(), 
+        		null, IPv4.toIPv4Address(gw1), null, null);
+        Command ret = listener.receive(sw1, mac1ToGwPacketIn, cntx);
+        assertTrue(ret == Command.CONTINUE);
+    }
+    
+    @Test
+    public void testDhcp() {
+        IOFMessageListener listener = getVirtualNetworkListener();
+        Ethernet dhcpPacket = PacketFactory.DhcpDiscoveryRequestEthernet(mac1);
+        OFPacketIn dhcpPacketOf = PacketFactory.DhcpDiscoveryRequestOFPacketIn(mac1);
+        cntx = new FloodlightContext();
+        IFloodlightProviderService.bcStore.put(cntx, 
+                           IFloodlightProviderService.CONTEXT_PI_PAYLOAD, 
+                           dhcpPacket);
+        Command ret = listener.receive(sw1, dhcpPacketOf, cntx);
+        assertTrue(ret == Command.CONTINUE);
+    }
+    
+    protected IOFMessageListener getVirtualNetworkListener() {
+    	List<IOFMessageListener> listeners = mockFloodlightProvider.getListeners().get(OFType.PACKET_IN);
+    	return listeners.get(listeners.indexOf(vns));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/BasicFactoryTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/BasicFactoryTest.java
new file mode 100644
index 0000000..6825008
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/BasicFactoryTest.java
@@ -0,0 +1,81 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.MessageParseException;
+import org.openflow.util.U16;
+
+import junit.framework.TestCase;
+
+public class BasicFactoryTest extends TestCase {
+
+    public void testCreateAndParse() throws MessageParseException {
+        BasicFactory factory = new BasicFactory();
+        OFMessage m = factory.getMessage(OFType.HELLO);
+        m.setVersion((byte) 1);
+        m.setType(OFType.ECHO_REQUEST);
+        m.setLength(U16.t(8));
+        m.setXid(0xdeadbeef);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        ChannelBuffer bb2 = ChannelBuffers.dynamicBuffer();
+        m.writeTo(bb);
+        bb2.writeBytes(bb, bb.readableBytes()-1);
+        TestCase.assertNull(factory.parseMessage(bb2));
+        bb2.writeByte(bb.readByte());
+        List<OFMessage> message = factory.parseMessage(bb2);
+        TestCase.assertNotNull(message);
+        TestCase.assertEquals(message.size(), 1);
+        TestCase.assertTrue(message.get(0).getType() == OFType.ECHO_REQUEST);
+    }
+
+    public void testInvalidMsgParse() throws MessageParseException {
+        BasicFactory factory = new BasicFactory();
+        OFMessage m = factory.getMessage(OFType.HELLO);
+        m.setVersion((byte) 1);
+        m.setType(OFType.ECHO_REQUEST);
+        m.setLength(U16.t(16));
+        m.setXid(0xdeadbeef);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        m.writeTo(bb);
+        List<OFMessage> message = factory.parseMessage(bb);
+        TestCase.assertNull(message);
+    }
+
+    public void testCurrouptedMsgParse() throws MessageParseException {
+        BasicFactory factory = new BasicFactory();
+        OFMessage m = factory.getMessage(OFType.HELLO);
+        m.setVersion((byte) 1);
+        m.setType(OFType.ERROR);
+        m.setLength(U16.t(8));
+        m.setXid(0xdeadbeef);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        m.writeTo(bb);
+        try {
+                factory.parseMessage(bb);
+        }
+        catch(Exception e) {
+            TestCase.assertEquals(MessageParseException.class, e.getClass());
+        }
+    }
+
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFActionTypeTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFActionTypeTest.java
new file mode 100644
index 0000000..ed8386c
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFActionTypeTest.java
@@ -0,0 +1,37 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+
+import org.junit.Test;
+import org.openflow.protocol.action.OFActionType;
+
+import junit.framework.TestCase;
+
+
+public class OFActionTypeTest extends TestCase {
+    @Test
+    public void testMapping() throws Exception {
+        TestCase.assertEquals(OFActionType.OUTPUT,
+                OFActionType.valueOf((short) 0));
+        TestCase.assertEquals(OFActionType.OPAQUE_ENQUEUE,
+                OFActionType.valueOf((short) 11));
+        TestCase.assertEquals(OFActionType.VENDOR,
+                OFActionType.valueOf((short) 0xffff));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierReplyTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierReplyTest.java
new file mode 100644
index 0000000..7e447bb
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierReplyTest.java
@@ -0,0 +1,36 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.util.OFTestCase;
+
+public class OFBarrierReplyTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFBarrierReply msg = (OFBarrierReply) messageFactory
+                .getMessage(OFType.BARRIER_REPLY);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals(OFType.BARRIER_REPLY, msg.getType());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierRequestTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierRequestTest.java
new file mode 100644
index 0000000..3aa9cb4
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierRequestTest.java
@@ -0,0 +1,36 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.util.OFTestCase;
+
+public class OFBarrierRequestTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFBarrierRequest msg = (OFBarrierRequest) messageFactory
+                .getMessage(OFType.BARRIER_REQUEST);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals(OFType.BARRIER_REQUEST, msg.getType());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFErrorTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFErrorTest.java
new file mode 100644
index 0000000..45d5257
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFErrorTest.java
@@ -0,0 +1,88 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.OFError.OFErrorType;
+import org.openflow.protocol.OFError.OFHelloFailedCode;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.MessageParseException;
+import org.openflow.protocol.factory.OFMessageFactory;
+import org.openflow.util.OFTestCase;
+
+public class OFErrorTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFError msg = (OFError) messageFactory.getMessage(OFType.ERROR);
+        msg.setMessageFactory(messageFactory);
+        msg.setErrorType((short) OFErrorType.OFPET_HELLO_FAILED.getValue());
+        msg.setErrorCode((short) OFHelloFailedCode.OFPHFC_INCOMPATIBLE
+                .ordinal());
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals((short) OFErrorType.OFPET_HELLO_FAILED.getValue(),
+                msg.getErrorType());
+        TestCase.assertEquals((short) OFHelloFailedCode.OFPHFC_INCOMPATIBLE
+                .ordinal(), msg.getErrorType());
+        TestCase.assertNull(msg.getOffendingMsg());
+
+        msg.setOffendingMsg(new OFHello());
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals((short) OFErrorType.OFPET_HELLO_FAILED.getValue(),
+                msg.getErrorType());
+        TestCase.assertEquals((short) OFHelloFailedCode.OFPHFC_INCOMPATIBLE
+                .ordinal(), msg.getErrorType());
+        TestCase.assertNotNull(msg.getOffendingMsg());
+        TestCase.assertEquals(OFHello.MINIMUM_LENGTH,
+                msg.getOffendingMsg().length);
+    }
+
+    public void testGarbageAtEnd() throws MessageParseException {
+        // This is a OFError msg (12 bytes), that encaps a OFVendor msg (24
+        // bytes)
+        // AND some zeros at the end (40 bytes) for a total of 76 bytes
+        // THIS is what an NEC sends in reply to Nox's VENDOR request
+        byte[] oferrorRaw = { 0x01, 0x01, 0x00, 0x4c, 0x00, 0x00, 0x10,
+                (byte) 0xcc, 0x00, 0x01, 0x00, 0x01, 0x01, 0x04, 0x00, 0x18,
+                0x00, 0x00, 0x10, (byte) 0xcc, 0x00, 0x00, 0x23, 0x20, 0x00,
+                0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00 };
+        OFMessageFactory factory = new BasicFactory();
+        ChannelBuffer oferrorBuf = 
+                ChannelBuffers.wrappedBuffer(oferrorRaw);
+        List<OFMessage> msg = factory.parseMessage(oferrorBuf);
+        TestCase.assertNotNull(msg);
+        TestCase.assertEquals(msg.size(), 1);
+        TestCase.assertEquals(76, msg.get(0).getLengthU());
+        ChannelBuffer out = ChannelBuffers.dynamicBuffer();
+        msg.get(0).writeTo(out);
+        TestCase.assertEquals(76, out.readableBytes());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFeaturesReplyTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFeaturesReplyTest.java
new file mode 100644
index 0000000..62e491a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFeaturesReplyTest.java
@@ -0,0 +1,62 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.util.OFTestCase;
+
+
+public class OFFeaturesReplyTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFFeaturesReply ofr = (OFFeaturesReply) messageFactory
+                .getMessage(OFType.FEATURES_REPLY);
+        List<OFPhysicalPort> ports = new ArrayList<OFPhysicalPort>();
+        OFPhysicalPort port = new OFPhysicalPort();
+        port.setHardwareAddress(new byte[6]);
+        port.setName("eth0");
+        ports.add(port);
+        ofr.setPorts(ports);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        ofr.writeTo(bb);
+        ofr.readFrom(bb);
+        TestCase.assertEquals(1, ofr.getPorts().size());
+        TestCase.assertEquals("eth0", ofr.getPorts().get(0).getName());
+
+        // test a 15 character name
+        ofr.getPorts().get(0).setName("012345678901234");
+        bb.clear();
+        ofr.writeTo(bb);
+        ofr.readFrom(bb);
+        TestCase.assertEquals("012345678901234", ofr.getPorts().get(0).getName());
+
+        // test a 16 character name getting truncated
+        ofr.getPorts().get(0).setName("0123456789012345");
+        bb.clear();
+        ofr.writeTo(bb);
+        ofr.readFrom(bb);
+        TestCase.assertEquals("012345678901234", ofr.getPorts().get(0).getName());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFlowRemovedTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFlowRemovedTest.java
new file mode 100644
index 0000000..11746e7
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFlowRemovedTest.java
@@ -0,0 +1,43 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.OFFlowRemoved.OFFlowRemovedReason;
+import org.openflow.util.OFTestCase;
+
+public class OFFlowRemovedTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFFlowRemoved msg = (OFFlowRemoved) messageFactory
+                .getMessage(OFType.FLOW_REMOVED);
+        msg.setMatch(new OFMatch());
+        byte[] hwAddr = new byte[6];
+        msg.getMatch().setDataLayerDestination(hwAddr);
+        msg.getMatch().setDataLayerSource(hwAddr);
+        msg.setReason(OFFlowRemovedReason.OFPRR_DELETE);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals(OFType.FLOW_REMOVED, msg.getType());
+        TestCase.assertEquals(OFFlowRemovedReason.OFPRR_DELETE, msg.getReason());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigReplyTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigReplyTest.java
new file mode 100644
index 0000000..c1f1f67
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigReplyTest.java
@@ -0,0 +1,38 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.util.OFTestCase;
+
+public class OFGetConfigReplyTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFSetConfig msg = (OFSetConfig) messageFactory
+                .getMessage(OFType.SET_CONFIG);
+        msg.setFlags((short) 1);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals(OFType.SET_CONFIG, msg.getType());
+        TestCase.assertEquals((short)1, msg.getFlags());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigRequestTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigRequestTest.java
new file mode 100644
index 0000000..94d9036
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigRequestTest.java
@@ -0,0 +1,36 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.util.OFTestCase;
+
+public class OFGetConfigRequestTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFGetConfigRequest msg = (OFGetConfigRequest) messageFactory
+                .getMessage(OFType.GET_CONFIG_REQUEST);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals(OFType.GET_CONFIG_REQUEST, msg.getType());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMatchTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMatchTest.java
new file mode 100644
index 0000000..fd7863a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMatchTest.java
@@ -0,0 +1,92 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+public class OFMatchTest extends TestCase {
+    public void testFromString() {
+        OFMatch correct = new OFMatch();
+        OFMatch tester = new OFMatch();
+
+        // Various combinations of "all"/"any"
+        tester.fromString("OFMatch[]");
+        // correct is already wildcarded
+        TestCase.assertEquals(correct, tester);
+        tester.fromString("all");
+        TestCase.assertEquals(correct, tester);
+        tester.fromString("ANY");
+        TestCase.assertEquals(correct, tester);
+        tester.fromString("");
+        TestCase.assertEquals(correct, tester);
+        tester.fromString("[]");
+        TestCase.assertEquals(correct, tester);
+
+        // ip_src
+        correct.setWildcards(~OFMatch.OFPFW_NW_SRC_MASK);
+        correct.setNetworkSource(0x01010203);
+        tester.fromString("nw_src=1.1.2.3");
+        TestCase.assertEquals(correct.getNetworkSourceMaskLen(), tester
+                .getNetworkSourceMaskLen());
+        TestCase.assertEquals(correct, tester);
+        tester.fromString("IP_sRc=1.1.2.3");
+        TestCase.assertEquals(correct.getNetworkSourceMaskLen(), tester
+                .getNetworkSourceMaskLen());
+        TestCase.assertEquals(correct, tester);
+        
+        // 0xVlan
+        correct = new OFMatch();
+        correct.setDataLayerVirtualLan((short)65535);
+        correct.setWildcards(~OFMatch.OFPFW_DL_VLAN);
+        tester = new OFMatch();
+        tester.fromString("dl_vlan=0xffff");
+        TestCase.assertEquals(correct, tester);
+        }
+
+    public void testToString() {
+        OFMatch match = new OFMatch();
+        match.fromString("nw_dst=3.4.5.6/8");
+        TestCase.assertEquals(8, match.getNetworkDestinationMaskLen());
+        String correct = "OFMatch[nw_dst=3.0.0.0/8]";
+        String tester = match.toString();
+
+        TestCase.assertEquals(correct, tester);
+        tester = "OFMatch[dl_type=35020]";
+        correct = "OFMatch[dl_type=0x88cc]";
+        match = new OFMatch();
+        match.fromString(tester);
+        TestCase.assertEquals(correct, match.toString());
+        OFMatch match2 = new OFMatch();
+        match2.fromString(correct);
+        TestCase.assertEquals(match, match2);
+    }
+
+    public void testClone() {
+        OFMatch match1 = new OFMatch();
+        OFMatch match2 = match1.clone();
+        TestCase.assertEquals(match1, match2);
+        match2.setNetworkProtocol((byte) 4);
+        match2.setWildcards(match2.getWildcards() & ~OFMatch.OFPFW_NW_PROTO);
+        TestCase.assertNotSame(match1, match2);
+    }
+
+    public void testIpToString() {
+        String test = OFMatch.ipToString(-1);
+        TestCase.assertEquals("255.255.255.255", test);
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMessageContextStoreTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMessageContextStoreTest.java
new file mode 100644
index 0000000..60a9e73
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMessageContextStoreTest.java
@@ -0,0 +1,31 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+public class OFMessageContextStoreTest extends TestCase {
+    public void testStoreAndGet() {
+        OFMessage msg = new OFMessage();
+        OFMessageContextStore<String> store = new OFMessageContextStore<String>(msg, this.getName());
+        String key = "mykey";
+        String value = "myvalue";
+        store.put(key, value);
+        TestCase.assertEquals(value, store.get(key));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortConfigTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortConfigTest.java
new file mode 100644
index 0000000..9b115eb
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortConfigTest.java
@@ -0,0 +1,39 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.util.OFTestCase;
+
+public class OFPortConfigTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFPortMod msg = (OFPortMod) messageFactory
+                .getMessage(OFType.PORT_MOD);
+        msg.setHardwareAddress(new byte[6]);
+        msg.portNumber = 1;
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals(OFType.PORT_MOD, msg.getType());
+        TestCase.assertEquals(1, msg.getPortNumber());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortStatusTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortStatusTest.java
new file mode 100644
index 0000000..4fab64e
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortStatusTest.java
@@ -0,0 +1,44 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.OFPortStatus.OFPortReason;
+import org.openflow.util.OFTestCase;
+
+public class OFPortStatusTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFPortStatus msg = (OFPortStatus) messageFactory
+                .getMessage(OFType.PORT_STATUS);
+        msg.setDesc(new OFPhysicalPort());
+        msg.getDesc().setHardwareAddress(new byte[6]);
+        msg.getDesc().setName("eth0");
+        msg.setReason((byte) OFPortReason.OFPPR_ADD.ordinal());
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals(OFType.PORT_STATUS, msg.getType());
+        TestCase.assertEquals((byte) OFPortReason.OFPPR_ADD.ordinal(), msg
+                .getReason());
+        TestCase.assertNotNull(msg.getDesc());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFSetConfigTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFSetConfigTest.java
new file mode 100644
index 0000000..2a9e86f
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFSetConfigTest.java
@@ -0,0 +1,38 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.util.OFTestCase;
+
+public class OFSetConfigTest extends OFTestCase {
+    public void testWriteRead() throws Exception {
+        OFGetConfigReply msg = (OFGetConfigReply) messageFactory
+                .getMessage(OFType.GET_CONFIG_REPLY);
+        msg.setFlags((short) 1);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals(OFType.GET_CONFIG_REPLY, msg.getType());
+        TestCase.assertEquals((short)1, msg.getFlags());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java
new file mode 100644
index 0000000..50bac8f
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java
@@ -0,0 +1,77 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.OFMessageFactory;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.util.OFTestCase;
+
+public class OFStatisticsReplyTest extends OFTestCase {
+    public void testOFFlowStatisticsReply() throws Exception {
+        byte[] packet = new byte[] { 0x01, 0x11, 0x01, 0x2c, 0x00, 0x00, 0x00,
+                0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, (byte) 0xff,
+                (byte) 0xff, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00,
+                0x0a, 0x00, 0x00, 0x03, 0x0a, 0x00, 0x00, 0x02, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, (byte) 0xa6,
+                (byte) 0xa6, 0x00, (byte) 0xff, (byte) 0xff, 0x00, 0x05, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                (byte) 0xc4, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00,
+                0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x02, (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x08, 0x06,
+                0x00, 0x02, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x03, 0x0a, 0x00,
+                0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x3b, 0x2f, (byte) 0xfa, 0x40, (byte) 0xff, (byte) 0xff, 0x00,
+                0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00,
+                0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x03, (byte) 0xff, (byte) 0xff, 0x00, 0x62, 0x08,
+                0x00, 0x00, 0x01, 0x62, 0x37, 0x0a, 0x00, 0x00, 0x02, 0x0a,
+                0x00, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x3a, (byte) 0xc5, 0x2a, (byte) 0x80, (byte) 0xff,
+                (byte) 0xff, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xc4, 0x00, 0x00, 0x00,
+                0x08, 0x00, 0x02, 0x00, 0x00 };
+
+        OFMessageFactory factory = new BasicFactory();
+        ChannelBuffer packetBuf = ChannelBuffers.wrappedBuffer(packet);
+        List<OFMessage> msg = factory.parseMessage(packetBuf);
+        TestCase.assertNotNull(msg);
+        TestCase.assertEquals(msg.size(), 1);
+        TestCase.assertTrue(msg.get(0) instanceof OFStatisticsReply);
+        OFStatisticsReply sr = (OFStatisticsReply) msg.get(0);
+        TestCase.assertEquals(OFStatisticsType.FLOW, sr.getStatisticType());
+        TestCase.assertEquals(3, sr.getStatistics().size());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java
new file mode 100644
index 0000000..74af6f4
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java
@@ -0,0 +1,79 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.OFMessageFactory;
+import org.openflow.protocol.statistics.OFFlowStatisticsRequest;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.protocol.statistics.OFVendorStatistics;
+import org.openflow.util.OFTestCase;
+
+public class OFStatisticsRequestTest extends OFTestCase {
+    public void testOFFlowStatisticsRequest() throws Exception {
+        byte[] packet = new byte[] { 0x01, 0x10, 0x00, 0x38, 0x00, 0x00, 0x00,
+                0x16, 0x00, 0x01, 0x00, 0x00, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                (byte) 0xff, 0x00, (byte) 0xff, (byte) 0xff };
+
+        OFMessageFactory factory = new BasicFactory();
+        ChannelBuffer packetBuf = ChannelBuffers.wrappedBuffer(packet);
+        List<OFMessage> msg = factory.parseMessage(packetBuf);
+        TestCase.assertNotNull(msg);
+        TestCase.assertEquals(msg.size(), 1);
+        TestCase.assertTrue(msg.get(0) instanceof OFStatisticsRequest);
+        OFStatisticsRequest sr = (OFStatisticsRequest) msg.get(0);
+        TestCase.assertEquals(OFStatisticsType.FLOW, sr.getStatisticType());
+        TestCase.assertEquals(1, sr.getStatistics().size());
+        TestCase.assertTrue(sr.getStatistics().get(0) instanceof OFFlowStatisticsRequest);
+    }
+
+    public void testOFStatisticsRequestVendor() throws Exception {
+        byte[] packet = new byte[] { 0x01, 0x10, 0x00, 0x50, 0x00, 0x00, 0x00,
+                0x63, (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x4c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x01, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x20,
+                (byte) 0xe0, 0x00, 0x11, 0x00, 0x0c, 0x29, (byte) 0xc5,
+                (byte) 0x95, 0x57, 0x02, 0x25, 0x5c, (byte) 0xca, 0x00, 0x02,
+                (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x50, 0x04,
+                0x00, 0x00, 0x00, 0x00, (byte) 0xff, 0x00, 0x00, 0x00,
+                (byte) 0xff, (byte) 0xff, 0x4e, 0x20 };
+
+        OFMessageFactory factory = new BasicFactory();
+        ChannelBuffer packetBuf = ChannelBuffers.wrappedBuffer(packet);
+        List<OFMessage> msg = factory.parseMessage(packetBuf);
+        TestCase.assertNotNull(msg);
+        TestCase.assertEquals(msg.size(), 1);
+        TestCase.assertTrue(msg.get(0) instanceof OFStatisticsRequest);
+        OFStatisticsRequest sr = (OFStatisticsRequest) msg.get(0);
+        TestCase.assertEquals(OFStatisticsType.VENDOR, sr.getStatisticType());
+        TestCase.assertEquals(1, sr.getStatistics().size());
+        TestCase.assertTrue(sr.getStatistics().get(0) instanceof OFVendorStatistics);
+        TestCase.assertEquals(68, ((OFVendorStatistics)sr.getStatistics().get(0)).getLength());
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsTypeTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsTypeTest.java
new file mode 100644
index 0000000..d44ef7f
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsTypeTest.java
@@ -0,0 +1,37 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+import org.openflow.protocol.statistics.OFStatisticsType;
+
+
+public class OFStatisticsTypeTest extends TestCase {
+    @Test
+    public void testMapping() throws Exception {
+        TestCase.assertEquals(OFStatisticsType.DESC,
+                OFStatisticsType.valueOf((short) 0, OFType.STATS_REQUEST));
+        TestCase.assertEquals(OFStatisticsType.QUEUE,
+                OFStatisticsType.valueOf((short) 5, OFType.STATS_REQUEST));
+        TestCase.assertEquals(OFStatisticsType.VENDOR,
+                OFStatisticsType.valueOf((short) 0xffff, OFType.STATS_REQUEST));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFTypeTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFTypeTest.java
new file mode 100644
index 0000000..c6bf0a3
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFTypeTest.java
@@ -0,0 +1,39 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+
+
+public class OFTypeTest extends TestCase {
+
+    public void testOFTypeCreate() throws Exception {
+        OFType foo = OFType.HELLO;
+        Class<? extends OFMessage> c = foo.toClass();
+        TestCase.assertEquals(c, OFHello.class);
+    }
+
+    @Test
+    public void testMapping() throws Exception {
+        TestCase.assertEquals(OFType.HELLO, OFType.valueOf((byte) 0));
+        TestCase.assertEquals(OFType.BARRIER_REPLY, OFType.valueOf((byte) 19));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFVendorTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFVendorTest.java
new file mode 100644
index 0000000..b85a915
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFVendorTest.java
@@ -0,0 +1,215 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.protocol;
+
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.vendor.OFByteArrayVendorData;
+import org.openflow.protocol.vendor.OFBasicVendorDataType;
+import org.openflow.protocol.vendor.OFBasicVendorId;
+import org.openflow.protocol.vendor.OFVendorData;
+import org.openflow.protocol.vendor.OFVendorId;
+import org.openflow.util.OFTestCase;
+
+public class OFVendorTest extends OFTestCase {
+
+    public static int ACME_VENDOR_ID = 0x00112233;
+    
+    static class AcmeVendorData implements OFVendorData {
+        protected int dataType;
+        
+        public int getLength() {
+            return 4;
+        }
+        
+        public void readFrom(ChannelBuffer data, int length) {
+            dataType = data.readInt();
+        }
+        
+        public void writeTo(ChannelBuffer data) {
+            data.writeInt(dataType);
+        }
+    }
+    
+    static class AcmeVendorData1 extends AcmeVendorData {
+        public short flags;
+        public short value;
+        
+        public static int DATA_TYPE = 1;
+        
+        public AcmeVendorData1() {
+        }
+        
+        public AcmeVendorData1(short flags, short value) {
+            this.dataType = DATA_TYPE;
+            this.flags = flags;
+            this.value = value;
+        }
+        
+        public short getFlags() {
+            return flags;
+        }
+        
+        public short getValue() {
+            return value;
+        }
+        
+        public int getLength() {
+            return 8;
+        }
+        
+        public void readFrom(ChannelBuffer data, int length) {
+            super.readFrom(data, length);
+            flags = data.readShort();
+            value = data.readShort();
+
+        }
+        public void writeTo(ChannelBuffer data) {
+            super.writeTo(data);
+            data.writeShort(flags);
+            data.writeShort(value);
+        }
+        
+        public static Instantiable<OFVendorData> getInstantiable() {
+            return new Instantiable<OFVendorData>() {
+                public OFVendorData instantiate() {
+                    return new AcmeVendorData1();
+                }
+            };
+        }
+    }
+    
+    static class AcmeVendorData2 extends AcmeVendorData {
+        public int type;
+        public int subtype;
+
+        public static int DATA_TYPE = 2;
+
+        public AcmeVendorData2() {
+        }
+        
+        public AcmeVendorData2(int type, int subtype) {
+            this.dataType = DATA_TYPE;
+            this.type = type;
+            this.subtype = subtype;
+        }
+        
+        public int getType() {
+            return type;
+        }
+        
+        public int getSubtype() {
+            return subtype;
+        }
+        
+        public int getLength() {
+            return 12;
+        }
+        
+        public void readFrom(ChannelBuffer data, int length) {
+            super.readFrom(data, length);
+            type = data.readShort();
+            subtype = data.readShort();
+
+        }
+        public void writeTo(ChannelBuffer data) {
+            super.writeTo(data);
+            data.writeShort(type);
+            data.writeShort(subtype);
+        }
+        
+        public static Instantiable<OFVendorData> getInstantiable() {
+            return new Instantiable<OFVendorData>() {
+                public OFVendorData instantiate() {
+                    return new AcmeVendorData2();
+                }
+            };
+        }
+    }
+    
+    {
+        OFBasicVendorId acmeVendorId = new OFBasicVendorId(ACME_VENDOR_ID, 4);
+        OFVendorId.registerVendorId(acmeVendorId);
+        OFBasicVendorDataType acmeVendorData1 = new OFBasicVendorDataType(
+            AcmeVendorData1.DATA_TYPE, AcmeVendorData1.getInstantiable());
+        acmeVendorId.registerVendorDataType(acmeVendorData1);
+        OFBasicVendorDataType acmeVendorData2 = new OFBasicVendorDataType(
+            AcmeVendorData2.DATA_TYPE, AcmeVendorData2.getInstantiable());
+        acmeVendorId.registerVendorDataType(acmeVendorData2);
+    }
+    
+    private OFVendor makeVendorMessage(int vendor) {
+        OFVendor msg = (OFVendor) messageFactory.getMessage(OFType.VENDOR);
+        msg.setVendorDataFactory(new BasicFactory());
+        msg.setVendor(vendor);
+        return msg;
+    }
+    
+    public void testWriteRead() throws Exception {
+        OFVendor msg = makeVendorMessage(1);
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        TestCase.assertEquals(1, msg.getVendor());
+    }
+    
+    public void testVendorData() throws Exception {
+        OFVendor msg = makeVendorMessage(ACME_VENDOR_ID);
+        OFVendorData vendorData = new AcmeVendorData1((short)11, (short)22);
+        msg.setVendorData(vendorData);
+        msg.setLengthU(OFVendor.MINIMUM_LENGTH + vendorData.getLength());
+        ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        assertEquals(ACME_VENDOR_ID, msg.getVendor());
+        AcmeVendorData1 vendorData1 = (AcmeVendorData1) msg.getVendorData();
+        assertEquals(11, vendorData1.getFlags());
+        assertEquals(22, vendorData1.getValue());
+        
+        vendorData = new AcmeVendorData2(33, 44);
+        msg.setVendorData(vendorData);
+        msg.setLengthU(OFVendor.MINIMUM_LENGTH + vendorData.getLength());
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        assertEquals(ACME_VENDOR_ID, msg.getVendor());
+        AcmeVendorData2 vendorData2 = (AcmeVendorData2) msg.getVendorData();
+        assertEquals(33, vendorData2.getType());
+        assertEquals(44, vendorData2.getSubtype());
+        
+        final int DUMMY_VENDOR_ID = 55;
+        msg.setVendor(DUMMY_VENDOR_ID);
+        byte[] genericVendorDataBytes = new byte[] {0x55, 0x66};
+        vendorData = new OFByteArrayVendorData(genericVendorDataBytes);
+        msg.setVendorData(vendorData);
+        msg.setLengthU(OFVendor.MINIMUM_LENGTH + vendorData.getLength());
+        bb.clear();
+        msg.writeTo(bb);
+        msg.readFrom(bb);
+        assertEquals(DUMMY_VENDOR_ID, msg.getVendor());
+        OFByteArrayVendorData genericVendorData = (OFByteArrayVendorData) msg.getVendorData();
+        assertTrue(Arrays.equals(genericVendorDataBytes, genericVendorData.getBytes()));
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/util/HexStringTest.java b/src/ext/floodlight/src/test/java/org/openflow/util/HexStringTest.java
new file mode 100644
index 0000000..a8f8ba4
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/HexStringTest.java
@@ -0,0 +1,88 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import org.junit.Test;
+
+import junit.framework.TestCase;
+
+/**
+ * Does hexstring conversion work?
+ * 
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ * 
+ */
+
+public class HexStringTest extends TestCase {
+    
+    @Test
+    public void testMarshalling() throws Exception {
+        String dpidStr = "00:00:00:23:20:2d:16:71";
+        long dpid = HexString.toLong(dpidStr);
+        String testStr = HexString.toHexString(dpid);
+        TestCase.assertEquals(dpidStr, testStr);
+    }
+    
+    @Test
+    public void testToLong() {
+        String dpidStr = "3e:1f:01:fc:72:8c:63:31";
+        long valid = 0x3e1f01fc728c6331L;
+        long testLong = HexString.toLong(dpidStr);
+        TestCase.assertEquals(valid, testLong);
+    }
+    
+    @Test
+    public void testToLongMSB() {
+        String dpidStr = "ca:7c:5e:d1:64:7a:95:9b";
+        long valid = -3856102927509056101L;
+        long testLong = HexString.toLong(dpidStr);
+        TestCase.assertEquals(valid, testLong);
+    }
+    
+    @Test
+    public void testToLongError() {
+        String dpidStr = "09:08:07:06:05:04:03:02:01";
+        try {
+            HexString.toLong(dpidStr);
+            fail("HexString.toLong() should have thrown a NumberFormatException");
+        }
+        catch (NumberFormatException expected) {
+            // do nothing
+        }
+    }
+
+    @Test
+    public void testToStringBytes() {
+        byte[] dpid = { 0, 0, 0, 0, 0, 0, 0, -1 };
+        String valid = "00:00:00:00:00:00:00:ff";
+        String testString = HexString.toHexString(dpid);
+        TestCase.assertEquals(valid, testString);
+    }
+    
+    @Test
+    public void testFromHexStringError() {
+        String invalidStr = "00:00:00:00:00:00:ffff";
+        try {
+            HexString.fromHexString(invalidStr);
+            fail("HexString.fromHexString() should have thrown a NumberFormatException");
+        }
+        catch (NumberFormatException expected) {
+            // do nothing
+        }
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/util/OFTestCase.java b/src/ext/floodlight/src/test/java/org/openflow/util/OFTestCase.java
new file mode 100644
index 0000000..5132c2c
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/OFTestCase.java
@@ -0,0 +1,36 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.OFMessageFactory;
+
+import junit.framework.TestCase;
+
+public class OFTestCase extends TestCase {
+    public OFMessageFactory messageFactory;
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        messageFactory = new BasicFactory();
+    }
+
+    public void test() throws Exception {
+    }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/util/U16Test.java b/src/ext/floodlight/src/test/java/org/openflow/util/U16Test.java
new file mode 100644
index 0000000..ba87e7b
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/U16Test.java
@@ -0,0 +1,33 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import junit.framework.TestCase;
+
+public class U16Test extends TestCase {
+  /**
+   * Tests that we correctly translate unsigned values in and out of a short
+   * @throws Exception
+   */
+  public void test() throws Exception {
+      int val = 0xffff;
+      TestCase.assertEquals((short)-1, U16.t(val));
+      TestCase.assertEquals((short)32767, U16.t(0x7fff));
+      TestCase.assertEquals(val, U16.f((short)-1));
+  }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/util/U32Test.java b/src/ext/floodlight/src/test/java/org/openflow/util/U32Test.java
new file mode 100644
index 0000000..223c103
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/U32Test.java
@@ -0,0 +1,32 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import junit.framework.TestCase;
+
+public class U32Test extends TestCase {
+  /**
+   * Tests that we correctly translate unsigned values in and out of an int
+   * @throws Exception
+   */
+  public void test() throws Exception {
+      long val = 0xffffffffL;
+      TestCase.assertEquals(-1, U32.t(val));
+      TestCase.assertEquals(val, U32.f(-1));
+  }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/util/U64Test.java b/src/ext/floodlight/src/test/java/org/openflow/util/U64Test.java
new file mode 100644
index 0000000..0a97e30
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/U64Test.java
@@ -0,0 +1,34 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import java.math.BigInteger;
+
+import junit.framework.TestCase;
+
+public class U64Test extends TestCase {
+  /**
+   * Tests that we correctly translate unsigned values in and out of a long
+   * @throws Exception
+   */
+  public void test() throws Exception {
+      BigInteger val = new BigInteger("ffffffffffffffff", 16);
+      TestCase.assertEquals(-1, U64.t(val));
+      TestCase.assertEquals(val, U64.f(-1));
+  }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/util/U8Test.java b/src/ext/floodlight/src/test/java/org/openflow/util/U8Test.java
new file mode 100644
index 0000000..2c06c4d
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/U8Test.java
@@ -0,0 +1,32 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import junit.framework.TestCase;
+
+public class U8Test extends TestCase {
+  /**
+   * Tests that we correctly translate unsigned values in and out of a byte
+   * @throws Exception
+   */
+  public void test() throws Exception {
+      short val = 0xff;
+      TestCase.assertEquals(-1, U8.t(val));
+      TestCase.assertEquals(val, U8.f((byte)-1));
+  }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/util/UnsignedTest.java b/src/ext/floodlight/src/test/java/org/openflow/util/UnsignedTest.java
new file mode 100644
index 0000000..7cdf739
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/UnsignedTest.java
@@ -0,0 +1,83 @@
+/**
+*    Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior
+*    University
+* 
+*    Licensed under the Apache License, Version 2.0 (the "License"); you may
+*    not use this file except in compliance with the License. You may obtain
+*    a copy of the License at
+*
+*         http://www.apache.org/licenses/LICENSE-2.0
+*
+*    Unless required by applicable law or agreed to in writing, software
+*    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+*    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+*    License for the specific language governing permissions and limitations
+*    under the License.
+**/
+
+package org.openflow.util;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+import junit.framework.TestCase;
+
+public class UnsignedTest extends TestCase {
+    public static String ULONG_MAX = "18446744073709551615";
+
+  /**
+   * Tests that we correctly extract an unsigned long into a BigInteger
+   * @throws Exception
+   */
+  public void testGetUnsignedLong() throws Exception {
+    ByteBuffer bb = ByteBuffer.allocate(8);
+    bb.put((byte)0xff).put((byte)0xff).put((byte)0xff).put((byte)0xff);
+    bb.put((byte)0xff).put((byte)0xff).put((byte)0xff).put((byte)0xff);
+    bb.position(0);
+    bb.limit(8);
+    BigInteger bi = Unsigned.getUnsignedLong(bb);
+    BigInteger uLongMax = new BigInteger(ULONG_MAX);
+    for (int i = 0; i < uLongMax.bitCount(); ++i) {
+        TestCase.assertTrue("Bit: " + i + " should be: " + uLongMax.testBit(i),
+                uLongMax.testBit(i) == bi.testBit(i));
+    }
+    TestCase.assertEquals(ULONG_MAX, bi.toString());
+
+    bb = ByteBuffer.allocate(10);
+    bb.put((byte)0x00);
+    bb.put((byte)0xff).put((byte)0xff).put((byte)0xff).put((byte)0xff);
+    bb.put((byte)0xff).put((byte)0xff).put((byte)0xff).put((byte)0xff);
+    bb.put((byte)0x00);
+    bb.position(0);
+    bb.limit(10);
+    bi = Unsigned.getUnsignedLong(bb, 1);
+    uLongMax = new BigInteger(ULONG_MAX);
+    for (int i = 0; i < uLongMax.bitCount(); ++i) {
+        TestCase.assertTrue("Bit: " + i + " should be: " + uLongMax.testBit(i),
+                uLongMax.testBit(i) == bi.testBit(i));
+    }
+    TestCase.assertEquals(ULONG_MAX, bi.toString());
+  }
+
+  /**
+   * Tests that we correctly put an unsigned long into a ByteBuffer
+   * @throws Exception
+   */
+  public void testPutUnsignedLong() throws Exception {
+    ByteBuffer bb = ByteBuffer.allocate(8);
+    BigInteger uLongMax = new BigInteger(ULONG_MAX);
+    Unsigned.putUnsignedLong(bb, uLongMax);
+    for (int i = 0; i < 8; ++i) {
+        TestCase.assertTrue("Byte: " + i + " should be 0xff, was: " + bb.get(i),
+                (bb.get(i) & (short)0xff) == 0xff);
+    }
+
+    bb = ByteBuffer.allocate(10);
+    Unsigned.putUnsignedLong(bb, uLongMax, 1);
+    int offset = 1;
+    for (int i = 0; i < 8; ++i) {
+        TestCase.assertTrue("Byte: " + i + " should be 0xff, was: " +
+                bb.get(offset+i), (bb.get(offset+i) & (short)0xff) == 0xff);
+    }
+  }
+}