diff --git a/.gitignore b/.gitignore
index 6bb47c581906a2d592173e1b0532486f4ace9bdc..a52a8756ab293e8c4b045439bcbfe5dc19a05f31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,8 +7,8 @@
 .settings
 .DS_Store
 target
-thrift
 *.swp
 *.pyc
 findbugs-results
 *.launch
+/thrift
diff --git a/build.xml b/build.xml
index 0a3eb4bcdf095b74b5a1eb32ba8d19bbc63b33c9..0f97f63306fe1d76a459e998bbfa036f1fb75e5c 100644
--- a/build.xml
+++ b/build.xml
@@ -43,7 +43,6 @@
     <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="findbugs.home" value="../build/findbugs-2.0.2"/>
@@ -53,14 +52,19 @@
     <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="jackson-core-2.1.4.jar"/>
+        <include name="jackson-annotations-2.1.4.jar"/>
+        <include name="jackson-databind-2.1.4.jar"/>
+        <include name="jackson-dataformat-smile-2.1.4.jar"/>
+        <include name="jackson-dataformat-xml-2.1.4.jar"/>
+        <include name="jackson-dataformat-yaml-2.1.4.jar"/>
+        <include name="jackson-dataformat-csv-2.1.4.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="org.restlet-2.2M3.jar"/>
+        <include name="org.restlet.ext.jackson-2.2M3.jar"/>
+        <include name="org.restlet.ext.simple-2.2M3.jar"/>
+        <include name="org.restlet.ext.slf4j-2.2M3.jar"/>
+        <include name="simple-5.1.1.jar"/>
         <include name="netty-3.2.6.Final.jar"/>
         <include name="args4j-2.0.16.jar"/>
         <include name="concurrentlinkedhashmap-lru-1.2.jar"/>
@@ -69,6 +73,7 @@
         <include name="guava-13.0.1.jar" />
         <include name="findbugs-annotations-2.0.1.jar" />
         <include name="findbugs-jsr305-2.0.1.jar" />
+        <include name="derby-10.9.1.0.jar"/>
     </patternset>
 
     <path id="classpath">
@@ -131,20 +136,35 @@
            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}'"/>
+      <echo message="Running thrift on '${thrift.dir}'"/>
+      <apply executable="./thrift/compiler/cpp/thrift">
+	<fileset dir="${thrift.dir}" casesensitive="yes">
+	  <include name="**/*.thrift"/>
+	</fileset>
+	<arg value="--strict"/>
+	<arg value="-v"/>
+	<arg value="--gen"/>
+	<arg value="java"/>
+	<arg value="-o"/>
+	<arg value="${thrift.out.dir}/.."/>
+      </apply>
+      <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="do-gen-thrift">
+        <exec executable="./thrift/compiler/cpp/thrift" dir="${basedir}" failonerror="true">
+            <arg line="--strict -v --gen java -o ${thrift.out.dir}/.. '${foreach.file}'"/>
         </exec>
         <!-- Get rid of annoying warnings in thrift java: at annotations -->
+
         <echo message="Adding @SuppressWarning annotations"/>
         <replaceregexp byline="true">
             <regexp pattern="^public "/>
diff --git a/lib/derby-10.9.1.0.jar b/lib/derby-10.9.1.0.jar
new file mode 100644
index 0000000000000000000000000000000000000000..26feece9e6b1568531ffdb38b173736c35f4d260
Binary files /dev/null and b/lib/derby-10.9.1.0.jar differ
diff --git a/lib/gen-java/net/floodlightcontroller/packetstreamer/thrift/PacketStreamer.java b/lib/gen-java/net/floodlightcontroller/packetstreamer/thrift/PacketStreamer.java
index f4e8ae512734dbf2253f92b2bde0f2387ca0eed2..d7e454716c55d7359c34b442d0d050783f5645b6 100644
--- a/lib/gen-java/net/floodlightcontroller/packetstreamer/thrift/PacketStreamer.java
+++ b/lib/gen-java/net/floodlightcontroller/packetstreamer/thrift/PacketStreamer.java
@@ -1618,8 +1618,6 @@ import org.slf4j.LoggerFactory;
 
     private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
       try {
-        // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
-        __isset_bit_vector = new BitSet(1);
         read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
       } catch (org.apache.thrift.TException te) {
         throw new java.io.IOException(te);
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/AsyncMessageHeader.java b/lib/gen-java/org/sdnplatform/sync/thrift/AsyncMessageHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a454a2d5a89f12b4578aa4eee4b4714ce650ba0
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/AsyncMessageHeader.java
@@ -0,0 +1,315 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class AsyncMessageHeader implements org.apache.thrift.TBase<AsyncMessageHeader, AsyncMessageHeader._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("AsyncMessageHeader");
+
+  private static final org.apache.thrift.protocol.TField TRANSACTION_ID_FIELD_DESC = new org.apache.thrift.protocol.TField("transactionId", org.apache.thrift.protocol.TType.I32, (short)1);
+
+  public int transactionId; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    TRANSACTION_ID((short)1, "transactionId");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // TRANSACTION_ID
+          return TRANSACTION_ID;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __TRANSACTIONID_ISSET_ID = 0;
+  private BitSet __isset_bit_vector = new BitSet(1);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.TRANSACTION_ID, new org.apache.thrift.meta_data.FieldMetaData("transactionId", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(AsyncMessageHeader.class, metaDataMap);
+  }
+
+  public AsyncMessageHeader() {
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public AsyncMessageHeader(AsyncMessageHeader other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    this.transactionId = other.transactionId;
+  }
+
+  public AsyncMessageHeader deepCopy() {
+    return new AsyncMessageHeader(this);
+  }
+
+  @Override
+  public void clear() {
+    setTransactionIdIsSet(false);
+    this.transactionId = 0;
+  }
+
+  public int getTransactionId() {
+    return this.transactionId;
+  }
+
+  public AsyncMessageHeader setTransactionId(int transactionId) {
+    this.transactionId = transactionId;
+    setTransactionIdIsSet(true);
+    return this;
+  }
+
+  public void unsetTransactionId() {
+    __isset_bit_vector.clear(__TRANSACTIONID_ISSET_ID);
+  }
+
+  /** Returns true if field transactionId is set (has been assigned a value) and false otherwise */
+  public boolean isSetTransactionId() {
+    return __isset_bit_vector.get(__TRANSACTIONID_ISSET_ID);
+  }
+
+  public void setTransactionIdIsSet(boolean value) {
+    __isset_bit_vector.set(__TRANSACTIONID_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case TRANSACTION_ID:
+      if (value == null) {
+        unsetTransactionId();
+      } else {
+        setTransactionId((Integer)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case TRANSACTION_ID:
+      return Integer.valueOf(getTransactionId());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case TRANSACTION_ID:
+      return isSetTransactionId();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof AsyncMessageHeader)
+      return this.equals((AsyncMessageHeader)that);
+    return false;
+  }
+
+  public boolean equals(AsyncMessageHeader that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_transactionId = true && this.isSetTransactionId();
+    boolean that_present_transactionId = true && that.isSetTransactionId();
+    if (this_present_transactionId || that_present_transactionId) {
+      if (!(this_present_transactionId && that_present_transactionId))
+        return false;
+      if (this.transactionId != that.transactionId)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(AsyncMessageHeader other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    AsyncMessageHeader typedOther = (AsyncMessageHeader)other;
+
+    lastComparison = Boolean.valueOf(isSetTransactionId()).compareTo(typedOther.isSetTransactionId());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetTransactionId()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.transactionId, typedOther.transactionId);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // TRANSACTION_ID
+          if (field.type == org.apache.thrift.protocol.TType.I32) {
+            this.transactionId = iprot.readI32();
+            setTransactionIdIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (isSetTransactionId()) {
+      oprot.writeFieldBegin(TRANSACTION_ID_FIELD_DESC);
+      oprot.writeI32(this.transactionId);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("AsyncMessageHeader(");
+    boolean first = true;
+
+    if (isSetTransactionId()) {
+      sb.append("transactionId:");
+      sb.append(this.transactionId);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/ClockEntry.java b/lib/gen-java/org/sdnplatform/sync/thrift/ClockEntry.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a199b072d2d12b29583b74aaf8b60b01f2bb17f
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/ClockEntry.java
@@ -0,0 +1,411 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class ClockEntry implements org.apache.thrift.TBase<ClockEntry, ClockEntry._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ClockEntry");
+
+  private static final org.apache.thrift.protocol.TField NODE_ID_FIELD_DESC = new org.apache.thrift.protocol.TField("nodeId", org.apache.thrift.protocol.TType.I16, (short)1);
+  private static final org.apache.thrift.protocol.TField VERSION_FIELD_DESC = new org.apache.thrift.protocol.TField("version", org.apache.thrift.protocol.TType.I64, (short)2);
+
+  public short nodeId; // required
+  public long version; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    NODE_ID((short)1, "nodeId"),
+    VERSION((short)2, "version");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // NODE_ID
+          return NODE_ID;
+        case 2: // VERSION
+          return VERSION;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __NODEID_ISSET_ID = 0;
+  private static final int __VERSION_ISSET_ID = 1;
+  private BitSet __isset_bit_vector = new BitSet(2);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.NODE_ID, new org.apache.thrift.meta_data.FieldMetaData("nodeId", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I16)));
+    tmpMap.put(_Fields.VERSION, new org.apache.thrift.meta_data.FieldMetaData("version", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(ClockEntry.class, metaDataMap);
+  }
+
+  public ClockEntry() {
+  }
+
+  public ClockEntry(
+    short nodeId,
+    long version)
+  {
+    this();
+    this.nodeId = nodeId;
+    setNodeIdIsSet(true);
+    this.version = version;
+    setVersionIsSet(true);
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public ClockEntry(ClockEntry other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    this.nodeId = other.nodeId;
+    this.version = other.version;
+  }
+
+  public ClockEntry deepCopy() {
+    return new ClockEntry(this);
+  }
+
+  @Override
+  public void clear() {
+    setNodeIdIsSet(false);
+    this.nodeId = 0;
+    setVersionIsSet(false);
+    this.version = 0;
+  }
+
+  public short getNodeId() {
+    return this.nodeId;
+  }
+
+  public ClockEntry setNodeId(short nodeId) {
+    this.nodeId = nodeId;
+    setNodeIdIsSet(true);
+    return this;
+  }
+
+  public void unsetNodeId() {
+    __isset_bit_vector.clear(__NODEID_ISSET_ID);
+  }
+
+  /** Returns true if field nodeId is set (has been assigned a value) and false otherwise */
+  public boolean isSetNodeId() {
+    return __isset_bit_vector.get(__NODEID_ISSET_ID);
+  }
+
+  public void setNodeIdIsSet(boolean value) {
+    __isset_bit_vector.set(__NODEID_ISSET_ID, value);
+  }
+
+  public long getVersion() {
+    return this.version;
+  }
+
+  public ClockEntry setVersion(long version) {
+    this.version = version;
+    setVersionIsSet(true);
+    return this;
+  }
+
+  public void unsetVersion() {
+    __isset_bit_vector.clear(__VERSION_ISSET_ID);
+  }
+
+  /** Returns true if field version is set (has been assigned a value) and false otherwise */
+  public boolean isSetVersion() {
+    return __isset_bit_vector.get(__VERSION_ISSET_ID);
+  }
+
+  public void setVersionIsSet(boolean value) {
+    __isset_bit_vector.set(__VERSION_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case NODE_ID:
+      if (value == null) {
+        unsetNodeId();
+      } else {
+        setNodeId((Short)value);
+      }
+      break;
+
+    case VERSION:
+      if (value == null) {
+        unsetVersion();
+      } else {
+        setVersion((Long)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case NODE_ID:
+      return Short.valueOf(getNodeId());
+
+    case VERSION:
+      return Long.valueOf(getVersion());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case NODE_ID:
+      return isSetNodeId();
+    case VERSION:
+      return isSetVersion();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof ClockEntry)
+      return this.equals((ClockEntry)that);
+    return false;
+  }
+
+  public boolean equals(ClockEntry that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_nodeId = true;
+    boolean that_present_nodeId = true;
+    if (this_present_nodeId || that_present_nodeId) {
+      if (!(this_present_nodeId && that_present_nodeId))
+        return false;
+      if (this.nodeId != that.nodeId)
+        return false;
+    }
+
+    boolean this_present_version = true;
+    boolean that_present_version = true;
+    if (this_present_version || that_present_version) {
+      if (!(this_present_version && that_present_version))
+        return false;
+      if (this.version != that.version)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(ClockEntry other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    ClockEntry typedOther = (ClockEntry)other;
+
+    lastComparison = Boolean.valueOf(isSetNodeId()).compareTo(typedOther.isSetNodeId());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetNodeId()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.nodeId, typedOther.nodeId);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetVersion()).compareTo(typedOther.isSetVersion());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetVersion()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.version, typedOther.version);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // NODE_ID
+          if (field.type == org.apache.thrift.protocol.TType.I16) {
+            this.nodeId = iprot.readI16();
+            setNodeIdIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // VERSION
+          if (field.type == org.apache.thrift.protocol.TType.I64) {
+            this.version = iprot.readI64();
+            setVersionIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    if (!isSetNodeId()) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'nodeId' was not found in serialized data! Struct: " + toString());
+    }
+    if (!isSetVersion()) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'version' was not found in serialized data! Struct: " + toString());
+    }
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    oprot.writeFieldBegin(NODE_ID_FIELD_DESC);
+    oprot.writeI16(this.nodeId);
+    oprot.writeFieldEnd();
+    oprot.writeFieldBegin(VERSION_FIELD_DESC);
+    oprot.writeI64(this.version);
+    oprot.writeFieldEnd();
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("ClockEntry(");
+    boolean first = true;
+
+    sb.append("nodeId:");
+    sb.append(this.nodeId);
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("version:");
+    sb.append(this.version);
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    // alas, we cannot check 'nodeId' because it's a primitive and you chose the non-beans generator.
+    // alas, we cannot check 'version' because it's a primitive and you chose the non-beans generator.
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/Constants.java b/lib/gen-java/org/sdnplatform/sync/thrift/Constants.java
new file mode 100644
index 0000000000000000000000000000000000000000..f134de66a2dc5b65feaece382bc63c638d526b4e
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/Constants.java
@@ -0,0 +1,27 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class Constants {
+
+  public static final String VERSION = "1.0.0";
+
+}
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/CursorRequestMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/CursorRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbca2d5f5261ba8a0efb6e2a65306f1b2679b913
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/CursorRequestMessage.java
@@ -0,0 +1,589 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class CursorRequestMessage implements org.apache.thrift.TBase<CursorRequestMessage, CursorRequestMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("CursorRequestMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField STORE_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("storeName", org.apache.thrift.protocol.TType.STRING, (short)2);
+  private static final org.apache.thrift.protocol.TField CURSOR_ID_FIELD_DESC = new org.apache.thrift.protocol.TField("cursorId", org.apache.thrift.protocol.TType.I32, (short)3);
+  private static final org.apache.thrift.protocol.TField CLOSE_FIELD_DESC = new org.apache.thrift.protocol.TField("close", org.apache.thrift.protocol.TType.BOOL, (short)4);
+
+  public AsyncMessageHeader header; // required
+  public String storeName; // required
+  public int cursorId; // required
+  public boolean close; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    STORE_NAME((short)2, "storeName"),
+    CURSOR_ID((short)3, "cursorId"),
+    CLOSE((short)4, "close");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // STORE_NAME
+          return STORE_NAME;
+        case 3: // CURSOR_ID
+          return CURSOR_ID;
+        case 4: // CLOSE
+          return CLOSE;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __CURSORID_ISSET_ID = 0;
+  private static final int __CLOSE_ISSET_ID = 1;
+  private BitSet __isset_bit_vector = new BitSet(2);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.STORE_NAME, new org.apache.thrift.meta_data.FieldMetaData("storeName", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.CURSOR_ID, new org.apache.thrift.meta_data.FieldMetaData("cursorId", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+    tmpMap.put(_Fields.CLOSE, new org.apache.thrift.meta_data.FieldMetaData("close", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.BOOL)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(CursorRequestMessage.class, metaDataMap);
+  }
+
+  public CursorRequestMessage() {
+  }
+
+  public CursorRequestMessage(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public CursorRequestMessage(CursorRequestMessage other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetStoreName()) {
+      this.storeName = other.storeName;
+    }
+    this.cursorId = other.cursorId;
+    this.close = other.close;
+  }
+
+  public CursorRequestMessage deepCopy() {
+    return new CursorRequestMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.storeName = null;
+    setCursorIdIsSet(false);
+    this.cursorId = 0;
+    setCloseIsSet(false);
+    this.close = false;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public CursorRequestMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public String getStoreName() {
+    return this.storeName;
+  }
+
+  public CursorRequestMessage setStoreName(String storeName) {
+    this.storeName = storeName;
+    return this;
+  }
+
+  public void unsetStoreName() {
+    this.storeName = null;
+  }
+
+  /** Returns true if field storeName is set (has been assigned a value) and false otherwise */
+  public boolean isSetStoreName() {
+    return this.storeName != null;
+  }
+
+  public void setStoreNameIsSet(boolean value) {
+    if (!value) {
+      this.storeName = null;
+    }
+  }
+
+  public int getCursorId() {
+    return this.cursorId;
+  }
+
+  public CursorRequestMessage setCursorId(int cursorId) {
+    this.cursorId = cursorId;
+    setCursorIdIsSet(true);
+    return this;
+  }
+
+  public void unsetCursorId() {
+    __isset_bit_vector.clear(__CURSORID_ISSET_ID);
+  }
+
+  /** Returns true if field cursorId is set (has been assigned a value) and false otherwise */
+  public boolean isSetCursorId() {
+    return __isset_bit_vector.get(__CURSORID_ISSET_ID);
+  }
+
+  public void setCursorIdIsSet(boolean value) {
+    __isset_bit_vector.set(__CURSORID_ISSET_ID, value);
+  }
+
+  public boolean isClose() {
+    return this.close;
+  }
+
+  public CursorRequestMessage setClose(boolean close) {
+    this.close = close;
+    setCloseIsSet(true);
+    return this;
+  }
+
+  public void unsetClose() {
+    __isset_bit_vector.clear(__CLOSE_ISSET_ID);
+  }
+
+  /** Returns true if field close is set (has been assigned a value) and false otherwise */
+  public boolean isSetClose() {
+    return __isset_bit_vector.get(__CLOSE_ISSET_ID);
+  }
+
+  public void setCloseIsSet(boolean value) {
+    __isset_bit_vector.set(__CLOSE_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case STORE_NAME:
+      if (value == null) {
+        unsetStoreName();
+      } else {
+        setStoreName((String)value);
+      }
+      break;
+
+    case CURSOR_ID:
+      if (value == null) {
+        unsetCursorId();
+      } else {
+        setCursorId((Integer)value);
+      }
+      break;
+
+    case CLOSE:
+      if (value == null) {
+        unsetClose();
+      } else {
+        setClose((Boolean)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case STORE_NAME:
+      return getStoreName();
+
+    case CURSOR_ID:
+      return Integer.valueOf(getCursorId());
+
+    case CLOSE:
+      return Boolean.valueOf(isClose());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case STORE_NAME:
+      return isSetStoreName();
+    case CURSOR_ID:
+      return isSetCursorId();
+    case CLOSE:
+      return isSetClose();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof CursorRequestMessage)
+      return this.equals((CursorRequestMessage)that);
+    return false;
+  }
+
+  public boolean equals(CursorRequestMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_storeName = true && this.isSetStoreName();
+    boolean that_present_storeName = true && that.isSetStoreName();
+    if (this_present_storeName || that_present_storeName) {
+      if (!(this_present_storeName && that_present_storeName))
+        return false;
+      if (!this.storeName.equals(that.storeName))
+        return false;
+    }
+
+    boolean this_present_cursorId = true && this.isSetCursorId();
+    boolean that_present_cursorId = true && that.isSetCursorId();
+    if (this_present_cursorId || that_present_cursorId) {
+      if (!(this_present_cursorId && that_present_cursorId))
+        return false;
+      if (this.cursorId != that.cursorId)
+        return false;
+    }
+
+    boolean this_present_close = true && this.isSetClose();
+    boolean that_present_close = true && that.isSetClose();
+    if (this_present_close || that_present_close) {
+      if (!(this_present_close && that_present_close))
+        return false;
+      if (this.close != that.close)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(CursorRequestMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    CursorRequestMessage typedOther = (CursorRequestMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetStoreName()).compareTo(typedOther.isSetStoreName());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStoreName()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.storeName, typedOther.storeName);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetCursorId()).compareTo(typedOther.isSetCursorId());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetCursorId()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.cursorId, typedOther.cursorId);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetClose()).compareTo(typedOther.isSetClose());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetClose()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.close, typedOther.close);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // STORE_NAME
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.storeName = iprot.readString();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // CURSOR_ID
+          if (field.type == org.apache.thrift.protocol.TType.I32) {
+            this.cursorId = iprot.readI32();
+            setCursorIdIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 4: // CLOSE
+          if (field.type == org.apache.thrift.protocol.TType.BOOL) {
+            this.close = iprot.readBool();
+            setCloseIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.storeName != null) {
+      if (isSetStoreName()) {
+        oprot.writeFieldBegin(STORE_NAME_FIELD_DESC);
+        oprot.writeString(this.storeName);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (isSetCursorId()) {
+      oprot.writeFieldBegin(CURSOR_ID_FIELD_DESC);
+      oprot.writeI32(this.cursorId);
+      oprot.writeFieldEnd();
+    }
+    if (isSetClose()) {
+      oprot.writeFieldBegin(CLOSE_FIELD_DESC);
+      oprot.writeBool(this.close);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("CursorRequestMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (isSetStoreName()) {
+      if (!first) sb.append(", ");
+      sb.append("storeName:");
+      if (this.storeName == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.storeName);
+      }
+      first = false;
+    }
+    if (isSetCursorId()) {
+      if (!first) sb.append(", ");
+      sb.append("cursorId:");
+      sb.append(this.cursorId);
+      first = false;
+    }
+    if (isSetClose()) {
+      if (!first) sb.append(", ");
+      sb.append("close:");
+      sb.append(this.close);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/CursorResponseMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/CursorResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9aa036fa141d4a4e12a6f028373f0012d41ea41
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/CursorResponseMessage.java
@@ -0,0 +1,543 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class CursorResponseMessage implements org.apache.thrift.TBase<CursorResponseMessage, CursorResponseMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("CursorResponseMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField CURSOR_ID_FIELD_DESC = new org.apache.thrift.protocol.TField("cursorId", org.apache.thrift.protocol.TType.I32, (short)2);
+  private static final org.apache.thrift.protocol.TField VALUES_FIELD_DESC = new org.apache.thrift.protocol.TField("values", org.apache.thrift.protocol.TType.LIST, (short)3);
+
+  public AsyncMessageHeader header; // required
+  public int cursorId; // required
+  public List<KeyedValues> values; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    CURSOR_ID((short)2, "cursorId"),
+    VALUES((short)3, "values");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // CURSOR_ID
+          return CURSOR_ID;
+        case 3: // VALUES
+          return VALUES;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __CURSORID_ISSET_ID = 0;
+  private BitSet __isset_bit_vector = new BitSet(1);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.CURSOR_ID, new org.apache.thrift.meta_data.FieldMetaData("cursorId", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+    tmpMap.put(_Fields.VALUES, new org.apache.thrift.meta_data.FieldMetaData("values", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, KeyedValues.class))));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(CursorResponseMessage.class, metaDataMap);
+  }
+
+  public CursorResponseMessage() {
+  }
+
+  public CursorResponseMessage(
+    AsyncMessageHeader header,
+    int cursorId,
+    List<KeyedValues> values)
+  {
+    this();
+    this.header = header;
+    this.cursorId = cursorId;
+    setCursorIdIsSet(true);
+    this.values = values;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public CursorResponseMessage(CursorResponseMessage other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    this.cursorId = other.cursorId;
+    if (other.isSetValues()) {
+      List<KeyedValues> __this__values = new ArrayList<KeyedValues>();
+      for (KeyedValues other_element : other.values) {
+        __this__values.add(new KeyedValues(other_element));
+      }
+      this.values = __this__values;
+    }
+  }
+
+  public CursorResponseMessage deepCopy() {
+    return new CursorResponseMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    setCursorIdIsSet(false);
+    this.cursorId = 0;
+    this.values = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public CursorResponseMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public int getCursorId() {
+    return this.cursorId;
+  }
+
+  public CursorResponseMessage setCursorId(int cursorId) {
+    this.cursorId = cursorId;
+    setCursorIdIsSet(true);
+    return this;
+  }
+
+  public void unsetCursorId() {
+    __isset_bit_vector.clear(__CURSORID_ISSET_ID);
+  }
+
+  /** Returns true if field cursorId is set (has been assigned a value) and false otherwise */
+  public boolean isSetCursorId() {
+    return __isset_bit_vector.get(__CURSORID_ISSET_ID);
+  }
+
+  public void setCursorIdIsSet(boolean value) {
+    __isset_bit_vector.set(__CURSORID_ISSET_ID, value);
+  }
+
+  public int getValuesSize() {
+    return (this.values == null) ? 0 : this.values.size();
+  }
+
+  public java.util.Iterator<KeyedValues> getValuesIterator() {
+    return (this.values == null) ? null : this.values.iterator();
+  }
+
+  public void addToValues(KeyedValues elem) {
+    if (this.values == null) {
+      this.values = new ArrayList<KeyedValues>();
+    }
+    this.values.add(elem);
+  }
+
+  public List<KeyedValues> getValues() {
+    return this.values;
+  }
+
+  public CursorResponseMessage setValues(List<KeyedValues> values) {
+    this.values = values;
+    return this;
+  }
+
+  public void unsetValues() {
+    this.values = null;
+  }
+
+  /** Returns true if field values is set (has been assigned a value) and false otherwise */
+  public boolean isSetValues() {
+    return this.values != null;
+  }
+
+  public void setValuesIsSet(boolean value) {
+    if (!value) {
+      this.values = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case CURSOR_ID:
+      if (value == null) {
+        unsetCursorId();
+      } else {
+        setCursorId((Integer)value);
+      }
+      break;
+
+    case VALUES:
+      if (value == null) {
+        unsetValues();
+      } else {
+        setValues((List<KeyedValues>)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case CURSOR_ID:
+      return Integer.valueOf(getCursorId());
+
+    case VALUES:
+      return getValues();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case CURSOR_ID:
+      return isSetCursorId();
+    case VALUES:
+      return isSetValues();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof CursorResponseMessage)
+      return this.equals((CursorResponseMessage)that);
+    return false;
+  }
+
+  public boolean equals(CursorResponseMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_cursorId = true;
+    boolean that_present_cursorId = true;
+    if (this_present_cursorId || that_present_cursorId) {
+      if (!(this_present_cursorId && that_present_cursorId))
+        return false;
+      if (this.cursorId != that.cursorId)
+        return false;
+    }
+
+    boolean this_present_values = true && this.isSetValues();
+    boolean that_present_values = true && that.isSetValues();
+    if (this_present_values || that_present_values) {
+      if (!(this_present_values && that_present_values))
+        return false;
+      if (!this.values.equals(that.values))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(CursorResponseMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    CursorResponseMessage typedOther = (CursorResponseMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetCursorId()).compareTo(typedOther.isSetCursorId());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetCursorId()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.cursorId, typedOther.cursorId);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetValues()).compareTo(typedOther.isSetValues());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetValues()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.values, typedOther.values);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // CURSOR_ID
+          if (field.type == org.apache.thrift.protocol.TType.I32) {
+            this.cursorId = iprot.readI32();
+            setCursorIdIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // VALUES
+          if (field.type == org.apache.thrift.protocol.TType.LIST) {
+            {
+              org.apache.thrift.protocol.TList _list28 = iprot.readListBegin();
+              this.values = new ArrayList<KeyedValues>(_list28.size);
+              for (int _i29 = 0; _i29 < _list28.size; ++_i29)
+              {
+                KeyedValues _elem30; // required
+                _elem30 = new KeyedValues();
+                _elem30.read(iprot);
+                this.values.add(_elem30);
+              }
+              iprot.readListEnd();
+            }
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    if (!isSetCursorId()) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'cursorId' was not found in serialized data! Struct: " + toString());
+    }
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldBegin(CURSOR_ID_FIELD_DESC);
+    oprot.writeI32(this.cursorId);
+    oprot.writeFieldEnd();
+    if (this.values != null) {
+      oprot.writeFieldBegin(VALUES_FIELD_DESC);
+      {
+        oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, this.values.size()));
+        for (KeyedValues _iter31 : this.values)
+        {
+          _iter31.write(oprot);
+        }
+        oprot.writeListEnd();
+      }
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("CursorResponseMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("cursorId:");
+    sb.append(this.cursorId);
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("values:");
+    if (this.values == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.values);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+    // alas, we cannot check 'cursorId' because it's a primitive and you chose the non-beans generator.
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/DeleteRequestMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/DeleteRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9f6a09125156cd9a2b39752b5e021074d43b2a9
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/DeleteRequestMessage.java
@@ -0,0 +1,610 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class DeleteRequestMessage implements org.apache.thrift.TBase<DeleteRequestMessage, DeleteRequestMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("DeleteRequestMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField STORE_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("storeName", org.apache.thrift.protocol.TType.STRING, (short)2);
+  private static final org.apache.thrift.protocol.TField KEY_FIELD_DESC = new org.apache.thrift.protocol.TField("key", org.apache.thrift.protocol.TType.STRING, (short)3);
+  private static final org.apache.thrift.protocol.TField VERSION_FIELD_DESC = new org.apache.thrift.protocol.TField("version", org.apache.thrift.protocol.TType.STRUCT, (short)4);
+
+  public AsyncMessageHeader header; // required
+  public String storeName; // required
+  public ByteBuffer key; // required
+  public VectorClock version; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    STORE_NAME((short)2, "storeName"),
+    KEY((short)3, "key"),
+    VERSION((short)4, "version");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // STORE_NAME
+          return STORE_NAME;
+        case 3: // KEY
+          return KEY;
+        case 4: // VERSION
+          return VERSION;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.STORE_NAME, new org.apache.thrift.meta_data.FieldMetaData("storeName", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.KEY, new org.apache.thrift.meta_data.FieldMetaData("key", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING        , true)));
+    tmpMap.put(_Fields.VERSION, new org.apache.thrift.meta_data.FieldMetaData("version", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, VectorClock.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(DeleteRequestMessage.class, metaDataMap);
+  }
+
+  public DeleteRequestMessage() {
+  }
+
+  public DeleteRequestMessage(
+    AsyncMessageHeader header,
+    String storeName,
+    ByteBuffer key)
+  {
+    this();
+    this.header = header;
+    this.storeName = storeName;
+    this.key = key;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public DeleteRequestMessage(DeleteRequestMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetStoreName()) {
+      this.storeName = other.storeName;
+    }
+    if (other.isSetKey()) {
+      this.key = org.apache.thrift.TBaseHelper.copyBinary(other.key);
+;
+    }
+    if (other.isSetVersion()) {
+      this.version = new VectorClock(other.version);
+    }
+  }
+
+  public DeleteRequestMessage deepCopy() {
+    return new DeleteRequestMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.storeName = null;
+    this.key = null;
+    this.version = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public DeleteRequestMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public String getStoreName() {
+    return this.storeName;
+  }
+
+  public DeleteRequestMessage setStoreName(String storeName) {
+    this.storeName = storeName;
+    return this;
+  }
+
+  public void unsetStoreName() {
+    this.storeName = null;
+  }
+
+  /** Returns true if field storeName is set (has been assigned a value) and false otherwise */
+  public boolean isSetStoreName() {
+    return this.storeName != null;
+  }
+
+  public void setStoreNameIsSet(boolean value) {
+    if (!value) {
+      this.storeName = null;
+    }
+  }
+
+  public byte[] getKey() {
+    setKey(org.apache.thrift.TBaseHelper.rightSize(key));
+    return key == null ? null : key.array();
+  }
+
+  public ByteBuffer bufferForKey() {
+    return key;
+  }
+
+  public DeleteRequestMessage setKey(byte[] key) {
+    setKey(key == null ? (ByteBuffer)null : ByteBuffer.wrap(key));
+    return this;
+  }
+
+  public DeleteRequestMessage setKey(ByteBuffer key) {
+    this.key = key;
+    return this;
+  }
+
+  public void unsetKey() {
+    this.key = null;
+  }
+
+  /** Returns true if field key is set (has been assigned a value) and false otherwise */
+  public boolean isSetKey() {
+    return this.key != null;
+  }
+
+  public void setKeyIsSet(boolean value) {
+    if (!value) {
+      this.key = null;
+    }
+  }
+
+  public VectorClock getVersion() {
+    return this.version;
+  }
+
+  public DeleteRequestMessage setVersion(VectorClock version) {
+    this.version = version;
+    return this;
+  }
+
+  public void unsetVersion() {
+    this.version = null;
+  }
+
+  /** Returns true if field version is set (has been assigned a value) and false otherwise */
+  public boolean isSetVersion() {
+    return this.version != null;
+  }
+
+  public void setVersionIsSet(boolean value) {
+    if (!value) {
+      this.version = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case STORE_NAME:
+      if (value == null) {
+        unsetStoreName();
+      } else {
+        setStoreName((String)value);
+      }
+      break;
+
+    case KEY:
+      if (value == null) {
+        unsetKey();
+      } else {
+        setKey((ByteBuffer)value);
+      }
+      break;
+
+    case VERSION:
+      if (value == null) {
+        unsetVersion();
+      } else {
+        setVersion((VectorClock)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case STORE_NAME:
+      return getStoreName();
+
+    case KEY:
+      return getKey();
+
+    case VERSION:
+      return getVersion();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case STORE_NAME:
+      return isSetStoreName();
+    case KEY:
+      return isSetKey();
+    case VERSION:
+      return isSetVersion();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof DeleteRequestMessage)
+      return this.equals((DeleteRequestMessage)that);
+    return false;
+  }
+
+  public boolean equals(DeleteRequestMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_storeName = true && this.isSetStoreName();
+    boolean that_present_storeName = true && that.isSetStoreName();
+    if (this_present_storeName || that_present_storeName) {
+      if (!(this_present_storeName && that_present_storeName))
+        return false;
+      if (!this.storeName.equals(that.storeName))
+        return false;
+    }
+
+    boolean this_present_key = true && this.isSetKey();
+    boolean that_present_key = true && that.isSetKey();
+    if (this_present_key || that_present_key) {
+      if (!(this_present_key && that_present_key))
+        return false;
+      if (!this.key.equals(that.key))
+        return false;
+    }
+
+    boolean this_present_version = true && this.isSetVersion();
+    boolean that_present_version = true && that.isSetVersion();
+    if (this_present_version || that_present_version) {
+      if (!(this_present_version && that_present_version))
+        return false;
+      if (!this.version.equals(that.version))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(DeleteRequestMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    DeleteRequestMessage typedOther = (DeleteRequestMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetStoreName()).compareTo(typedOther.isSetStoreName());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStoreName()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.storeName, typedOther.storeName);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetKey()).compareTo(typedOther.isSetKey());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetKey()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.key, typedOther.key);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetVersion()).compareTo(typedOther.isSetVersion());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetVersion()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.version, typedOther.version);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // STORE_NAME
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.storeName = iprot.readString();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // KEY
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.key = iprot.readBinary();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 4: // VERSION
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.version = new VectorClock();
+            this.version.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.storeName != null) {
+      oprot.writeFieldBegin(STORE_NAME_FIELD_DESC);
+      oprot.writeString(this.storeName);
+      oprot.writeFieldEnd();
+    }
+    if (this.key != null) {
+      oprot.writeFieldBegin(KEY_FIELD_DESC);
+      oprot.writeBinary(this.key);
+      oprot.writeFieldEnd();
+    }
+    if (this.version != null) {
+      if (isSetVersion()) {
+        oprot.writeFieldBegin(VERSION_FIELD_DESC);
+        this.version.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("DeleteRequestMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("storeName:");
+    if (this.storeName == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.storeName);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("key:");
+    if (this.key == null) {
+      sb.append("null");
+    } else {
+      org.apache.thrift.TBaseHelper.toString(this.key, sb);
+    }
+    first = false;
+    if (isSetVersion()) {
+      if (!first) sb.append(", ");
+      sb.append("version:");
+      if (this.version == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.version);
+      }
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+    if (storeName == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'storeName' was not present! Struct: " + toString());
+    }
+    if (key == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/DeleteResponseMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/DeleteResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..82bc0803d20e171834bb26f47a43c324dbf8af19
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/DeleteResponseMessage.java
@@ -0,0 +1,407 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class DeleteResponseMessage implements org.apache.thrift.TBase<DeleteResponseMessage, DeleteResponseMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("DeleteResponseMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField DELETED_FIELD_DESC = new org.apache.thrift.protocol.TField("deleted", org.apache.thrift.protocol.TType.BOOL, (short)2);
+
+  public AsyncMessageHeader header; // required
+  public boolean deleted; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    DELETED((short)2, "deleted");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // DELETED
+          return DELETED;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __DELETED_ISSET_ID = 0;
+  private BitSet __isset_bit_vector = new BitSet(1);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.DELETED, new org.apache.thrift.meta_data.FieldMetaData("deleted", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.BOOL)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(DeleteResponseMessage.class, metaDataMap);
+  }
+
+  public DeleteResponseMessage() {
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public DeleteResponseMessage(DeleteResponseMessage other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    this.deleted = other.deleted;
+  }
+
+  public DeleteResponseMessage deepCopy() {
+    return new DeleteResponseMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    setDeletedIsSet(false);
+    this.deleted = false;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public DeleteResponseMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public boolean isDeleted() {
+    return this.deleted;
+  }
+
+  public DeleteResponseMessage setDeleted(boolean deleted) {
+    this.deleted = deleted;
+    setDeletedIsSet(true);
+    return this;
+  }
+
+  public void unsetDeleted() {
+    __isset_bit_vector.clear(__DELETED_ISSET_ID);
+  }
+
+  /** Returns true if field deleted is set (has been assigned a value) and false otherwise */
+  public boolean isSetDeleted() {
+    return __isset_bit_vector.get(__DELETED_ISSET_ID);
+  }
+
+  public void setDeletedIsSet(boolean value) {
+    __isset_bit_vector.set(__DELETED_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case DELETED:
+      if (value == null) {
+        unsetDeleted();
+      } else {
+        setDeleted((Boolean)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case DELETED:
+      return Boolean.valueOf(isDeleted());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case DELETED:
+      return isSetDeleted();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof DeleteResponseMessage)
+      return this.equals((DeleteResponseMessage)that);
+    return false;
+  }
+
+  public boolean equals(DeleteResponseMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_deleted = true && this.isSetDeleted();
+    boolean that_present_deleted = true && that.isSetDeleted();
+    if (this_present_deleted || that_present_deleted) {
+      if (!(this_present_deleted && that_present_deleted))
+        return false;
+      if (this.deleted != that.deleted)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(DeleteResponseMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    DeleteResponseMessage typedOther = (DeleteResponseMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetDeleted()).compareTo(typedOther.isSetDeleted());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetDeleted()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.deleted, typedOther.deleted);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // DELETED
+          if (field.type == org.apache.thrift.protocol.TType.BOOL) {
+            this.deleted = iprot.readBool();
+            setDeletedIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      if (isSetHeader()) {
+        oprot.writeFieldBegin(HEADER_FIELD_DESC);
+        this.header.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (isSetDeleted()) {
+      oprot.writeFieldBegin(DELETED_FIELD_DESC);
+      oprot.writeBool(this.deleted);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("DeleteResponseMessage(");
+    boolean first = true;
+
+    if (isSetHeader()) {
+      sb.append("header:");
+      if (this.header == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.header);
+      }
+      first = false;
+    }
+    if (isSetDeleted()) {
+      if (!first) sb.append(", ");
+      sb.append("deleted:");
+      sb.append(this.deleted);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/EchoReplyMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/EchoReplyMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5fce6cf500415ae3bc40f91275b77d10c41b151
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/EchoReplyMessage.java
@@ -0,0 +1,323 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class EchoReplyMessage implements org.apache.thrift.TBase<EchoReplyMessage, EchoReplyMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("EchoReplyMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+
+  public AsyncMessageHeader header; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(EchoReplyMessage.class, metaDataMap);
+  }
+
+  public EchoReplyMessage() {
+  }
+
+  public EchoReplyMessage(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public EchoReplyMessage(EchoReplyMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+  }
+
+  public EchoReplyMessage deepCopy() {
+    return new EchoReplyMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public EchoReplyMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof EchoReplyMessage)
+      return this.equals((EchoReplyMessage)that);
+    return false;
+  }
+
+  public boolean equals(EchoReplyMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(EchoReplyMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    EchoReplyMessage typedOther = (EchoReplyMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("EchoReplyMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/EchoRequestMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/EchoRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..1d01226d71d93bf92458e43b1bea662b31ee19c5
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/EchoRequestMessage.java
@@ -0,0 +1,323 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class EchoRequestMessage implements org.apache.thrift.TBase<EchoRequestMessage, EchoRequestMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("EchoRequestMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+
+  public AsyncMessageHeader header; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(EchoRequestMessage.class, metaDataMap);
+  }
+
+  public EchoRequestMessage() {
+  }
+
+  public EchoRequestMessage(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public EchoRequestMessage(EchoRequestMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+  }
+
+  public EchoRequestMessage deepCopy() {
+    return new EchoRequestMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public EchoRequestMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof EchoRequestMessage)
+      return this.equals((EchoRequestMessage)that);
+    return false;
+  }
+
+  public boolean equals(EchoRequestMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(EchoRequestMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    EchoRequestMessage typedOther = (EchoRequestMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("EchoRequestMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/ErrorMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/ErrorMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..451afa192b7063fcd627731c54580d077f3c373e
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/ErrorMessage.java
@@ -0,0 +1,522 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class ErrorMessage implements org.apache.thrift.TBase<ErrorMessage, ErrorMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ErrorMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField ERROR_FIELD_DESC = new org.apache.thrift.protocol.TField("error", org.apache.thrift.protocol.TType.STRUCT, (short)2);
+  private static final org.apache.thrift.protocol.TField TYPE_FIELD_DESC = new org.apache.thrift.protocol.TField("type", org.apache.thrift.protocol.TType.I32, (short)3);
+
+  public AsyncMessageHeader header; // required
+  public SyncError error; // required
+  /**
+   * 
+   * @see MessageType
+   */
+  public MessageType type; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    ERROR((short)2, "error"),
+    /**
+     * 
+     * @see MessageType
+     */
+    TYPE((short)3, "type");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // ERROR
+          return ERROR;
+        case 3: // TYPE
+          return TYPE;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.ERROR, new org.apache.thrift.meta_data.FieldMetaData("error", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, SyncError.class)));
+    tmpMap.put(_Fields.TYPE, new org.apache.thrift.meta_data.FieldMetaData("type", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, MessageType.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(ErrorMessage.class, metaDataMap);
+  }
+
+  public ErrorMessage() {
+  }
+
+  public ErrorMessage(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public ErrorMessage(ErrorMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetError()) {
+      this.error = new SyncError(other.error);
+    }
+    if (other.isSetType()) {
+      this.type = other.type;
+    }
+  }
+
+  public ErrorMessage deepCopy() {
+    return new ErrorMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.error = null;
+    this.type = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public ErrorMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public SyncError getError() {
+    return this.error;
+  }
+
+  public ErrorMessage setError(SyncError error) {
+    this.error = error;
+    return this;
+  }
+
+  public void unsetError() {
+    this.error = null;
+  }
+
+  /** Returns true if field error is set (has been assigned a value) and false otherwise */
+  public boolean isSetError() {
+    return this.error != null;
+  }
+
+  public void setErrorIsSet(boolean value) {
+    if (!value) {
+      this.error = null;
+    }
+  }
+
+  /**
+   * 
+   * @see MessageType
+   */
+  public MessageType getType() {
+    return this.type;
+  }
+
+  /**
+   * 
+   * @see MessageType
+   */
+  public ErrorMessage setType(MessageType type) {
+    this.type = type;
+    return this;
+  }
+
+  public void unsetType() {
+    this.type = null;
+  }
+
+  /** Returns true if field type is set (has been assigned a value) and false otherwise */
+  public boolean isSetType() {
+    return this.type != null;
+  }
+
+  public void setTypeIsSet(boolean value) {
+    if (!value) {
+      this.type = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case ERROR:
+      if (value == null) {
+        unsetError();
+      } else {
+        setError((SyncError)value);
+      }
+      break;
+
+    case TYPE:
+      if (value == null) {
+        unsetType();
+      } else {
+        setType((MessageType)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case ERROR:
+      return getError();
+
+    case TYPE:
+      return getType();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case ERROR:
+      return isSetError();
+    case TYPE:
+      return isSetType();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof ErrorMessage)
+      return this.equals((ErrorMessage)that);
+    return false;
+  }
+
+  public boolean equals(ErrorMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_error = true && this.isSetError();
+    boolean that_present_error = true && that.isSetError();
+    if (this_present_error || that_present_error) {
+      if (!(this_present_error && that_present_error))
+        return false;
+      if (!this.error.equals(that.error))
+        return false;
+    }
+
+    boolean this_present_type = true && this.isSetType();
+    boolean that_present_type = true && that.isSetType();
+    if (this_present_type || that_present_type) {
+      if (!(this_present_type && that_present_type))
+        return false;
+      if (!this.type.equals(that.type))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(ErrorMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    ErrorMessage typedOther = (ErrorMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetError()).compareTo(typedOther.isSetError());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetError()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.error, typedOther.error);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetType()).compareTo(typedOther.isSetType());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetType()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.type, typedOther.type);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // ERROR
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.error = new SyncError();
+            this.error.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // TYPE
+          if (field.type == org.apache.thrift.protocol.TType.I32) {
+            this.type = MessageType.findByValue(iprot.readI32());
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.error != null) {
+      if (isSetError()) {
+        oprot.writeFieldBegin(ERROR_FIELD_DESC);
+        this.error.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.type != null) {
+      if (isSetType()) {
+        oprot.writeFieldBegin(TYPE_FIELD_DESC);
+        oprot.writeI32(this.type.getValue());
+        oprot.writeFieldEnd();
+      }
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("ErrorMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (isSetError()) {
+      if (!first) sb.append(", ");
+      sb.append("error:");
+      if (this.error == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.error);
+      }
+      first = false;
+    }
+    if (isSetType()) {
+      if (!first) sb.append(", ");
+      sb.append("type:");
+      if (this.type == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.type);
+      }
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/FullSyncRequestMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/FullSyncRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..20afd157e5bcccddaba97be407129f7f9aa3f91f
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/FullSyncRequestMessage.java
@@ -0,0 +1,323 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class FullSyncRequestMessage implements org.apache.thrift.TBase<FullSyncRequestMessage, FullSyncRequestMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("FullSyncRequestMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+
+  public AsyncMessageHeader header; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(FullSyncRequestMessage.class, metaDataMap);
+  }
+
+  public FullSyncRequestMessage() {
+  }
+
+  public FullSyncRequestMessage(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public FullSyncRequestMessage(FullSyncRequestMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+  }
+
+  public FullSyncRequestMessage deepCopy() {
+    return new FullSyncRequestMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public FullSyncRequestMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof FullSyncRequestMessage)
+      return this.equals((FullSyncRequestMessage)that);
+    return false;
+  }
+
+  public boolean equals(FullSyncRequestMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(FullSyncRequestMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    FullSyncRequestMessage typedOther = (FullSyncRequestMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("FullSyncRequestMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/GetRequestMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/GetRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..83e91ebc9812de13a0768d3cf594107694b98839
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/GetRequestMessage.java
@@ -0,0 +1,518 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class GetRequestMessage implements org.apache.thrift.TBase<GetRequestMessage, GetRequestMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("GetRequestMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField STORE_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("storeName", org.apache.thrift.protocol.TType.STRING, (short)2);
+  private static final org.apache.thrift.protocol.TField KEY_FIELD_DESC = new org.apache.thrift.protocol.TField("key", org.apache.thrift.protocol.TType.STRING, (short)3);
+
+  public AsyncMessageHeader header; // required
+  public String storeName; // required
+  public ByteBuffer key; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    STORE_NAME((short)2, "storeName"),
+    KEY((short)3, "key");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // STORE_NAME
+          return STORE_NAME;
+        case 3: // KEY
+          return KEY;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.STORE_NAME, new org.apache.thrift.meta_data.FieldMetaData("storeName", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.KEY, new org.apache.thrift.meta_data.FieldMetaData("key", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING        , true)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(GetRequestMessage.class, metaDataMap);
+  }
+
+  public GetRequestMessage() {
+  }
+
+  public GetRequestMessage(
+    AsyncMessageHeader header,
+    String storeName,
+    ByteBuffer key)
+  {
+    this();
+    this.header = header;
+    this.storeName = storeName;
+    this.key = key;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public GetRequestMessage(GetRequestMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetStoreName()) {
+      this.storeName = other.storeName;
+    }
+    if (other.isSetKey()) {
+      this.key = org.apache.thrift.TBaseHelper.copyBinary(other.key);
+;
+    }
+  }
+
+  public GetRequestMessage deepCopy() {
+    return new GetRequestMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.storeName = null;
+    this.key = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public GetRequestMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public String getStoreName() {
+    return this.storeName;
+  }
+
+  public GetRequestMessage setStoreName(String storeName) {
+    this.storeName = storeName;
+    return this;
+  }
+
+  public void unsetStoreName() {
+    this.storeName = null;
+  }
+
+  /** Returns true if field storeName is set (has been assigned a value) and false otherwise */
+  public boolean isSetStoreName() {
+    return this.storeName != null;
+  }
+
+  public void setStoreNameIsSet(boolean value) {
+    if (!value) {
+      this.storeName = null;
+    }
+  }
+
+  public byte[] getKey() {
+    setKey(org.apache.thrift.TBaseHelper.rightSize(key));
+    return key == null ? null : key.array();
+  }
+
+  public ByteBuffer bufferForKey() {
+    return key;
+  }
+
+  public GetRequestMessage setKey(byte[] key) {
+    setKey(key == null ? (ByteBuffer)null : ByteBuffer.wrap(key));
+    return this;
+  }
+
+  public GetRequestMessage setKey(ByteBuffer key) {
+    this.key = key;
+    return this;
+  }
+
+  public void unsetKey() {
+    this.key = null;
+  }
+
+  /** Returns true if field key is set (has been assigned a value) and false otherwise */
+  public boolean isSetKey() {
+    return this.key != null;
+  }
+
+  public void setKeyIsSet(boolean value) {
+    if (!value) {
+      this.key = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case STORE_NAME:
+      if (value == null) {
+        unsetStoreName();
+      } else {
+        setStoreName((String)value);
+      }
+      break;
+
+    case KEY:
+      if (value == null) {
+        unsetKey();
+      } else {
+        setKey((ByteBuffer)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case STORE_NAME:
+      return getStoreName();
+
+    case KEY:
+      return getKey();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case STORE_NAME:
+      return isSetStoreName();
+    case KEY:
+      return isSetKey();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof GetRequestMessage)
+      return this.equals((GetRequestMessage)that);
+    return false;
+  }
+
+  public boolean equals(GetRequestMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_storeName = true && this.isSetStoreName();
+    boolean that_present_storeName = true && that.isSetStoreName();
+    if (this_present_storeName || that_present_storeName) {
+      if (!(this_present_storeName && that_present_storeName))
+        return false;
+      if (!this.storeName.equals(that.storeName))
+        return false;
+    }
+
+    boolean this_present_key = true && this.isSetKey();
+    boolean that_present_key = true && that.isSetKey();
+    if (this_present_key || that_present_key) {
+      if (!(this_present_key && that_present_key))
+        return false;
+      if (!this.key.equals(that.key))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(GetRequestMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    GetRequestMessage typedOther = (GetRequestMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetStoreName()).compareTo(typedOther.isSetStoreName());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStoreName()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.storeName, typedOther.storeName);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetKey()).compareTo(typedOther.isSetKey());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetKey()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.key, typedOther.key);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // STORE_NAME
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.storeName = iprot.readString();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // KEY
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.key = iprot.readBinary();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.storeName != null) {
+      oprot.writeFieldBegin(STORE_NAME_FIELD_DESC);
+      oprot.writeString(this.storeName);
+      oprot.writeFieldEnd();
+    }
+    if (this.key != null) {
+      oprot.writeFieldBegin(KEY_FIELD_DESC);
+      oprot.writeBinary(this.key);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("GetRequestMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("storeName:");
+    if (this.storeName == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.storeName);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("key:");
+    if (this.key == null) {
+      sb.append("null");
+    } else {
+      org.apache.thrift.TBaseHelper.toString(this.key, sb);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+    if (storeName == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'storeName' was not present! Struct: " + toString());
+    }
+    if (key == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/GetResponseMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/GetResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..5bfe48dbdcaa85ee1ad6d89b955c84481376c437
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/GetResponseMessage.java
@@ -0,0 +1,542 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class GetResponseMessage implements org.apache.thrift.TBase<GetResponseMessage, GetResponseMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("GetResponseMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField VALUES_FIELD_DESC = new org.apache.thrift.protocol.TField("values", org.apache.thrift.protocol.TType.LIST, (short)2);
+  private static final org.apache.thrift.protocol.TField ERROR_FIELD_DESC = new org.apache.thrift.protocol.TField("error", org.apache.thrift.protocol.TType.STRUCT, (short)3);
+
+  public AsyncMessageHeader header; // required
+  public List<VersionedValue> values; // required
+  public SyncError error; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    VALUES((short)2, "values"),
+    ERROR((short)3, "error");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // VALUES
+          return VALUES;
+        case 3: // ERROR
+          return ERROR;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.VALUES, new org.apache.thrift.meta_data.FieldMetaData("values", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, VersionedValue.class))));
+    tmpMap.put(_Fields.ERROR, new org.apache.thrift.meta_data.FieldMetaData("error", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, SyncError.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(GetResponseMessage.class, metaDataMap);
+  }
+
+  public GetResponseMessage() {
+  }
+
+  public GetResponseMessage(
+    AsyncMessageHeader header,
+    List<VersionedValue> values)
+  {
+    this();
+    this.header = header;
+    this.values = values;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public GetResponseMessage(GetResponseMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetValues()) {
+      List<VersionedValue> __this__values = new ArrayList<VersionedValue>();
+      for (VersionedValue other_element : other.values) {
+        __this__values.add(new VersionedValue(other_element));
+      }
+      this.values = __this__values;
+    }
+    if (other.isSetError()) {
+      this.error = new SyncError(other.error);
+    }
+  }
+
+  public GetResponseMessage deepCopy() {
+    return new GetResponseMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.values = null;
+    this.error = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public GetResponseMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public int getValuesSize() {
+    return (this.values == null) ? 0 : this.values.size();
+  }
+
+  public java.util.Iterator<VersionedValue> getValuesIterator() {
+    return (this.values == null) ? null : this.values.iterator();
+  }
+
+  public void addToValues(VersionedValue elem) {
+    if (this.values == null) {
+      this.values = new ArrayList<VersionedValue>();
+    }
+    this.values.add(elem);
+  }
+
+  public List<VersionedValue> getValues() {
+    return this.values;
+  }
+
+  public GetResponseMessage setValues(List<VersionedValue> values) {
+    this.values = values;
+    return this;
+  }
+
+  public void unsetValues() {
+    this.values = null;
+  }
+
+  /** Returns true if field values is set (has been assigned a value) and false otherwise */
+  public boolean isSetValues() {
+    return this.values != null;
+  }
+
+  public void setValuesIsSet(boolean value) {
+    if (!value) {
+      this.values = null;
+    }
+  }
+
+  public SyncError getError() {
+    return this.error;
+  }
+
+  public GetResponseMessage setError(SyncError error) {
+    this.error = error;
+    return this;
+  }
+
+  public void unsetError() {
+    this.error = null;
+  }
+
+  /** Returns true if field error is set (has been assigned a value) and false otherwise */
+  public boolean isSetError() {
+    return this.error != null;
+  }
+
+  public void setErrorIsSet(boolean value) {
+    if (!value) {
+      this.error = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case VALUES:
+      if (value == null) {
+        unsetValues();
+      } else {
+        setValues((List<VersionedValue>)value);
+      }
+      break;
+
+    case ERROR:
+      if (value == null) {
+        unsetError();
+      } else {
+        setError((SyncError)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case VALUES:
+      return getValues();
+
+    case ERROR:
+      return getError();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case VALUES:
+      return isSetValues();
+    case ERROR:
+      return isSetError();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof GetResponseMessage)
+      return this.equals((GetResponseMessage)that);
+    return false;
+  }
+
+  public boolean equals(GetResponseMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_values = true && this.isSetValues();
+    boolean that_present_values = true && that.isSetValues();
+    if (this_present_values || that_present_values) {
+      if (!(this_present_values && that_present_values))
+        return false;
+      if (!this.values.equals(that.values))
+        return false;
+    }
+
+    boolean this_present_error = true && this.isSetError();
+    boolean that_present_error = true && that.isSetError();
+    if (this_present_error || that_present_error) {
+      if (!(this_present_error && that_present_error))
+        return false;
+      if (!this.error.equals(that.error))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(GetResponseMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    GetResponseMessage typedOther = (GetResponseMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetValues()).compareTo(typedOther.isSetValues());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetValues()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.values, typedOther.values);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetError()).compareTo(typedOther.isSetError());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetError()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.error, typedOther.error);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // VALUES
+          if (field.type == org.apache.thrift.protocol.TType.LIST) {
+            {
+              org.apache.thrift.protocol.TList _list12 = iprot.readListBegin();
+              this.values = new ArrayList<VersionedValue>(_list12.size);
+              for (int _i13 = 0; _i13 < _list12.size; ++_i13)
+              {
+                VersionedValue _elem14; // required
+                _elem14 = new VersionedValue();
+                _elem14.read(iprot);
+                this.values.add(_elem14);
+              }
+              iprot.readListEnd();
+            }
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // ERROR
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.error = new SyncError();
+            this.error.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.values != null) {
+      oprot.writeFieldBegin(VALUES_FIELD_DESC);
+      {
+        oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, this.values.size()));
+        for (VersionedValue _iter15 : this.values)
+        {
+          _iter15.write(oprot);
+        }
+        oprot.writeListEnd();
+      }
+      oprot.writeFieldEnd();
+    }
+    if (this.error != null) {
+      if (isSetError()) {
+        oprot.writeFieldBegin(ERROR_FIELD_DESC);
+        this.error.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("GetResponseMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("values:");
+    if (this.values == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.values);
+    }
+    first = false;
+    if (isSetError()) {
+      if (!first) sb.append(", ");
+      sb.append("error:");
+      if (this.error == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.error);
+      }
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/HelloMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/HelloMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..a18bc0c8c73344d3f38992b0bb8489b320311f95
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/HelloMessage.java
@@ -0,0 +1,413 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class HelloMessage implements org.apache.thrift.TBase<HelloMessage, HelloMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("HelloMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField NODE_ID_FIELD_DESC = new org.apache.thrift.protocol.TField("nodeId", org.apache.thrift.protocol.TType.I16, (short)2);
+
+  public AsyncMessageHeader header; // required
+  public short nodeId; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    NODE_ID((short)2, "nodeId");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // NODE_ID
+          return NODE_ID;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __NODEID_ISSET_ID = 0;
+  private BitSet __isset_bit_vector = new BitSet(1);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.NODE_ID, new org.apache.thrift.meta_data.FieldMetaData("nodeId", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I16)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(HelloMessage.class, metaDataMap);
+  }
+
+  public HelloMessage() {
+  }
+
+  public HelloMessage(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public HelloMessage(HelloMessage other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    this.nodeId = other.nodeId;
+  }
+
+  public HelloMessage deepCopy() {
+    return new HelloMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    setNodeIdIsSet(false);
+    this.nodeId = 0;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public HelloMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public short getNodeId() {
+    return this.nodeId;
+  }
+
+  public HelloMessage setNodeId(short nodeId) {
+    this.nodeId = nodeId;
+    setNodeIdIsSet(true);
+    return this;
+  }
+
+  public void unsetNodeId() {
+    __isset_bit_vector.clear(__NODEID_ISSET_ID);
+  }
+
+  /** Returns true if field nodeId is set (has been assigned a value) and false otherwise */
+  public boolean isSetNodeId() {
+    return __isset_bit_vector.get(__NODEID_ISSET_ID);
+  }
+
+  public void setNodeIdIsSet(boolean value) {
+    __isset_bit_vector.set(__NODEID_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case NODE_ID:
+      if (value == null) {
+        unsetNodeId();
+      } else {
+        setNodeId((Short)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case NODE_ID:
+      return Short.valueOf(getNodeId());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case NODE_ID:
+      return isSetNodeId();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof HelloMessage)
+      return this.equals((HelloMessage)that);
+    return false;
+  }
+
+  public boolean equals(HelloMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_nodeId = true && this.isSetNodeId();
+    boolean that_present_nodeId = true && that.isSetNodeId();
+    if (this_present_nodeId || that_present_nodeId) {
+      if (!(this_present_nodeId && that_present_nodeId))
+        return false;
+      if (this.nodeId != that.nodeId)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(HelloMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    HelloMessage typedOther = (HelloMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetNodeId()).compareTo(typedOther.isSetNodeId());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetNodeId()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.nodeId, typedOther.nodeId);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // NODE_ID
+          if (field.type == org.apache.thrift.protocol.TType.I16) {
+            this.nodeId = iprot.readI16();
+            setNodeIdIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (isSetNodeId()) {
+      oprot.writeFieldBegin(NODE_ID_FIELD_DESC);
+      oprot.writeI16(this.nodeId);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("HelloMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (isSetNodeId()) {
+      if (!first) sb.append(", ");
+      sb.append("nodeId:");
+      sb.append(this.nodeId);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/KeyedValues.java b/lib/gen-java/org/sdnplatform/sync/thrift/KeyedValues.java
new file mode 100644
index 0000000000000000000000000000000000000000..a286c55fcbb1252ddf21b862d1158e716184b1e2
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/KeyedValues.java
@@ -0,0 +1,463 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class KeyedValues implements org.apache.thrift.TBase<KeyedValues, KeyedValues._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("KeyedValues");
+
+  private static final org.apache.thrift.protocol.TField KEY_FIELD_DESC = new org.apache.thrift.protocol.TField("key", org.apache.thrift.protocol.TType.STRING, (short)1);
+  private static final org.apache.thrift.protocol.TField VALUES_FIELD_DESC = new org.apache.thrift.protocol.TField("values", org.apache.thrift.protocol.TType.LIST, (short)2);
+
+  public ByteBuffer key; // required
+  public List<VersionedValue> values; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    KEY((short)1, "key"),
+    VALUES((short)2, "values");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // KEY
+          return KEY;
+        case 2: // VALUES
+          return VALUES;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.KEY, new org.apache.thrift.meta_data.FieldMetaData("key", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING        , true)));
+    tmpMap.put(_Fields.VALUES, new org.apache.thrift.meta_data.FieldMetaData("values", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, VersionedValue.class))));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(KeyedValues.class, metaDataMap);
+  }
+
+  public KeyedValues() {
+  }
+
+  public KeyedValues(
+    ByteBuffer key,
+    List<VersionedValue> values)
+  {
+    this();
+    this.key = key;
+    this.values = values;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public KeyedValues(KeyedValues other) {
+    if (other.isSetKey()) {
+      this.key = org.apache.thrift.TBaseHelper.copyBinary(other.key);
+;
+    }
+    if (other.isSetValues()) {
+      List<VersionedValue> __this__values = new ArrayList<VersionedValue>();
+      for (VersionedValue other_element : other.values) {
+        __this__values.add(new VersionedValue(other_element));
+      }
+      this.values = __this__values;
+    }
+  }
+
+  public KeyedValues deepCopy() {
+    return new KeyedValues(this);
+  }
+
+  @Override
+  public void clear() {
+    this.key = null;
+    this.values = null;
+  }
+
+  public byte[] getKey() {
+    setKey(org.apache.thrift.TBaseHelper.rightSize(key));
+    return key == null ? null : key.array();
+  }
+
+  public ByteBuffer bufferForKey() {
+    return key;
+  }
+
+  public KeyedValues setKey(byte[] key) {
+    setKey(key == null ? (ByteBuffer)null : ByteBuffer.wrap(key));
+    return this;
+  }
+
+  public KeyedValues setKey(ByteBuffer key) {
+    this.key = key;
+    return this;
+  }
+
+  public void unsetKey() {
+    this.key = null;
+  }
+
+  /** Returns true if field key is set (has been assigned a value) and false otherwise */
+  public boolean isSetKey() {
+    return this.key != null;
+  }
+
+  public void setKeyIsSet(boolean value) {
+    if (!value) {
+      this.key = null;
+    }
+  }
+
+  public int getValuesSize() {
+    return (this.values == null) ? 0 : this.values.size();
+  }
+
+  public java.util.Iterator<VersionedValue> getValuesIterator() {
+    return (this.values == null) ? null : this.values.iterator();
+  }
+
+  public void addToValues(VersionedValue elem) {
+    if (this.values == null) {
+      this.values = new ArrayList<VersionedValue>();
+    }
+    this.values.add(elem);
+  }
+
+  public List<VersionedValue> getValues() {
+    return this.values;
+  }
+
+  public KeyedValues setValues(List<VersionedValue> values) {
+    this.values = values;
+    return this;
+  }
+
+  public void unsetValues() {
+    this.values = null;
+  }
+
+  /** Returns true if field values is set (has been assigned a value) and false otherwise */
+  public boolean isSetValues() {
+    return this.values != null;
+  }
+
+  public void setValuesIsSet(boolean value) {
+    if (!value) {
+      this.values = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case KEY:
+      if (value == null) {
+        unsetKey();
+      } else {
+        setKey((ByteBuffer)value);
+      }
+      break;
+
+    case VALUES:
+      if (value == null) {
+        unsetValues();
+      } else {
+        setValues((List<VersionedValue>)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case KEY:
+      return getKey();
+
+    case VALUES:
+      return getValues();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case KEY:
+      return isSetKey();
+    case VALUES:
+      return isSetValues();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof KeyedValues)
+      return this.equals((KeyedValues)that);
+    return false;
+  }
+
+  public boolean equals(KeyedValues that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_key = true && this.isSetKey();
+    boolean that_present_key = true && that.isSetKey();
+    if (this_present_key || that_present_key) {
+      if (!(this_present_key && that_present_key))
+        return false;
+      if (!this.key.equals(that.key))
+        return false;
+    }
+
+    boolean this_present_values = true && this.isSetValues();
+    boolean that_present_values = true && that.isSetValues();
+    if (this_present_values || that_present_values) {
+      if (!(this_present_values && that_present_values))
+        return false;
+      if (!this.values.equals(that.values))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(KeyedValues other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    KeyedValues typedOther = (KeyedValues)other;
+
+    lastComparison = Boolean.valueOf(isSetKey()).compareTo(typedOther.isSetKey());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetKey()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.key, typedOther.key);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetValues()).compareTo(typedOther.isSetValues());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetValues()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.values, typedOther.values);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // KEY
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.key = iprot.readBinary();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // VALUES
+          if (field.type == org.apache.thrift.protocol.TType.LIST) {
+            {
+              org.apache.thrift.protocol.TList _list4 = iprot.readListBegin();
+              this.values = new ArrayList<VersionedValue>(_list4.size);
+              for (int _i5 = 0; _i5 < _list4.size; ++_i5)
+              {
+                VersionedValue _elem6; // required
+                _elem6 = new VersionedValue();
+                _elem6.read(iprot);
+                this.values.add(_elem6);
+              }
+              iprot.readListEnd();
+            }
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.key != null) {
+      oprot.writeFieldBegin(KEY_FIELD_DESC);
+      oprot.writeBinary(this.key);
+      oprot.writeFieldEnd();
+    }
+    if (this.values != null) {
+      oprot.writeFieldBegin(VALUES_FIELD_DESC);
+      {
+        oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, this.values.size()));
+        for (VersionedValue _iter7 : this.values)
+        {
+          _iter7.write(oprot);
+        }
+        oprot.writeListEnd();
+      }
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("KeyedValues(");
+    boolean first = true;
+
+    sb.append("key:");
+    if (this.key == null) {
+      sb.append("null");
+    } else {
+      org.apache.thrift.TBaseHelper.toString(this.key, sb);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("values:");
+    if (this.values == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.values);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (key == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not present! Struct: " + toString());
+    }
+    if (values == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'values' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/KeyedVersions.java b/lib/gen-java/org/sdnplatform/sync/thrift/KeyedVersions.java
new file mode 100644
index 0000000000000000000000000000000000000000..ee153a4cda4e058b3dcaa65ea71bb80753d0ad72
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/KeyedVersions.java
@@ -0,0 +1,463 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class KeyedVersions implements org.apache.thrift.TBase<KeyedVersions, KeyedVersions._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("KeyedVersions");
+
+  private static final org.apache.thrift.protocol.TField KEY_FIELD_DESC = new org.apache.thrift.protocol.TField("key", org.apache.thrift.protocol.TType.STRING, (short)1);
+  private static final org.apache.thrift.protocol.TField VERSIONS_FIELD_DESC = new org.apache.thrift.protocol.TField("versions", org.apache.thrift.protocol.TType.LIST, (short)2);
+
+  public ByteBuffer key; // required
+  public List<VectorClock> versions; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    KEY((short)1, "key"),
+    VERSIONS((short)2, "versions");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // KEY
+          return KEY;
+        case 2: // VERSIONS
+          return VERSIONS;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.KEY, new org.apache.thrift.meta_data.FieldMetaData("key", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING        , true)));
+    tmpMap.put(_Fields.VERSIONS, new org.apache.thrift.meta_data.FieldMetaData("versions", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, VectorClock.class))));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(KeyedVersions.class, metaDataMap);
+  }
+
+  public KeyedVersions() {
+  }
+
+  public KeyedVersions(
+    ByteBuffer key,
+    List<VectorClock> versions)
+  {
+    this();
+    this.key = key;
+    this.versions = versions;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public KeyedVersions(KeyedVersions other) {
+    if (other.isSetKey()) {
+      this.key = org.apache.thrift.TBaseHelper.copyBinary(other.key);
+;
+    }
+    if (other.isSetVersions()) {
+      List<VectorClock> __this__versions = new ArrayList<VectorClock>();
+      for (VectorClock other_element : other.versions) {
+        __this__versions.add(new VectorClock(other_element));
+      }
+      this.versions = __this__versions;
+    }
+  }
+
+  public KeyedVersions deepCopy() {
+    return new KeyedVersions(this);
+  }
+
+  @Override
+  public void clear() {
+    this.key = null;
+    this.versions = null;
+  }
+
+  public byte[] getKey() {
+    setKey(org.apache.thrift.TBaseHelper.rightSize(key));
+    return key == null ? null : key.array();
+  }
+
+  public ByteBuffer bufferForKey() {
+    return key;
+  }
+
+  public KeyedVersions setKey(byte[] key) {
+    setKey(key == null ? (ByteBuffer)null : ByteBuffer.wrap(key));
+    return this;
+  }
+
+  public KeyedVersions setKey(ByteBuffer key) {
+    this.key = key;
+    return this;
+  }
+
+  public void unsetKey() {
+    this.key = null;
+  }
+
+  /** Returns true if field key is set (has been assigned a value) and false otherwise */
+  public boolean isSetKey() {
+    return this.key != null;
+  }
+
+  public void setKeyIsSet(boolean value) {
+    if (!value) {
+      this.key = null;
+    }
+  }
+
+  public int getVersionsSize() {
+    return (this.versions == null) ? 0 : this.versions.size();
+  }
+
+  public java.util.Iterator<VectorClock> getVersionsIterator() {
+    return (this.versions == null) ? null : this.versions.iterator();
+  }
+
+  public void addToVersions(VectorClock elem) {
+    if (this.versions == null) {
+      this.versions = new ArrayList<VectorClock>();
+    }
+    this.versions.add(elem);
+  }
+
+  public List<VectorClock> getVersions() {
+    return this.versions;
+  }
+
+  public KeyedVersions setVersions(List<VectorClock> versions) {
+    this.versions = versions;
+    return this;
+  }
+
+  public void unsetVersions() {
+    this.versions = null;
+  }
+
+  /** Returns true if field versions is set (has been assigned a value) and false otherwise */
+  public boolean isSetVersions() {
+    return this.versions != null;
+  }
+
+  public void setVersionsIsSet(boolean value) {
+    if (!value) {
+      this.versions = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case KEY:
+      if (value == null) {
+        unsetKey();
+      } else {
+        setKey((ByteBuffer)value);
+      }
+      break;
+
+    case VERSIONS:
+      if (value == null) {
+        unsetVersions();
+      } else {
+        setVersions((List<VectorClock>)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case KEY:
+      return getKey();
+
+    case VERSIONS:
+      return getVersions();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case KEY:
+      return isSetKey();
+    case VERSIONS:
+      return isSetVersions();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof KeyedVersions)
+      return this.equals((KeyedVersions)that);
+    return false;
+  }
+
+  public boolean equals(KeyedVersions that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_key = true && this.isSetKey();
+    boolean that_present_key = true && that.isSetKey();
+    if (this_present_key || that_present_key) {
+      if (!(this_present_key && that_present_key))
+        return false;
+      if (!this.key.equals(that.key))
+        return false;
+    }
+
+    boolean this_present_versions = true && this.isSetVersions();
+    boolean that_present_versions = true && that.isSetVersions();
+    if (this_present_versions || that_present_versions) {
+      if (!(this_present_versions && that_present_versions))
+        return false;
+      if (!this.versions.equals(that.versions))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(KeyedVersions other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    KeyedVersions typedOther = (KeyedVersions)other;
+
+    lastComparison = Boolean.valueOf(isSetKey()).compareTo(typedOther.isSetKey());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetKey()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.key, typedOther.key);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetVersions()).compareTo(typedOther.isSetVersions());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetVersions()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.versions, typedOther.versions);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // KEY
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.key = iprot.readBinary();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // VERSIONS
+          if (field.type == org.apache.thrift.protocol.TType.LIST) {
+            {
+              org.apache.thrift.protocol.TList _list8 = iprot.readListBegin();
+              this.versions = new ArrayList<VectorClock>(_list8.size);
+              for (int _i9 = 0; _i9 < _list8.size; ++_i9)
+              {
+                VectorClock _elem10; // required
+                _elem10 = new VectorClock();
+                _elem10.read(iprot);
+                this.versions.add(_elem10);
+              }
+              iprot.readListEnd();
+            }
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.key != null) {
+      oprot.writeFieldBegin(KEY_FIELD_DESC);
+      oprot.writeBinary(this.key);
+      oprot.writeFieldEnd();
+    }
+    if (this.versions != null) {
+      oprot.writeFieldBegin(VERSIONS_FIELD_DESC);
+      {
+        oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, this.versions.size()));
+        for (VectorClock _iter11 : this.versions)
+        {
+          _iter11.write(oprot);
+        }
+        oprot.writeListEnd();
+      }
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("KeyedVersions(");
+    boolean first = true;
+
+    sb.append("key:");
+    if (this.key == null) {
+      sb.append("null");
+    } else {
+      org.apache.thrift.TBaseHelper.toString(this.key, sb);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("versions:");
+    if (this.versions == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.versions);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (key == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not present! Struct: " + toString());
+    }
+    if (versions == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'versions' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/MessageType.java b/lib/gen-java/org/sdnplatform/sync/thrift/MessageType.java
new file mode 100644
index 0000000000000000000000000000000000000000..e39c860dd1c24053920826af82e78db6663d1d72
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/MessageType.java
@@ -0,0 +1,95 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+
+import java.util.Map;
+import java.util.HashMap;
+import org.apache.thrift.TEnum;
+
+@SuppressWarnings("all") public enum MessageType implements org.apache.thrift.TEnum {
+  HELLO(1),
+  ERROR(2),
+  ECHO_REQUEST(3),
+  ECHO_REPLY(4),
+  GET_REQUEST(5),
+  GET_RESPONSE(6),
+  PUT_REQUEST(7),
+  PUT_RESPONSE(8),
+  DELETE_REQUEST(9),
+  DELETE_RESPONSE(10),
+  SYNC_VALUE(11),
+  SYNC_VALUE_RESPONSE(12),
+  SYNC_OFFER(13),
+  SYNC_REQUEST(14),
+  FULL_SYNC_REQUEST(15),
+  CURSOR_REQUEST(16),
+  CURSOR_RESPONSE(17),
+  REGISTER_REQUEST(18),
+  REGISTER_RESPONSE(19);
+
+  private final int value;
+
+  private MessageType(int value) {
+    this.value = value;
+  }
+
+  /**
+   * Get the integer value of this enum value, as defined in the Thrift IDL.
+   */
+  public int getValue() {
+    return value;
+  }
+
+  /**
+   * Find a the enum type by its integer value, as defined in the Thrift IDL.
+   * @return null if the value is not found.
+   */
+  public static MessageType findByValue(int value) { 
+    switch (value) {
+      case 1:
+        return HELLO;
+      case 2:
+        return ERROR;
+      case 3:
+        return ECHO_REQUEST;
+      case 4:
+        return ECHO_REPLY;
+      case 5:
+        return GET_REQUEST;
+      case 6:
+        return GET_RESPONSE;
+      case 7:
+        return PUT_REQUEST;
+      case 8:
+        return PUT_RESPONSE;
+      case 9:
+        return DELETE_REQUEST;
+      case 10:
+        return DELETE_RESPONSE;
+      case 11:
+        return SYNC_VALUE;
+      case 12:
+        return SYNC_VALUE_RESPONSE;
+      case 13:
+        return SYNC_OFFER;
+      case 14:
+        return SYNC_REQUEST;
+      case 15:
+        return FULL_SYNC_REQUEST;
+      case 16:
+        return CURSOR_REQUEST;
+      case 17:
+        return CURSOR_RESPONSE;
+      case 18:
+        return REGISTER_REQUEST;
+      case 19:
+        return REGISTER_RESPONSE;
+      default:
+        return null;
+    }
+  }
+}
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/PutRequestMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/PutRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..c60e13e0ceff8b65f45f7e6f03cd07c719021072
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/PutRequestMessage.java
@@ -0,0 +1,712 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class PutRequestMessage implements org.apache.thrift.TBase<PutRequestMessage, PutRequestMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("PutRequestMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField STORE_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("storeName", org.apache.thrift.protocol.TType.STRING, (short)2);
+  private static final org.apache.thrift.protocol.TField KEY_FIELD_DESC = new org.apache.thrift.protocol.TField("key", org.apache.thrift.protocol.TType.STRING, (short)3);
+  private static final org.apache.thrift.protocol.TField VERSIONED_VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("versionedValue", org.apache.thrift.protocol.TType.STRUCT, (short)4);
+  private static final org.apache.thrift.protocol.TField VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("value", org.apache.thrift.protocol.TType.STRING, (short)5);
+
+  public AsyncMessageHeader header; // required
+  public String storeName; // required
+  public ByteBuffer key; // required
+  public VersionedValue versionedValue; // required
+  public ByteBuffer value; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    STORE_NAME((short)2, "storeName"),
+    KEY((short)3, "key"),
+    VERSIONED_VALUE((short)4, "versionedValue"),
+    VALUE((short)5, "value");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // STORE_NAME
+          return STORE_NAME;
+        case 3: // KEY
+          return KEY;
+        case 4: // VERSIONED_VALUE
+          return VERSIONED_VALUE;
+        case 5: // VALUE
+          return VALUE;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.STORE_NAME, new org.apache.thrift.meta_data.FieldMetaData("storeName", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.KEY, new org.apache.thrift.meta_data.FieldMetaData("key", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING        , true)));
+    tmpMap.put(_Fields.VERSIONED_VALUE, new org.apache.thrift.meta_data.FieldMetaData("versionedValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, VersionedValue.class)));
+    tmpMap.put(_Fields.VALUE, new org.apache.thrift.meta_data.FieldMetaData("value", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING        , true)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(PutRequestMessage.class, metaDataMap);
+  }
+
+  public PutRequestMessage() {
+  }
+
+  public PutRequestMessage(
+    AsyncMessageHeader header,
+    String storeName,
+    ByteBuffer key)
+  {
+    this();
+    this.header = header;
+    this.storeName = storeName;
+    this.key = key;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public PutRequestMessage(PutRequestMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetStoreName()) {
+      this.storeName = other.storeName;
+    }
+    if (other.isSetKey()) {
+      this.key = org.apache.thrift.TBaseHelper.copyBinary(other.key);
+;
+    }
+    if (other.isSetVersionedValue()) {
+      this.versionedValue = new VersionedValue(other.versionedValue);
+    }
+    if (other.isSetValue()) {
+      this.value = org.apache.thrift.TBaseHelper.copyBinary(other.value);
+;
+    }
+  }
+
+  public PutRequestMessage deepCopy() {
+    return new PutRequestMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.storeName = null;
+    this.key = null;
+    this.versionedValue = null;
+    this.value = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public PutRequestMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public String getStoreName() {
+    return this.storeName;
+  }
+
+  public PutRequestMessage setStoreName(String storeName) {
+    this.storeName = storeName;
+    return this;
+  }
+
+  public void unsetStoreName() {
+    this.storeName = null;
+  }
+
+  /** Returns true if field storeName is set (has been assigned a value) and false otherwise */
+  public boolean isSetStoreName() {
+    return this.storeName != null;
+  }
+
+  public void setStoreNameIsSet(boolean value) {
+    if (!value) {
+      this.storeName = null;
+    }
+  }
+
+  public byte[] getKey() {
+    setKey(org.apache.thrift.TBaseHelper.rightSize(key));
+    return key == null ? null : key.array();
+  }
+
+  public ByteBuffer bufferForKey() {
+    return key;
+  }
+
+  public PutRequestMessage setKey(byte[] key) {
+    setKey(key == null ? (ByteBuffer)null : ByteBuffer.wrap(key));
+    return this;
+  }
+
+  public PutRequestMessage setKey(ByteBuffer key) {
+    this.key = key;
+    return this;
+  }
+
+  public void unsetKey() {
+    this.key = null;
+  }
+
+  /** Returns true if field key is set (has been assigned a value) and false otherwise */
+  public boolean isSetKey() {
+    return this.key != null;
+  }
+
+  public void setKeyIsSet(boolean value) {
+    if (!value) {
+      this.key = null;
+    }
+  }
+
+  public VersionedValue getVersionedValue() {
+    return this.versionedValue;
+  }
+
+  public PutRequestMessage setVersionedValue(VersionedValue versionedValue) {
+    this.versionedValue = versionedValue;
+    return this;
+  }
+
+  public void unsetVersionedValue() {
+    this.versionedValue = null;
+  }
+
+  /** Returns true if field versionedValue is set (has been assigned a value) and false otherwise */
+  public boolean isSetVersionedValue() {
+    return this.versionedValue != null;
+  }
+
+  public void setVersionedValueIsSet(boolean value) {
+    if (!value) {
+      this.versionedValue = null;
+    }
+  }
+
+  public byte[] getValue() {
+    setValue(org.apache.thrift.TBaseHelper.rightSize(value));
+    return value == null ? null : value.array();
+  }
+
+  public ByteBuffer bufferForValue() {
+    return value;
+  }
+
+  public PutRequestMessage setValue(byte[] value) {
+    setValue(value == null ? (ByteBuffer)null : ByteBuffer.wrap(value));
+    return this;
+  }
+
+  public PutRequestMessage setValue(ByteBuffer value) {
+    this.value = value;
+    return this;
+  }
+
+  public void unsetValue() {
+    this.value = null;
+  }
+
+  /** Returns true if field value is set (has been assigned a value) and false otherwise */
+  public boolean isSetValue() {
+    return this.value != null;
+  }
+
+  public void setValueIsSet(boolean value) {
+    if (!value) {
+      this.value = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case STORE_NAME:
+      if (value == null) {
+        unsetStoreName();
+      } else {
+        setStoreName((String)value);
+      }
+      break;
+
+    case KEY:
+      if (value == null) {
+        unsetKey();
+      } else {
+        setKey((ByteBuffer)value);
+      }
+      break;
+
+    case VERSIONED_VALUE:
+      if (value == null) {
+        unsetVersionedValue();
+      } else {
+        setVersionedValue((VersionedValue)value);
+      }
+      break;
+
+    case VALUE:
+      if (value == null) {
+        unsetValue();
+      } else {
+        setValue((ByteBuffer)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case STORE_NAME:
+      return getStoreName();
+
+    case KEY:
+      return getKey();
+
+    case VERSIONED_VALUE:
+      return getVersionedValue();
+
+    case VALUE:
+      return getValue();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case STORE_NAME:
+      return isSetStoreName();
+    case KEY:
+      return isSetKey();
+    case VERSIONED_VALUE:
+      return isSetVersionedValue();
+    case VALUE:
+      return isSetValue();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof PutRequestMessage)
+      return this.equals((PutRequestMessage)that);
+    return false;
+  }
+
+  public boolean equals(PutRequestMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_storeName = true && this.isSetStoreName();
+    boolean that_present_storeName = true && that.isSetStoreName();
+    if (this_present_storeName || that_present_storeName) {
+      if (!(this_present_storeName && that_present_storeName))
+        return false;
+      if (!this.storeName.equals(that.storeName))
+        return false;
+    }
+
+    boolean this_present_key = true && this.isSetKey();
+    boolean that_present_key = true && that.isSetKey();
+    if (this_present_key || that_present_key) {
+      if (!(this_present_key && that_present_key))
+        return false;
+      if (!this.key.equals(that.key))
+        return false;
+    }
+
+    boolean this_present_versionedValue = true && this.isSetVersionedValue();
+    boolean that_present_versionedValue = true && that.isSetVersionedValue();
+    if (this_present_versionedValue || that_present_versionedValue) {
+      if (!(this_present_versionedValue && that_present_versionedValue))
+        return false;
+      if (!this.versionedValue.equals(that.versionedValue))
+        return false;
+    }
+
+    boolean this_present_value = true && this.isSetValue();
+    boolean that_present_value = true && that.isSetValue();
+    if (this_present_value || that_present_value) {
+      if (!(this_present_value && that_present_value))
+        return false;
+      if (!this.value.equals(that.value))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(PutRequestMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    PutRequestMessage typedOther = (PutRequestMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetStoreName()).compareTo(typedOther.isSetStoreName());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStoreName()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.storeName, typedOther.storeName);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetKey()).compareTo(typedOther.isSetKey());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetKey()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.key, typedOther.key);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetVersionedValue()).compareTo(typedOther.isSetVersionedValue());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetVersionedValue()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.versionedValue, typedOther.versionedValue);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetValue()).compareTo(typedOther.isSetValue());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetValue()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.value, typedOther.value);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // STORE_NAME
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.storeName = iprot.readString();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // KEY
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.key = iprot.readBinary();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 4: // VERSIONED_VALUE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.versionedValue = new VersionedValue();
+            this.versionedValue.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 5: // VALUE
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.value = iprot.readBinary();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.storeName != null) {
+      oprot.writeFieldBegin(STORE_NAME_FIELD_DESC);
+      oprot.writeString(this.storeName);
+      oprot.writeFieldEnd();
+    }
+    if (this.key != null) {
+      oprot.writeFieldBegin(KEY_FIELD_DESC);
+      oprot.writeBinary(this.key);
+      oprot.writeFieldEnd();
+    }
+    if (this.versionedValue != null) {
+      if (isSetVersionedValue()) {
+        oprot.writeFieldBegin(VERSIONED_VALUE_FIELD_DESC);
+        this.versionedValue.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.value != null) {
+      if (isSetValue()) {
+        oprot.writeFieldBegin(VALUE_FIELD_DESC);
+        oprot.writeBinary(this.value);
+        oprot.writeFieldEnd();
+      }
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("PutRequestMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("storeName:");
+    if (this.storeName == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.storeName);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("key:");
+    if (this.key == null) {
+      sb.append("null");
+    } else {
+      org.apache.thrift.TBaseHelper.toString(this.key, sb);
+    }
+    first = false;
+    if (isSetVersionedValue()) {
+      if (!first) sb.append(", ");
+      sb.append("versionedValue:");
+      if (this.versionedValue == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.versionedValue);
+      }
+      first = false;
+    }
+    if (isSetValue()) {
+      if (!first) sb.append(", ");
+      sb.append("value:");
+      if (this.value == null) {
+        sb.append("null");
+      } else {
+        org.apache.thrift.TBaseHelper.toString(this.value, sb);
+      }
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+    if (storeName == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'storeName' was not present! Struct: " + toString());
+    }
+    if (key == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/PutResponseMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/PutResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..01153eacf5caa1fe31fe4607151b3779f88a8067
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/PutResponseMessage.java
@@ -0,0 +1,323 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class PutResponseMessage implements org.apache.thrift.TBase<PutResponseMessage, PutResponseMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("PutResponseMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+
+  public AsyncMessageHeader header; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(PutResponseMessage.class, metaDataMap);
+  }
+
+  public PutResponseMessage() {
+  }
+
+  public PutResponseMessage(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public PutResponseMessage(PutResponseMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+  }
+
+  public PutResponseMessage deepCopy() {
+    return new PutResponseMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public PutResponseMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof PutResponseMessage)
+      return this.equals((PutResponseMessage)that);
+    return false;
+  }
+
+  public boolean equals(PutResponseMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(PutResponseMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    PutResponseMessage typedOther = (PutResponseMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("PutResponseMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/RegisterRequestMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/RegisterRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..e87e460c92b7e6f7bcea90f0ed319a91853fe65d
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/RegisterRequestMessage.java
@@ -0,0 +1,416 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class RegisterRequestMessage implements org.apache.thrift.TBase<RegisterRequestMessage, RegisterRequestMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("RegisterRequestMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField STORE_FIELD_DESC = new org.apache.thrift.protocol.TField("store", org.apache.thrift.protocol.TType.STRUCT, (short)2);
+
+  public AsyncMessageHeader header; // required
+  public Store store; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    STORE((short)2, "store");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // STORE
+          return STORE;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.STORE, new org.apache.thrift.meta_data.FieldMetaData("store", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Store.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(RegisterRequestMessage.class, metaDataMap);
+  }
+
+  public RegisterRequestMessage() {
+  }
+
+  public RegisterRequestMessage(
+    AsyncMessageHeader header,
+    Store store)
+  {
+    this();
+    this.header = header;
+    this.store = store;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public RegisterRequestMessage(RegisterRequestMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetStore()) {
+      this.store = new Store(other.store);
+    }
+  }
+
+  public RegisterRequestMessage deepCopy() {
+    return new RegisterRequestMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.store = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public RegisterRequestMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public Store getStore() {
+    return this.store;
+  }
+
+  public RegisterRequestMessage setStore(Store store) {
+    this.store = store;
+    return this;
+  }
+
+  public void unsetStore() {
+    this.store = null;
+  }
+
+  /** Returns true if field store is set (has been assigned a value) and false otherwise */
+  public boolean isSetStore() {
+    return this.store != null;
+  }
+
+  public void setStoreIsSet(boolean value) {
+    if (!value) {
+      this.store = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case STORE:
+      if (value == null) {
+        unsetStore();
+      } else {
+        setStore((Store)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case STORE:
+      return getStore();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case STORE:
+      return isSetStore();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof RegisterRequestMessage)
+      return this.equals((RegisterRequestMessage)that);
+    return false;
+  }
+
+  public boolean equals(RegisterRequestMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_store = true && this.isSetStore();
+    boolean that_present_store = true && that.isSetStore();
+    if (this_present_store || that_present_store) {
+      if (!(this_present_store && that_present_store))
+        return false;
+      if (!this.store.equals(that.store))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(RegisterRequestMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    RegisterRequestMessage typedOther = (RegisterRequestMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetStore()).compareTo(typedOther.isSetStore());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStore()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.store, typedOther.store);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // STORE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.store = new Store();
+            this.store.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.store != null) {
+      oprot.writeFieldBegin(STORE_FIELD_DESC);
+      this.store.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("RegisterRequestMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("store:");
+    if (this.store == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.store);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+    if (store == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'store' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/RegisterResponseMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/RegisterResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..613a4d56882be16592577d01f66dedc01e7d6cc1
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/RegisterResponseMessage.java
@@ -0,0 +1,323 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class RegisterResponseMessage implements org.apache.thrift.TBase<RegisterResponseMessage, RegisterResponseMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("RegisterResponseMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+
+  public AsyncMessageHeader header; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(RegisterResponseMessage.class, metaDataMap);
+  }
+
+  public RegisterResponseMessage() {
+  }
+
+  public RegisterResponseMessage(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public RegisterResponseMessage(RegisterResponseMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+  }
+
+  public RegisterResponseMessage deepCopy() {
+    return new RegisterResponseMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public RegisterResponseMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof RegisterResponseMessage)
+      return this.equals((RegisterResponseMessage)that);
+    return false;
+  }
+
+  public boolean equals(RegisterResponseMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(RegisterResponseMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    RegisterResponseMessage typedOther = (RegisterResponseMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("RegisterResponseMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/Scope.java b/lib/gen-java/org/sdnplatform/sync/thrift/Scope.java
new file mode 100644
index 0000000000000000000000000000000000000000..a310deda54287e32b90fc2af038d3449c40655bd
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/Scope.java
@@ -0,0 +1,44 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+
+import java.util.Map;
+import java.util.HashMap;
+import org.apache.thrift.TEnum;
+
+@SuppressWarnings("all") public enum Scope implements org.apache.thrift.TEnum {
+  GLOBAL(0),
+  LOCAL(1);
+
+  private final int value;
+
+  private Scope(int value) {
+    this.value = value;
+  }
+
+  /**
+   * Get the integer value of this enum value, as defined in the Thrift IDL.
+   */
+  public int getValue() {
+    return value;
+  }
+
+  /**
+   * Find a the enum type by its integer value, as defined in the Thrift IDL.
+   * @return null if the value is not found.
+   */
+  public static Scope findByValue(int value) { 
+    switch (value) {
+      case 0:
+        return GLOBAL;
+      case 1:
+        return LOCAL;
+      default:
+        return null;
+    }
+  }
+}
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/Store.java b/lib/gen-java/org/sdnplatform/sync/thrift/Store.java
new file mode 100644
index 0000000000000000000000000000000000000000..453aeb071f72f2e74377c19a44497b21d04ee262
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/Store.java
@@ -0,0 +1,519 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class Store implements org.apache.thrift.TBase<Store, Store._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Store");
+
+  private static final org.apache.thrift.protocol.TField STORE_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("storeName", org.apache.thrift.protocol.TType.STRING, (short)1);
+  private static final org.apache.thrift.protocol.TField SCOPE_FIELD_DESC = new org.apache.thrift.protocol.TField("scope", org.apache.thrift.protocol.TType.I32, (short)2);
+  private static final org.apache.thrift.protocol.TField PERSIST_FIELD_DESC = new org.apache.thrift.protocol.TField("persist", org.apache.thrift.protocol.TType.BOOL, (short)3);
+
+  public String storeName; // required
+  /**
+   * 
+   * @see Scope
+   */
+  public Scope scope; // required
+  public boolean persist; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    STORE_NAME((short)1, "storeName"),
+    /**
+     * 
+     * @see Scope
+     */
+    SCOPE((short)2, "scope"),
+    PERSIST((short)3, "persist");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // STORE_NAME
+          return STORE_NAME;
+        case 2: // SCOPE
+          return SCOPE;
+        case 3: // PERSIST
+          return PERSIST;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __PERSIST_ISSET_ID = 0;
+  private BitSet __isset_bit_vector = new BitSet(1);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.STORE_NAME, new org.apache.thrift.meta_data.FieldMetaData("storeName", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.SCOPE, new org.apache.thrift.meta_data.FieldMetaData("scope", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, Scope.class)));
+    tmpMap.put(_Fields.PERSIST, new org.apache.thrift.meta_data.FieldMetaData("persist", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.BOOL)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Store.class, metaDataMap);
+  }
+
+  public Store() {
+  }
+
+  public Store(
+    String storeName)
+  {
+    this();
+    this.storeName = storeName;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public Store(Store other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    if (other.isSetStoreName()) {
+      this.storeName = other.storeName;
+    }
+    if (other.isSetScope()) {
+      this.scope = other.scope;
+    }
+    this.persist = other.persist;
+  }
+
+  public Store deepCopy() {
+    return new Store(this);
+  }
+
+  @Override
+  public void clear() {
+    this.storeName = null;
+    this.scope = null;
+    setPersistIsSet(false);
+    this.persist = false;
+  }
+
+  public String getStoreName() {
+    return this.storeName;
+  }
+
+  public Store setStoreName(String storeName) {
+    this.storeName = storeName;
+    return this;
+  }
+
+  public void unsetStoreName() {
+    this.storeName = null;
+  }
+
+  /** Returns true if field storeName is set (has been assigned a value) and false otherwise */
+  public boolean isSetStoreName() {
+    return this.storeName != null;
+  }
+
+  public void setStoreNameIsSet(boolean value) {
+    if (!value) {
+      this.storeName = null;
+    }
+  }
+
+  /**
+   * 
+   * @see Scope
+   */
+  public Scope getScope() {
+    return this.scope;
+  }
+
+  /**
+   * 
+   * @see Scope
+   */
+  public Store setScope(Scope scope) {
+    this.scope = scope;
+    return this;
+  }
+
+  public void unsetScope() {
+    this.scope = null;
+  }
+
+  /** Returns true if field scope is set (has been assigned a value) and false otherwise */
+  public boolean isSetScope() {
+    return this.scope != null;
+  }
+
+  public void setScopeIsSet(boolean value) {
+    if (!value) {
+      this.scope = null;
+    }
+  }
+
+  public boolean isPersist() {
+    return this.persist;
+  }
+
+  public Store setPersist(boolean persist) {
+    this.persist = persist;
+    setPersistIsSet(true);
+    return this;
+  }
+
+  public void unsetPersist() {
+    __isset_bit_vector.clear(__PERSIST_ISSET_ID);
+  }
+
+  /** Returns true if field persist is set (has been assigned a value) and false otherwise */
+  public boolean isSetPersist() {
+    return __isset_bit_vector.get(__PERSIST_ISSET_ID);
+  }
+
+  public void setPersistIsSet(boolean value) {
+    __isset_bit_vector.set(__PERSIST_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case STORE_NAME:
+      if (value == null) {
+        unsetStoreName();
+      } else {
+        setStoreName((String)value);
+      }
+      break;
+
+    case SCOPE:
+      if (value == null) {
+        unsetScope();
+      } else {
+        setScope((Scope)value);
+      }
+      break;
+
+    case PERSIST:
+      if (value == null) {
+        unsetPersist();
+      } else {
+        setPersist((Boolean)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case STORE_NAME:
+      return getStoreName();
+
+    case SCOPE:
+      return getScope();
+
+    case PERSIST:
+      return Boolean.valueOf(isPersist());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case STORE_NAME:
+      return isSetStoreName();
+    case SCOPE:
+      return isSetScope();
+    case PERSIST:
+      return isSetPersist();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof Store)
+      return this.equals((Store)that);
+    return false;
+  }
+
+  public boolean equals(Store that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_storeName = true && this.isSetStoreName();
+    boolean that_present_storeName = true && that.isSetStoreName();
+    if (this_present_storeName || that_present_storeName) {
+      if (!(this_present_storeName && that_present_storeName))
+        return false;
+      if (!this.storeName.equals(that.storeName))
+        return false;
+    }
+
+    boolean this_present_scope = true && this.isSetScope();
+    boolean that_present_scope = true && that.isSetScope();
+    if (this_present_scope || that_present_scope) {
+      if (!(this_present_scope && that_present_scope))
+        return false;
+      if (!this.scope.equals(that.scope))
+        return false;
+    }
+
+    boolean this_present_persist = true && this.isSetPersist();
+    boolean that_present_persist = true && that.isSetPersist();
+    if (this_present_persist || that_present_persist) {
+      if (!(this_present_persist && that_present_persist))
+        return false;
+      if (this.persist != that.persist)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(Store other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    Store typedOther = (Store)other;
+
+    lastComparison = Boolean.valueOf(isSetStoreName()).compareTo(typedOther.isSetStoreName());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStoreName()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.storeName, typedOther.storeName);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetScope()).compareTo(typedOther.isSetScope());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetScope()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.scope, typedOther.scope);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetPersist()).compareTo(typedOther.isSetPersist());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetPersist()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.persist, typedOther.persist);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // STORE_NAME
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.storeName = iprot.readString();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // SCOPE
+          if (field.type == org.apache.thrift.protocol.TType.I32) {
+            this.scope = Scope.findByValue(iprot.readI32());
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // PERSIST
+          if (field.type == org.apache.thrift.protocol.TType.BOOL) {
+            this.persist = iprot.readBool();
+            setPersistIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.storeName != null) {
+      oprot.writeFieldBegin(STORE_NAME_FIELD_DESC);
+      oprot.writeString(this.storeName);
+      oprot.writeFieldEnd();
+    }
+    if (this.scope != null) {
+      if (isSetScope()) {
+        oprot.writeFieldBegin(SCOPE_FIELD_DESC);
+        oprot.writeI32(this.scope.getValue());
+        oprot.writeFieldEnd();
+      }
+    }
+    if (isSetPersist()) {
+      oprot.writeFieldBegin(PERSIST_FIELD_DESC);
+      oprot.writeBool(this.persist);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("Store(");
+    boolean first = true;
+
+    sb.append("storeName:");
+    if (this.storeName == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.storeName);
+    }
+    first = false;
+    if (isSetScope()) {
+      if (!first) sb.append(", ");
+      sb.append("scope:");
+      if (this.scope == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.scope);
+      }
+      first = false;
+    }
+    if (isSetPersist()) {
+      if (!first) sb.append(", ");
+      sb.append("persist:");
+      sb.append(this.persist);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (storeName == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'storeName' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/SyncError.java b/lib/gen-java/org/sdnplatform/sync/thrift/SyncError.java
new file mode 100644
index 0000000000000000000000000000000000000000..aebf77603423990e8c6d17f4dc004b3a4b9749f3
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/SyncError.java
@@ -0,0 +1,408 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class SyncError implements org.apache.thrift.TBase<SyncError, SyncError._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("SyncError");
+
+  private static final org.apache.thrift.protocol.TField ERROR_CODE_FIELD_DESC = new org.apache.thrift.protocol.TField("errorCode", org.apache.thrift.protocol.TType.I32, (short)1);
+  private static final org.apache.thrift.protocol.TField MESSAGE_FIELD_DESC = new org.apache.thrift.protocol.TField("message", org.apache.thrift.protocol.TType.STRING, (short)2);
+
+  public int errorCode; // required
+  public String message; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    ERROR_CODE((short)1, "errorCode"),
+    MESSAGE((short)2, "message");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // ERROR_CODE
+          return ERROR_CODE;
+        case 2: // MESSAGE
+          return MESSAGE;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __ERRORCODE_ISSET_ID = 0;
+  private BitSet __isset_bit_vector = new BitSet(1);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.ERROR_CODE, new org.apache.thrift.meta_data.FieldMetaData("errorCode", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+    tmpMap.put(_Fields.MESSAGE, new org.apache.thrift.meta_data.FieldMetaData("message", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(SyncError.class, metaDataMap);
+  }
+
+  public SyncError() {
+  }
+
+  public SyncError(
+    int errorCode,
+    String message)
+  {
+    this();
+    this.errorCode = errorCode;
+    setErrorCodeIsSet(true);
+    this.message = message;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public SyncError(SyncError other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    this.errorCode = other.errorCode;
+    if (other.isSetMessage()) {
+      this.message = other.message;
+    }
+  }
+
+  public SyncError deepCopy() {
+    return new SyncError(this);
+  }
+
+  @Override
+  public void clear() {
+    setErrorCodeIsSet(false);
+    this.errorCode = 0;
+    this.message = null;
+  }
+
+  public int getErrorCode() {
+    return this.errorCode;
+  }
+
+  public SyncError setErrorCode(int errorCode) {
+    this.errorCode = errorCode;
+    setErrorCodeIsSet(true);
+    return this;
+  }
+
+  public void unsetErrorCode() {
+    __isset_bit_vector.clear(__ERRORCODE_ISSET_ID);
+  }
+
+  /** Returns true if field errorCode is set (has been assigned a value) and false otherwise */
+  public boolean isSetErrorCode() {
+    return __isset_bit_vector.get(__ERRORCODE_ISSET_ID);
+  }
+
+  public void setErrorCodeIsSet(boolean value) {
+    __isset_bit_vector.set(__ERRORCODE_ISSET_ID, value);
+  }
+
+  public String getMessage() {
+    return this.message;
+  }
+
+  public SyncError setMessage(String message) {
+    this.message = message;
+    return this;
+  }
+
+  public void unsetMessage() {
+    this.message = null;
+  }
+
+  /** Returns true if field message is set (has been assigned a value) and false otherwise */
+  public boolean isSetMessage() {
+    return this.message != null;
+  }
+
+  public void setMessageIsSet(boolean value) {
+    if (!value) {
+      this.message = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case ERROR_CODE:
+      if (value == null) {
+        unsetErrorCode();
+      } else {
+        setErrorCode((Integer)value);
+      }
+      break;
+
+    case MESSAGE:
+      if (value == null) {
+        unsetMessage();
+      } else {
+        setMessage((String)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case ERROR_CODE:
+      return Integer.valueOf(getErrorCode());
+
+    case MESSAGE:
+      return getMessage();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case ERROR_CODE:
+      return isSetErrorCode();
+    case MESSAGE:
+      return isSetMessage();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof SyncError)
+      return this.equals((SyncError)that);
+    return false;
+  }
+
+  public boolean equals(SyncError that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_errorCode = true;
+    boolean that_present_errorCode = true;
+    if (this_present_errorCode || that_present_errorCode) {
+      if (!(this_present_errorCode && that_present_errorCode))
+        return false;
+      if (this.errorCode != that.errorCode)
+        return false;
+    }
+
+    boolean this_present_message = true && this.isSetMessage();
+    boolean that_present_message = true && that.isSetMessage();
+    if (this_present_message || that_present_message) {
+      if (!(this_present_message && that_present_message))
+        return false;
+      if (!this.message.equals(that.message))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(SyncError other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    SyncError typedOther = (SyncError)other;
+
+    lastComparison = Boolean.valueOf(isSetErrorCode()).compareTo(typedOther.isSetErrorCode());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetErrorCode()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.errorCode, typedOther.errorCode);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetMessage()).compareTo(typedOther.isSetMessage());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetMessage()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.message, typedOther.message);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // ERROR_CODE
+          if (field.type == org.apache.thrift.protocol.TType.I32) {
+            this.errorCode = iprot.readI32();
+            setErrorCodeIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // MESSAGE
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.message = iprot.readString();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    oprot.writeFieldBegin(ERROR_CODE_FIELD_DESC);
+    oprot.writeI32(this.errorCode);
+    oprot.writeFieldEnd();
+    if (this.message != null) {
+      oprot.writeFieldBegin(MESSAGE_FIELD_DESC);
+      oprot.writeString(this.message);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("SyncError(");
+    boolean first = true;
+
+    sb.append("errorCode:");
+    sb.append(this.errorCode);
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("message:");
+    if (this.message == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.message);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/SyncMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/SyncMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..e068c82499103b095637fd534dc0a8f9b10cf9d2
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/SyncMessage.java
@@ -0,0 +1,2086 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class SyncMessage implements org.apache.thrift.TBase<SyncMessage, SyncMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("SyncMessage");
+
+  private static final org.apache.thrift.protocol.TField TYPE_FIELD_DESC = new org.apache.thrift.protocol.TField("type", org.apache.thrift.protocol.TType.I32, (short)1);
+  private static final org.apache.thrift.protocol.TField HELLO_FIELD_DESC = new org.apache.thrift.protocol.TField("hello", org.apache.thrift.protocol.TType.STRUCT, (short)2);
+  private static final org.apache.thrift.protocol.TField ERROR_FIELD_DESC = new org.apache.thrift.protocol.TField("error", org.apache.thrift.protocol.TType.STRUCT, (short)3);
+  private static final org.apache.thrift.protocol.TField ECHO_REQUEST_FIELD_DESC = new org.apache.thrift.protocol.TField("echoRequest", org.apache.thrift.protocol.TType.STRUCT, (short)4);
+  private static final org.apache.thrift.protocol.TField ECHO_REPLY_FIELD_DESC = new org.apache.thrift.protocol.TField("echoReply", org.apache.thrift.protocol.TType.STRUCT, (short)5);
+  private static final org.apache.thrift.protocol.TField GET_REQUEST_FIELD_DESC = new org.apache.thrift.protocol.TField("getRequest", org.apache.thrift.protocol.TType.STRUCT, (short)6);
+  private static final org.apache.thrift.protocol.TField GET_RESPONSE_FIELD_DESC = new org.apache.thrift.protocol.TField("getResponse", org.apache.thrift.protocol.TType.STRUCT, (short)7);
+  private static final org.apache.thrift.protocol.TField PUT_REQUEST_FIELD_DESC = new org.apache.thrift.protocol.TField("putRequest", org.apache.thrift.protocol.TType.STRUCT, (short)8);
+  private static final org.apache.thrift.protocol.TField PUT_RESPONSE_FIELD_DESC = new org.apache.thrift.protocol.TField("putResponse", org.apache.thrift.protocol.TType.STRUCT, (short)9);
+  private static final org.apache.thrift.protocol.TField DELETE_REQUEST_FIELD_DESC = new org.apache.thrift.protocol.TField("deleteRequest", org.apache.thrift.protocol.TType.STRUCT, (short)10);
+  private static final org.apache.thrift.protocol.TField DELETE_RESPONSE_FIELD_DESC = new org.apache.thrift.protocol.TField("deleteResponse", org.apache.thrift.protocol.TType.STRUCT, (short)11);
+  private static final org.apache.thrift.protocol.TField SYNC_VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("syncValue", org.apache.thrift.protocol.TType.STRUCT, (short)12);
+  private static final org.apache.thrift.protocol.TField SYNC_VALUE_RESPONSE_FIELD_DESC = new org.apache.thrift.protocol.TField("syncValueResponse", org.apache.thrift.protocol.TType.STRUCT, (short)13);
+  private static final org.apache.thrift.protocol.TField SYNC_OFFER_FIELD_DESC = new org.apache.thrift.protocol.TField("syncOffer", org.apache.thrift.protocol.TType.STRUCT, (short)14);
+  private static final org.apache.thrift.protocol.TField SYNC_REQUEST_FIELD_DESC = new org.apache.thrift.protocol.TField("syncRequest", org.apache.thrift.protocol.TType.STRUCT, (short)15);
+  private static final org.apache.thrift.protocol.TField FULL_SYNC_REQUEST_FIELD_DESC = new org.apache.thrift.protocol.TField("fullSyncRequest", org.apache.thrift.protocol.TType.STRUCT, (short)16);
+  private static final org.apache.thrift.protocol.TField CURSOR_REQUEST_FIELD_DESC = new org.apache.thrift.protocol.TField("cursorRequest", org.apache.thrift.protocol.TType.STRUCT, (short)17);
+  private static final org.apache.thrift.protocol.TField CURSOR_RESPONSE_FIELD_DESC = new org.apache.thrift.protocol.TField("cursorResponse", org.apache.thrift.protocol.TType.STRUCT, (short)18);
+  private static final org.apache.thrift.protocol.TField REGISTER_REQUEST_FIELD_DESC = new org.apache.thrift.protocol.TField("registerRequest", org.apache.thrift.protocol.TType.STRUCT, (short)19);
+  private static final org.apache.thrift.protocol.TField REGISTER_RESPONSE_FIELD_DESC = new org.apache.thrift.protocol.TField("registerResponse", org.apache.thrift.protocol.TType.STRUCT, (short)20);
+
+  /**
+   * 
+   * @see MessageType
+   */
+  public MessageType type; // required
+  public HelloMessage hello; // required
+  public ErrorMessage error; // required
+  public EchoRequestMessage echoRequest; // required
+  public EchoReplyMessage echoReply; // required
+  public GetRequestMessage getRequest; // required
+  public GetResponseMessage getResponse; // required
+  public PutRequestMessage putRequest; // required
+  public PutResponseMessage putResponse; // required
+  public DeleteRequestMessage deleteRequest; // required
+  public DeleteResponseMessage deleteResponse; // required
+  public SyncValueMessage syncValue; // required
+  public SyncValueResponseMessage syncValueResponse; // required
+  public SyncOfferMessage syncOffer; // required
+  public SyncRequestMessage syncRequest; // required
+  public FullSyncRequestMessage fullSyncRequest; // required
+  public CursorRequestMessage cursorRequest; // required
+  public CursorResponseMessage cursorResponse; // required
+  public RegisterRequestMessage registerRequest; // required
+  public RegisterResponseMessage registerResponse; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    /**
+     * 
+     * @see MessageType
+     */
+    TYPE((short)1, "type"),
+    HELLO((short)2, "hello"),
+    ERROR((short)3, "error"),
+    ECHO_REQUEST((short)4, "echoRequest"),
+    ECHO_REPLY((short)5, "echoReply"),
+    GET_REQUEST((short)6, "getRequest"),
+    GET_RESPONSE((short)7, "getResponse"),
+    PUT_REQUEST((short)8, "putRequest"),
+    PUT_RESPONSE((short)9, "putResponse"),
+    DELETE_REQUEST((short)10, "deleteRequest"),
+    DELETE_RESPONSE((short)11, "deleteResponse"),
+    SYNC_VALUE((short)12, "syncValue"),
+    SYNC_VALUE_RESPONSE((short)13, "syncValueResponse"),
+    SYNC_OFFER((short)14, "syncOffer"),
+    SYNC_REQUEST((short)15, "syncRequest"),
+    FULL_SYNC_REQUEST((short)16, "fullSyncRequest"),
+    CURSOR_REQUEST((short)17, "cursorRequest"),
+    CURSOR_RESPONSE((short)18, "cursorResponse"),
+    REGISTER_REQUEST((short)19, "registerRequest"),
+    REGISTER_RESPONSE((short)20, "registerResponse");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // TYPE
+          return TYPE;
+        case 2: // HELLO
+          return HELLO;
+        case 3: // ERROR
+          return ERROR;
+        case 4: // ECHO_REQUEST
+          return ECHO_REQUEST;
+        case 5: // ECHO_REPLY
+          return ECHO_REPLY;
+        case 6: // GET_REQUEST
+          return GET_REQUEST;
+        case 7: // GET_RESPONSE
+          return GET_RESPONSE;
+        case 8: // PUT_REQUEST
+          return PUT_REQUEST;
+        case 9: // PUT_RESPONSE
+          return PUT_RESPONSE;
+        case 10: // DELETE_REQUEST
+          return DELETE_REQUEST;
+        case 11: // DELETE_RESPONSE
+          return DELETE_RESPONSE;
+        case 12: // SYNC_VALUE
+          return SYNC_VALUE;
+        case 13: // SYNC_VALUE_RESPONSE
+          return SYNC_VALUE_RESPONSE;
+        case 14: // SYNC_OFFER
+          return SYNC_OFFER;
+        case 15: // SYNC_REQUEST
+          return SYNC_REQUEST;
+        case 16: // FULL_SYNC_REQUEST
+          return FULL_SYNC_REQUEST;
+        case 17: // CURSOR_REQUEST
+          return CURSOR_REQUEST;
+        case 18: // CURSOR_RESPONSE
+          return CURSOR_RESPONSE;
+        case 19: // REGISTER_REQUEST
+          return REGISTER_REQUEST;
+        case 20: // REGISTER_RESPONSE
+          return REGISTER_RESPONSE;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.TYPE, new org.apache.thrift.meta_data.FieldMetaData("type", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, MessageType.class)));
+    tmpMap.put(_Fields.HELLO, new org.apache.thrift.meta_data.FieldMetaData("hello", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, HelloMessage.class)));
+    tmpMap.put(_Fields.ERROR, new org.apache.thrift.meta_data.FieldMetaData("error", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, ErrorMessage.class)));
+    tmpMap.put(_Fields.ECHO_REQUEST, new org.apache.thrift.meta_data.FieldMetaData("echoRequest", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, EchoRequestMessage.class)));
+    tmpMap.put(_Fields.ECHO_REPLY, new org.apache.thrift.meta_data.FieldMetaData("echoReply", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, EchoReplyMessage.class)));
+    tmpMap.put(_Fields.GET_REQUEST, new org.apache.thrift.meta_data.FieldMetaData("getRequest", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, GetRequestMessage.class)));
+    tmpMap.put(_Fields.GET_RESPONSE, new org.apache.thrift.meta_data.FieldMetaData("getResponse", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, GetResponseMessage.class)));
+    tmpMap.put(_Fields.PUT_REQUEST, new org.apache.thrift.meta_data.FieldMetaData("putRequest", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, PutRequestMessage.class)));
+    tmpMap.put(_Fields.PUT_RESPONSE, new org.apache.thrift.meta_data.FieldMetaData("putResponse", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, PutResponseMessage.class)));
+    tmpMap.put(_Fields.DELETE_REQUEST, new org.apache.thrift.meta_data.FieldMetaData("deleteRequest", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, DeleteRequestMessage.class)));
+    tmpMap.put(_Fields.DELETE_RESPONSE, new org.apache.thrift.meta_data.FieldMetaData("deleteResponse", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, DeleteResponseMessage.class)));
+    tmpMap.put(_Fields.SYNC_VALUE, new org.apache.thrift.meta_data.FieldMetaData("syncValue", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, SyncValueMessage.class)));
+    tmpMap.put(_Fields.SYNC_VALUE_RESPONSE, new org.apache.thrift.meta_data.FieldMetaData("syncValueResponse", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, SyncValueResponseMessage.class)));
+    tmpMap.put(_Fields.SYNC_OFFER, new org.apache.thrift.meta_data.FieldMetaData("syncOffer", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, SyncOfferMessage.class)));
+    tmpMap.put(_Fields.SYNC_REQUEST, new org.apache.thrift.meta_data.FieldMetaData("syncRequest", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, SyncRequestMessage.class)));
+    tmpMap.put(_Fields.FULL_SYNC_REQUEST, new org.apache.thrift.meta_data.FieldMetaData("fullSyncRequest", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, FullSyncRequestMessage.class)));
+    tmpMap.put(_Fields.CURSOR_REQUEST, new org.apache.thrift.meta_data.FieldMetaData("cursorRequest", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, CursorRequestMessage.class)));
+    tmpMap.put(_Fields.CURSOR_RESPONSE, new org.apache.thrift.meta_data.FieldMetaData("cursorResponse", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, CursorResponseMessage.class)));
+    tmpMap.put(_Fields.REGISTER_REQUEST, new org.apache.thrift.meta_data.FieldMetaData("registerRequest", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RegisterRequestMessage.class)));
+    tmpMap.put(_Fields.REGISTER_RESPONSE, new org.apache.thrift.meta_data.FieldMetaData("registerResponse", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, RegisterResponseMessage.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(SyncMessage.class, metaDataMap);
+  }
+
+  public SyncMessage() {
+  }
+
+  public SyncMessage(
+    MessageType type)
+  {
+    this();
+    this.type = type;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public SyncMessage(SyncMessage other) {
+    if (other.isSetType()) {
+      this.type = other.type;
+    }
+    if (other.isSetHello()) {
+      this.hello = new HelloMessage(other.hello);
+    }
+    if (other.isSetError()) {
+      this.error = new ErrorMessage(other.error);
+    }
+    if (other.isSetEchoRequest()) {
+      this.echoRequest = new EchoRequestMessage(other.echoRequest);
+    }
+    if (other.isSetEchoReply()) {
+      this.echoReply = new EchoReplyMessage(other.echoReply);
+    }
+    if (other.isSetGetRequest()) {
+      this.getRequest = new GetRequestMessage(other.getRequest);
+    }
+    if (other.isSetGetResponse()) {
+      this.getResponse = new GetResponseMessage(other.getResponse);
+    }
+    if (other.isSetPutRequest()) {
+      this.putRequest = new PutRequestMessage(other.putRequest);
+    }
+    if (other.isSetPutResponse()) {
+      this.putResponse = new PutResponseMessage(other.putResponse);
+    }
+    if (other.isSetDeleteRequest()) {
+      this.deleteRequest = new DeleteRequestMessage(other.deleteRequest);
+    }
+    if (other.isSetDeleteResponse()) {
+      this.deleteResponse = new DeleteResponseMessage(other.deleteResponse);
+    }
+    if (other.isSetSyncValue()) {
+      this.syncValue = new SyncValueMessage(other.syncValue);
+    }
+    if (other.isSetSyncValueResponse()) {
+      this.syncValueResponse = new SyncValueResponseMessage(other.syncValueResponse);
+    }
+    if (other.isSetSyncOffer()) {
+      this.syncOffer = new SyncOfferMessage(other.syncOffer);
+    }
+    if (other.isSetSyncRequest()) {
+      this.syncRequest = new SyncRequestMessage(other.syncRequest);
+    }
+    if (other.isSetFullSyncRequest()) {
+      this.fullSyncRequest = new FullSyncRequestMessage(other.fullSyncRequest);
+    }
+    if (other.isSetCursorRequest()) {
+      this.cursorRequest = new CursorRequestMessage(other.cursorRequest);
+    }
+    if (other.isSetCursorResponse()) {
+      this.cursorResponse = new CursorResponseMessage(other.cursorResponse);
+    }
+    if (other.isSetRegisterRequest()) {
+      this.registerRequest = new RegisterRequestMessage(other.registerRequest);
+    }
+    if (other.isSetRegisterResponse()) {
+      this.registerResponse = new RegisterResponseMessage(other.registerResponse);
+    }
+  }
+
+  public SyncMessage deepCopy() {
+    return new SyncMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.type = null;
+    this.hello = null;
+    this.error = null;
+    this.echoRequest = null;
+    this.echoReply = null;
+    this.getRequest = null;
+    this.getResponse = null;
+    this.putRequest = null;
+    this.putResponse = null;
+    this.deleteRequest = null;
+    this.deleteResponse = null;
+    this.syncValue = null;
+    this.syncValueResponse = null;
+    this.syncOffer = null;
+    this.syncRequest = null;
+    this.fullSyncRequest = null;
+    this.cursorRequest = null;
+    this.cursorResponse = null;
+    this.registerRequest = null;
+    this.registerResponse = null;
+  }
+
+  /**
+   * 
+   * @see MessageType
+   */
+  public MessageType getType() {
+    return this.type;
+  }
+
+  /**
+   * 
+   * @see MessageType
+   */
+  public SyncMessage setType(MessageType type) {
+    this.type = type;
+    return this;
+  }
+
+  public void unsetType() {
+    this.type = null;
+  }
+
+  /** Returns true if field type is set (has been assigned a value) and false otherwise */
+  public boolean isSetType() {
+    return this.type != null;
+  }
+
+  public void setTypeIsSet(boolean value) {
+    if (!value) {
+      this.type = null;
+    }
+  }
+
+  public HelloMessage getHello() {
+    return this.hello;
+  }
+
+  public SyncMessage setHello(HelloMessage hello) {
+    this.hello = hello;
+    return this;
+  }
+
+  public void unsetHello() {
+    this.hello = null;
+  }
+
+  /** Returns true if field hello is set (has been assigned a value) and false otherwise */
+  public boolean isSetHello() {
+    return this.hello != null;
+  }
+
+  public void setHelloIsSet(boolean value) {
+    if (!value) {
+      this.hello = null;
+    }
+  }
+
+  public ErrorMessage getError() {
+    return this.error;
+  }
+
+  public SyncMessage setError(ErrorMessage error) {
+    this.error = error;
+    return this;
+  }
+
+  public void unsetError() {
+    this.error = null;
+  }
+
+  /** Returns true if field error is set (has been assigned a value) and false otherwise */
+  public boolean isSetError() {
+    return this.error != null;
+  }
+
+  public void setErrorIsSet(boolean value) {
+    if (!value) {
+      this.error = null;
+    }
+  }
+
+  public EchoRequestMessage getEchoRequest() {
+    return this.echoRequest;
+  }
+
+  public SyncMessage setEchoRequest(EchoRequestMessage echoRequest) {
+    this.echoRequest = echoRequest;
+    return this;
+  }
+
+  public void unsetEchoRequest() {
+    this.echoRequest = null;
+  }
+
+  /** Returns true if field echoRequest is set (has been assigned a value) and false otherwise */
+  public boolean isSetEchoRequest() {
+    return this.echoRequest != null;
+  }
+
+  public void setEchoRequestIsSet(boolean value) {
+    if (!value) {
+      this.echoRequest = null;
+    }
+  }
+
+  public EchoReplyMessage getEchoReply() {
+    return this.echoReply;
+  }
+
+  public SyncMessage setEchoReply(EchoReplyMessage echoReply) {
+    this.echoReply = echoReply;
+    return this;
+  }
+
+  public void unsetEchoReply() {
+    this.echoReply = null;
+  }
+
+  /** Returns true if field echoReply is set (has been assigned a value) and false otherwise */
+  public boolean isSetEchoReply() {
+    return this.echoReply != null;
+  }
+
+  public void setEchoReplyIsSet(boolean value) {
+    if (!value) {
+      this.echoReply = null;
+    }
+  }
+
+  public GetRequestMessage getGetRequest() {
+    return this.getRequest;
+  }
+
+  public SyncMessage setGetRequest(GetRequestMessage getRequest) {
+    this.getRequest = getRequest;
+    return this;
+  }
+
+  public void unsetGetRequest() {
+    this.getRequest = null;
+  }
+
+  /** Returns true if field getRequest is set (has been assigned a value) and false otherwise */
+  public boolean isSetGetRequest() {
+    return this.getRequest != null;
+  }
+
+  public void setGetRequestIsSet(boolean value) {
+    if (!value) {
+      this.getRequest = null;
+    }
+  }
+
+  public GetResponseMessage getGetResponse() {
+    return this.getResponse;
+  }
+
+  public SyncMessage setGetResponse(GetResponseMessage getResponse) {
+    this.getResponse = getResponse;
+    return this;
+  }
+
+  public void unsetGetResponse() {
+    this.getResponse = null;
+  }
+
+  /** Returns true if field getResponse is set (has been assigned a value) and false otherwise */
+  public boolean isSetGetResponse() {
+    return this.getResponse != null;
+  }
+
+  public void setGetResponseIsSet(boolean value) {
+    if (!value) {
+      this.getResponse = null;
+    }
+  }
+
+  public PutRequestMessage getPutRequest() {
+    return this.putRequest;
+  }
+
+  public SyncMessage setPutRequest(PutRequestMessage putRequest) {
+    this.putRequest = putRequest;
+    return this;
+  }
+
+  public void unsetPutRequest() {
+    this.putRequest = null;
+  }
+
+  /** Returns true if field putRequest is set (has been assigned a value) and false otherwise */
+  public boolean isSetPutRequest() {
+    return this.putRequest != null;
+  }
+
+  public void setPutRequestIsSet(boolean value) {
+    if (!value) {
+      this.putRequest = null;
+    }
+  }
+
+  public PutResponseMessage getPutResponse() {
+    return this.putResponse;
+  }
+
+  public SyncMessage setPutResponse(PutResponseMessage putResponse) {
+    this.putResponse = putResponse;
+    return this;
+  }
+
+  public void unsetPutResponse() {
+    this.putResponse = null;
+  }
+
+  /** Returns true if field putResponse is set (has been assigned a value) and false otherwise */
+  public boolean isSetPutResponse() {
+    return this.putResponse != null;
+  }
+
+  public void setPutResponseIsSet(boolean value) {
+    if (!value) {
+      this.putResponse = null;
+    }
+  }
+
+  public DeleteRequestMessage getDeleteRequest() {
+    return this.deleteRequest;
+  }
+
+  public SyncMessage setDeleteRequest(DeleteRequestMessage deleteRequest) {
+    this.deleteRequest = deleteRequest;
+    return this;
+  }
+
+  public void unsetDeleteRequest() {
+    this.deleteRequest = null;
+  }
+
+  /** Returns true if field deleteRequest is set (has been assigned a value) and false otherwise */
+  public boolean isSetDeleteRequest() {
+    return this.deleteRequest != null;
+  }
+
+  public void setDeleteRequestIsSet(boolean value) {
+    if (!value) {
+      this.deleteRequest = null;
+    }
+  }
+
+  public DeleteResponseMessage getDeleteResponse() {
+    return this.deleteResponse;
+  }
+
+  public SyncMessage setDeleteResponse(DeleteResponseMessage deleteResponse) {
+    this.deleteResponse = deleteResponse;
+    return this;
+  }
+
+  public void unsetDeleteResponse() {
+    this.deleteResponse = null;
+  }
+
+  /** Returns true if field deleteResponse is set (has been assigned a value) and false otherwise */
+  public boolean isSetDeleteResponse() {
+    return this.deleteResponse != null;
+  }
+
+  public void setDeleteResponseIsSet(boolean value) {
+    if (!value) {
+      this.deleteResponse = null;
+    }
+  }
+
+  public SyncValueMessage getSyncValue() {
+    return this.syncValue;
+  }
+
+  public SyncMessage setSyncValue(SyncValueMessage syncValue) {
+    this.syncValue = syncValue;
+    return this;
+  }
+
+  public void unsetSyncValue() {
+    this.syncValue = null;
+  }
+
+  /** Returns true if field syncValue is set (has been assigned a value) and false otherwise */
+  public boolean isSetSyncValue() {
+    return this.syncValue != null;
+  }
+
+  public void setSyncValueIsSet(boolean value) {
+    if (!value) {
+      this.syncValue = null;
+    }
+  }
+
+  public SyncValueResponseMessage getSyncValueResponse() {
+    return this.syncValueResponse;
+  }
+
+  public SyncMessage setSyncValueResponse(SyncValueResponseMessage syncValueResponse) {
+    this.syncValueResponse = syncValueResponse;
+    return this;
+  }
+
+  public void unsetSyncValueResponse() {
+    this.syncValueResponse = null;
+  }
+
+  /** Returns true if field syncValueResponse is set (has been assigned a value) and false otherwise */
+  public boolean isSetSyncValueResponse() {
+    return this.syncValueResponse != null;
+  }
+
+  public void setSyncValueResponseIsSet(boolean value) {
+    if (!value) {
+      this.syncValueResponse = null;
+    }
+  }
+
+  public SyncOfferMessage getSyncOffer() {
+    return this.syncOffer;
+  }
+
+  public SyncMessage setSyncOffer(SyncOfferMessage syncOffer) {
+    this.syncOffer = syncOffer;
+    return this;
+  }
+
+  public void unsetSyncOffer() {
+    this.syncOffer = null;
+  }
+
+  /** Returns true if field syncOffer is set (has been assigned a value) and false otherwise */
+  public boolean isSetSyncOffer() {
+    return this.syncOffer != null;
+  }
+
+  public void setSyncOfferIsSet(boolean value) {
+    if (!value) {
+      this.syncOffer = null;
+    }
+  }
+
+  public SyncRequestMessage getSyncRequest() {
+    return this.syncRequest;
+  }
+
+  public SyncMessage setSyncRequest(SyncRequestMessage syncRequest) {
+    this.syncRequest = syncRequest;
+    return this;
+  }
+
+  public void unsetSyncRequest() {
+    this.syncRequest = null;
+  }
+
+  /** Returns true if field syncRequest is set (has been assigned a value) and false otherwise */
+  public boolean isSetSyncRequest() {
+    return this.syncRequest != null;
+  }
+
+  public void setSyncRequestIsSet(boolean value) {
+    if (!value) {
+      this.syncRequest = null;
+    }
+  }
+
+  public FullSyncRequestMessage getFullSyncRequest() {
+    return this.fullSyncRequest;
+  }
+
+  public SyncMessage setFullSyncRequest(FullSyncRequestMessage fullSyncRequest) {
+    this.fullSyncRequest = fullSyncRequest;
+    return this;
+  }
+
+  public void unsetFullSyncRequest() {
+    this.fullSyncRequest = null;
+  }
+
+  /** Returns true if field fullSyncRequest is set (has been assigned a value) and false otherwise */
+  public boolean isSetFullSyncRequest() {
+    return this.fullSyncRequest != null;
+  }
+
+  public void setFullSyncRequestIsSet(boolean value) {
+    if (!value) {
+      this.fullSyncRequest = null;
+    }
+  }
+
+  public CursorRequestMessage getCursorRequest() {
+    return this.cursorRequest;
+  }
+
+  public SyncMessage setCursorRequest(CursorRequestMessage cursorRequest) {
+    this.cursorRequest = cursorRequest;
+    return this;
+  }
+
+  public void unsetCursorRequest() {
+    this.cursorRequest = null;
+  }
+
+  /** Returns true if field cursorRequest is set (has been assigned a value) and false otherwise */
+  public boolean isSetCursorRequest() {
+    return this.cursorRequest != null;
+  }
+
+  public void setCursorRequestIsSet(boolean value) {
+    if (!value) {
+      this.cursorRequest = null;
+    }
+  }
+
+  public CursorResponseMessage getCursorResponse() {
+    return this.cursorResponse;
+  }
+
+  public SyncMessage setCursorResponse(CursorResponseMessage cursorResponse) {
+    this.cursorResponse = cursorResponse;
+    return this;
+  }
+
+  public void unsetCursorResponse() {
+    this.cursorResponse = null;
+  }
+
+  /** Returns true if field cursorResponse is set (has been assigned a value) and false otherwise */
+  public boolean isSetCursorResponse() {
+    return this.cursorResponse != null;
+  }
+
+  public void setCursorResponseIsSet(boolean value) {
+    if (!value) {
+      this.cursorResponse = null;
+    }
+  }
+
+  public RegisterRequestMessage getRegisterRequest() {
+    return this.registerRequest;
+  }
+
+  public SyncMessage setRegisterRequest(RegisterRequestMessage registerRequest) {
+    this.registerRequest = registerRequest;
+    return this;
+  }
+
+  public void unsetRegisterRequest() {
+    this.registerRequest = null;
+  }
+
+  /** Returns true if field registerRequest is set (has been assigned a value) and false otherwise */
+  public boolean isSetRegisterRequest() {
+    return this.registerRequest != null;
+  }
+
+  public void setRegisterRequestIsSet(boolean value) {
+    if (!value) {
+      this.registerRequest = null;
+    }
+  }
+
+  public RegisterResponseMessage getRegisterResponse() {
+    return this.registerResponse;
+  }
+
+  public SyncMessage setRegisterResponse(RegisterResponseMessage registerResponse) {
+    this.registerResponse = registerResponse;
+    return this;
+  }
+
+  public void unsetRegisterResponse() {
+    this.registerResponse = null;
+  }
+
+  /** Returns true if field registerResponse is set (has been assigned a value) and false otherwise */
+  public boolean isSetRegisterResponse() {
+    return this.registerResponse != null;
+  }
+
+  public void setRegisterResponseIsSet(boolean value) {
+    if (!value) {
+      this.registerResponse = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case TYPE:
+      if (value == null) {
+        unsetType();
+      } else {
+        setType((MessageType)value);
+      }
+      break;
+
+    case HELLO:
+      if (value == null) {
+        unsetHello();
+      } else {
+        setHello((HelloMessage)value);
+      }
+      break;
+
+    case ERROR:
+      if (value == null) {
+        unsetError();
+      } else {
+        setError((ErrorMessage)value);
+      }
+      break;
+
+    case ECHO_REQUEST:
+      if (value == null) {
+        unsetEchoRequest();
+      } else {
+        setEchoRequest((EchoRequestMessage)value);
+      }
+      break;
+
+    case ECHO_REPLY:
+      if (value == null) {
+        unsetEchoReply();
+      } else {
+        setEchoReply((EchoReplyMessage)value);
+      }
+      break;
+
+    case GET_REQUEST:
+      if (value == null) {
+        unsetGetRequest();
+      } else {
+        setGetRequest((GetRequestMessage)value);
+      }
+      break;
+
+    case GET_RESPONSE:
+      if (value == null) {
+        unsetGetResponse();
+      } else {
+        setGetResponse((GetResponseMessage)value);
+      }
+      break;
+
+    case PUT_REQUEST:
+      if (value == null) {
+        unsetPutRequest();
+      } else {
+        setPutRequest((PutRequestMessage)value);
+      }
+      break;
+
+    case PUT_RESPONSE:
+      if (value == null) {
+        unsetPutResponse();
+      } else {
+        setPutResponse((PutResponseMessage)value);
+      }
+      break;
+
+    case DELETE_REQUEST:
+      if (value == null) {
+        unsetDeleteRequest();
+      } else {
+        setDeleteRequest((DeleteRequestMessage)value);
+      }
+      break;
+
+    case DELETE_RESPONSE:
+      if (value == null) {
+        unsetDeleteResponse();
+      } else {
+        setDeleteResponse((DeleteResponseMessage)value);
+      }
+      break;
+
+    case SYNC_VALUE:
+      if (value == null) {
+        unsetSyncValue();
+      } else {
+        setSyncValue((SyncValueMessage)value);
+      }
+      break;
+
+    case SYNC_VALUE_RESPONSE:
+      if (value == null) {
+        unsetSyncValueResponse();
+      } else {
+        setSyncValueResponse((SyncValueResponseMessage)value);
+      }
+      break;
+
+    case SYNC_OFFER:
+      if (value == null) {
+        unsetSyncOffer();
+      } else {
+        setSyncOffer((SyncOfferMessage)value);
+      }
+      break;
+
+    case SYNC_REQUEST:
+      if (value == null) {
+        unsetSyncRequest();
+      } else {
+        setSyncRequest((SyncRequestMessage)value);
+      }
+      break;
+
+    case FULL_SYNC_REQUEST:
+      if (value == null) {
+        unsetFullSyncRequest();
+      } else {
+        setFullSyncRequest((FullSyncRequestMessage)value);
+      }
+      break;
+
+    case CURSOR_REQUEST:
+      if (value == null) {
+        unsetCursorRequest();
+      } else {
+        setCursorRequest((CursorRequestMessage)value);
+      }
+      break;
+
+    case CURSOR_RESPONSE:
+      if (value == null) {
+        unsetCursorResponse();
+      } else {
+        setCursorResponse((CursorResponseMessage)value);
+      }
+      break;
+
+    case REGISTER_REQUEST:
+      if (value == null) {
+        unsetRegisterRequest();
+      } else {
+        setRegisterRequest((RegisterRequestMessage)value);
+      }
+      break;
+
+    case REGISTER_RESPONSE:
+      if (value == null) {
+        unsetRegisterResponse();
+      } else {
+        setRegisterResponse((RegisterResponseMessage)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case TYPE:
+      return getType();
+
+    case HELLO:
+      return getHello();
+
+    case ERROR:
+      return getError();
+
+    case ECHO_REQUEST:
+      return getEchoRequest();
+
+    case ECHO_REPLY:
+      return getEchoReply();
+
+    case GET_REQUEST:
+      return getGetRequest();
+
+    case GET_RESPONSE:
+      return getGetResponse();
+
+    case PUT_REQUEST:
+      return getPutRequest();
+
+    case PUT_RESPONSE:
+      return getPutResponse();
+
+    case DELETE_REQUEST:
+      return getDeleteRequest();
+
+    case DELETE_RESPONSE:
+      return getDeleteResponse();
+
+    case SYNC_VALUE:
+      return getSyncValue();
+
+    case SYNC_VALUE_RESPONSE:
+      return getSyncValueResponse();
+
+    case SYNC_OFFER:
+      return getSyncOffer();
+
+    case SYNC_REQUEST:
+      return getSyncRequest();
+
+    case FULL_SYNC_REQUEST:
+      return getFullSyncRequest();
+
+    case CURSOR_REQUEST:
+      return getCursorRequest();
+
+    case CURSOR_RESPONSE:
+      return getCursorResponse();
+
+    case REGISTER_REQUEST:
+      return getRegisterRequest();
+
+    case REGISTER_RESPONSE:
+      return getRegisterResponse();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case TYPE:
+      return isSetType();
+    case HELLO:
+      return isSetHello();
+    case ERROR:
+      return isSetError();
+    case ECHO_REQUEST:
+      return isSetEchoRequest();
+    case ECHO_REPLY:
+      return isSetEchoReply();
+    case GET_REQUEST:
+      return isSetGetRequest();
+    case GET_RESPONSE:
+      return isSetGetResponse();
+    case PUT_REQUEST:
+      return isSetPutRequest();
+    case PUT_RESPONSE:
+      return isSetPutResponse();
+    case DELETE_REQUEST:
+      return isSetDeleteRequest();
+    case DELETE_RESPONSE:
+      return isSetDeleteResponse();
+    case SYNC_VALUE:
+      return isSetSyncValue();
+    case SYNC_VALUE_RESPONSE:
+      return isSetSyncValueResponse();
+    case SYNC_OFFER:
+      return isSetSyncOffer();
+    case SYNC_REQUEST:
+      return isSetSyncRequest();
+    case FULL_SYNC_REQUEST:
+      return isSetFullSyncRequest();
+    case CURSOR_REQUEST:
+      return isSetCursorRequest();
+    case CURSOR_RESPONSE:
+      return isSetCursorResponse();
+    case REGISTER_REQUEST:
+      return isSetRegisterRequest();
+    case REGISTER_RESPONSE:
+      return isSetRegisterResponse();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof SyncMessage)
+      return this.equals((SyncMessage)that);
+    return false;
+  }
+
+  public boolean equals(SyncMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_type = true && this.isSetType();
+    boolean that_present_type = true && that.isSetType();
+    if (this_present_type || that_present_type) {
+      if (!(this_present_type && that_present_type))
+        return false;
+      if (!this.type.equals(that.type))
+        return false;
+    }
+
+    boolean this_present_hello = true && this.isSetHello();
+    boolean that_present_hello = true && that.isSetHello();
+    if (this_present_hello || that_present_hello) {
+      if (!(this_present_hello && that_present_hello))
+        return false;
+      if (!this.hello.equals(that.hello))
+        return false;
+    }
+
+    boolean this_present_error = true && this.isSetError();
+    boolean that_present_error = true && that.isSetError();
+    if (this_present_error || that_present_error) {
+      if (!(this_present_error && that_present_error))
+        return false;
+      if (!this.error.equals(that.error))
+        return false;
+    }
+
+    boolean this_present_echoRequest = true && this.isSetEchoRequest();
+    boolean that_present_echoRequest = true && that.isSetEchoRequest();
+    if (this_present_echoRequest || that_present_echoRequest) {
+      if (!(this_present_echoRequest && that_present_echoRequest))
+        return false;
+      if (!this.echoRequest.equals(that.echoRequest))
+        return false;
+    }
+
+    boolean this_present_echoReply = true && this.isSetEchoReply();
+    boolean that_present_echoReply = true && that.isSetEchoReply();
+    if (this_present_echoReply || that_present_echoReply) {
+      if (!(this_present_echoReply && that_present_echoReply))
+        return false;
+      if (!this.echoReply.equals(that.echoReply))
+        return false;
+    }
+
+    boolean this_present_getRequest = true && this.isSetGetRequest();
+    boolean that_present_getRequest = true && that.isSetGetRequest();
+    if (this_present_getRequest || that_present_getRequest) {
+      if (!(this_present_getRequest && that_present_getRequest))
+        return false;
+      if (!this.getRequest.equals(that.getRequest))
+        return false;
+    }
+
+    boolean this_present_getResponse = true && this.isSetGetResponse();
+    boolean that_present_getResponse = true && that.isSetGetResponse();
+    if (this_present_getResponse || that_present_getResponse) {
+      if (!(this_present_getResponse && that_present_getResponse))
+        return false;
+      if (!this.getResponse.equals(that.getResponse))
+        return false;
+    }
+
+    boolean this_present_putRequest = true && this.isSetPutRequest();
+    boolean that_present_putRequest = true && that.isSetPutRequest();
+    if (this_present_putRequest || that_present_putRequest) {
+      if (!(this_present_putRequest && that_present_putRequest))
+        return false;
+      if (!this.putRequest.equals(that.putRequest))
+        return false;
+    }
+
+    boolean this_present_putResponse = true && this.isSetPutResponse();
+    boolean that_present_putResponse = true && that.isSetPutResponse();
+    if (this_present_putResponse || that_present_putResponse) {
+      if (!(this_present_putResponse && that_present_putResponse))
+        return false;
+      if (!this.putResponse.equals(that.putResponse))
+        return false;
+    }
+
+    boolean this_present_deleteRequest = true && this.isSetDeleteRequest();
+    boolean that_present_deleteRequest = true && that.isSetDeleteRequest();
+    if (this_present_deleteRequest || that_present_deleteRequest) {
+      if (!(this_present_deleteRequest && that_present_deleteRequest))
+        return false;
+      if (!this.deleteRequest.equals(that.deleteRequest))
+        return false;
+    }
+
+    boolean this_present_deleteResponse = true && this.isSetDeleteResponse();
+    boolean that_present_deleteResponse = true && that.isSetDeleteResponse();
+    if (this_present_deleteResponse || that_present_deleteResponse) {
+      if (!(this_present_deleteResponse && that_present_deleteResponse))
+        return false;
+      if (!this.deleteResponse.equals(that.deleteResponse))
+        return false;
+    }
+
+    boolean this_present_syncValue = true && this.isSetSyncValue();
+    boolean that_present_syncValue = true && that.isSetSyncValue();
+    if (this_present_syncValue || that_present_syncValue) {
+      if (!(this_present_syncValue && that_present_syncValue))
+        return false;
+      if (!this.syncValue.equals(that.syncValue))
+        return false;
+    }
+
+    boolean this_present_syncValueResponse = true && this.isSetSyncValueResponse();
+    boolean that_present_syncValueResponse = true && that.isSetSyncValueResponse();
+    if (this_present_syncValueResponse || that_present_syncValueResponse) {
+      if (!(this_present_syncValueResponse && that_present_syncValueResponse))
+        return false;
+      if (!this.syncValueResponse.equals(that.syncValueResponse))
+        return false;
+    }
+
+    boolean this_present_syncOffer = true && this.isSetSyncOffer();
+    boolean that_present_syncOffer = true && that.isSetSyncOffer();
+    if (this_present_syncOffer || that_present_syncOffer) {
+      if (!(this_present_syncOffer && that_present_syncOffer))
+        return false;
+      if (!this.syncOffer.equals(that.syncOffer))
+        return false;
+    }
+
+    boolean this_present_syncRequest = true && this.isSetSyncRequest();
+    boolean that_present_syncRequest = true && that.isSetSyncRequest();
+    if (this_present_syncRequest || that_present_syncRequest) {
+      if (!(this_present_syncRequest && that_present_syncRequest))
+        return false;
+      if (!this.syncRequest.equals(that.syncRequest))
+        return false;
+    }
+
+    boolean this_present_fullSyncRequest = true && this.isSetFullSyncRequest();
+    boolean that_present_fullSyncRequest = true && that.isSetFullSyncRequest();
+    if (this_present_fullSyncRequest || that_present_fullSyncRequest) {
+      if (!(this_present_fullSyncRequest && that_present_fullSyncRequest))
+        return false;
+      if (!this.fullSyncRequest.equals(that.fullSyncRequest))
+        return false;
+    }
+
+    boolean this_present_cursorRequest = true && this.isSetCursorRequest();
+    boolean that_present_cursorRequest = true && that.isSetCursorRequest();
+    if (this_present_cursorRequest || that_present_cursorRequest) {
+      if (!(this_present_cursorRequest && that_present_cursorRequest))
+        return false;
+      if (!this.cursorRequest.equals(that.cursorRequest))
+        return false;
+    }
+
+    boolean this_present_cursorResponse = true && this.isSetCursorResponse();
+    boolean that_present_cursorResponse = true && that.isSetCursorResponse();
+    if (this_present_cursorResponse || that_present_cursorResponse) {
+      if (!(this_present_cursorResponse && that_present_cursorResponse))
+        return false;
+      if (!this.cursorResponse.equals(that.cursorResponse))
+        return false;
+    }
+
+    boolean this_present_registerRequest = true && this.isSetRegisterRequest();
+    boolean that_present_registerRequest = true && that.isSetRegisterRequest();
+    if (this_present_registerRequest || that_present_registerRequest) {
+      if (!(this_present_registerRequest && that_present_registerRequest))
+        return false;
+      if (!this.registerRequest.equals(that.registerRequest))
+        return false;
+    }
+
+    boolean this_present_registerResponse = true && this.isSetRegisterResponse();
+    boolean that_present_registerResponse = true && that.isSetRegisterResponse();
+    if (this_present_registerResponse || that_present_registerResponse) {
+      if (!(this_present_registerResponse && that_present_registerResponse))
+        return false;
+      if (!this.registerResponse.equals(that.registerResponse))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(SyncMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    SyncMessage typedOther = (SyncMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetType()).compareTo(typedOther.isSetType());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetType()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.type, typedOther.type);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetHello()).compareTo(typedOther.isSetHello());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHello()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.hello, typedOther.hello);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetError()).compareTo(typedOther.isSetError());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetError()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.error, typedOther.error);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetEchoRequest()).compareTo(typedOther.isSetEchoRequest());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetEchoRequest()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.echoRequest, typedOther.echoRequest);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetEchoReply()).compareTo(typedOther.isSetEchoReply());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetEchoReply()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.echoReply, typedOther.echoReply);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetGetRequest()).compareTo(typedOther.isSetGetRequest());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetGetRequest()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.getRequest, typedOther.getRequest);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetGetResponse()).compareTo(typedOther.isSetGetResponse());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetGetResponse()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.getResponse, typedOther.getResponse);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetPutRequest()).compareTo(typedOther.isSetPutRequest());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetPutRequest()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.putRequest, typedOther.putRequest);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetPutResponse()).compareTo(typedOther.isSetPutResponse());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetPutResponse()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.putResponse, typedOther.putResponse);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetDeleteRequest()).compareTo(typedOther.isSetDeleteRequest());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetDeleteRequest()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.deleteRequest, typedOther.deleteRequest);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetDeleteResponse()).compareTo(typedOther.isSetDeleteResponse());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetDeleteResponse()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.deleteResponse, typedOther.deleteResponse);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetSyncValue()).compareTo(typedOther.isSetSyncValue());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetSyncValue()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.syncValue, typedOther.syncValue);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetSyncValueResponse()).compareTo(typedOther.isSetSyncValueResponse());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetSyncValueResponse()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.syncValueResponse, typedOther.syncValueResponse);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetSyncOffer()).compareTo(typedOther.isSetSyncOffer());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetSyncOffer()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.syncOffer, typedOther.syncOffer);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetSyncRequest()).compareTo(typedOther.isSetSyncRequest());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetSyncRequest()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.syncRequest, typedOther.syncRequest);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetFullSyncRequest()).compareTo(typedOther.isSetFullSyncRequest());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetFullSyncRequest()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.fullSyncRequest, typedOther.fullSyncRequest);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetCursorRequest()).compareTo(typedOther.isSetCursorRequest());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetCursorRequest()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.cursorRequest, typedOther.cursorRequest);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetCursorResponse()).compareTo(typedOther.isSetCursorResponse());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetCursorResponse()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.cursorResponse, typedOther.cursorResponse);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetRegisterRequest()).compareTo(typedOther.isSetRegisterRequest());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetRegisterRequest()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.registerRequest, typedOther.registerRequest);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetRegisterResponse()).compareTo(typedOther.isSetRegisterResponse());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetRegisterResponse()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.registerResponse, typedOther.registerResponse);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // TYPE
+          if (field.type == org.apache.thrift.protocol.TType.I32) {
+            this.type = MessageType.findByValue(iprot.readI32());
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // HELLO
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.hello = new HelloMessage();
+            this.hello.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // ERROR
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.error = new ErrorMessage();
+            this.error.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 4: // ECHO_REQUEST
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.echoRequest = new EchoRequestMessage();
+            this.echoRequest.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 5: // ECHO_REPLY
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.echoReply = new EchoReplyMessage();
+            this.echoReply.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 6: // GET_REQUEST
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.getRequest = new GetRequestMessage();
+            this.getRequest.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 7: // GET_RESPONSE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.getResponse = new GetResponseMessage();
+            this.getResponse.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 8: // PUT_REQUEST
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.putRequest = new PutRequestMessage();
+            this.putRequest.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 9: // PUT_RESPONSE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.putResponse = new PutResponseMessage();
+            this.putResponse.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 10: // DELETE_REQUEST
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.deleteRequest = new DeleteRequestMessage();
+            this.deleteRequest.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 11: // DELETE_RESPONSE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.deleteResponse = new DeleteResponseMessage();
+            this.deleteResponse.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 12: // SYNC_VALUE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.syncValue = new SyncValueMessage();
+            this.syncValue.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 13: // SYNC_VALUE_RESPONSE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.syncValueResponse = new SyncValueResponseMessage();
+            this.syncValueResponse.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 14: // SYNC_OFFER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.syncOffer = new SyncOfferMessage();
+            this.syncOffer.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 15: // SYNC_REQUEST
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.syncRequest = new SyncRequestMessage();
+            this.syncRequest.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 16: // FULL_SYNC_REQUEST
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.fullSyncRequest = new FullSyncRequestMessage();
+            this.fullSyncRequest.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 17: // CURSOR_REQUEST
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.cursorRequest = new CursorRequestMessage();
+            this.cursorRequest.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 18: // CURSOR_RESPONSE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.cursorResponse = new CursorResponseMessage();
+            this.cursorResponse.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 19: // REGISTER_REQUEST
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.registerRequest = new RegisterRequestMessage();
+            this.registerRequest.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 20: // REGISTER_RESPONSE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.registerResponse = new RegisterResponseMessage();
+            this.registerResponse.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.type != null) {
+      oprot.writeFieldBegin(TYPE_FIELD_DESC);
+      oprot.writeI32(this.type.getValue());
+      oprot.writeFieldEnd();
+    }
+    if (this.hello != null) {
+      if (isSetHello()) {
+        oprot.writeFieldBegin(HELLO_FIELD_DESC);
+        this.hello.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.error != null) {
+      if (isSetError()) {
+        oprot.writeFieldBegin(ERROR_FIELD_DESC);
+        this.error.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.echoRequest != null) {
+      if (isSetEchoRequest()) {
+        oprot.writeFieldBegin(ECHO_REQUEST_FIELD_DESC);
+        this.echoRequest.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.echoReply != null) {
+      if (isSetEchoReply()) {
+        oprot.writeFieldBegin(ECHO_REPLY_FIELD_DESC);
+        this.echoReply.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.getRequest != null) {
+      if (isSetGetRequest()) {
+        oprot.writeFieldBegin(GET_REQUEST_FIELD_DESC);
+        this.getRequest.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.getResponse != null) {
+      if (isSetGetResponse()) {
+        oprot.writeFieldBegin(GET_RESPONSE_FIELD_DESC);
+        this.getResponse.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.putRequest != null) {
+      if (isSetPutRequest()) {
+        oprot.writeFieldBegin(PUT_REQUEST_FIELD_DESC);
+        this.putRequest.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.putResponse != null) {
+      if (isSetPutResponse()) {
+        oprot.writeFieldBegin(PUT_RESPONSE_FIELD_DESC);
+        this.putResponse.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.deleteRequest != null) {
+      if (isSetDeleteRequest()) {
+        oprot.writeFieldBegin(DELETE_REQUEST_FIELD_DESC);
+        this.deleteRequest.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.deleteResponse != null) {
+      if (isSetDeleteResponse()) {
+        oprot.writeFieldBegin(DELETE_RESPONSE_FIELD_DESC);
+        this.deleteResponse.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.syncValue != null) {
+      if (isSetSyncValue()) {
+        oprot.writeFieldBegin(SYNC_VALUE_FIELD_DESC);
+        this.syncValue.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.syncValueResponse != null) {
+      if (isSetSyncValueResponse()) {
+        oprot.writeFieldBegin(SYNC_VALUE_RESPONSE_FIELD_DESC);
+        this.syncValueResponse.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.syncOffer != null) {
+      if (isSetSyncOffer()) {
+        oprot.writeFieldBegin(SYNC_OFFER_FIELD_DESC);
+        this.syncOffer.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.syncRequest != null) {
+      if (isSetSyncRequest()) {
+        oprot.writeFieldBegin(SYNC_REQUEST_FIELD_DESC);
+        this.syncRequest.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.fullSyncRequest != null) {
+      if (isSetFullSyncRequest()) {
+        oprot.writeFieldBegin(FULL_SYNC_REQUEST_FIELD_DESC);
+        this.fullSyncRequest.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.cursorRequest != null) {
+      if (isSetCursorRequest()) {
+        oprot.writeFieldBegin(CURSOR_REQUEST_FIELD_DESC);
+        this.cursorRequest.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.cursorResponse != null) {
+      if (isSetCursorResponse()) {
+        oprot.writeFieldBegin(CURSOR_RESPONSE_FIELD_DESC);
+        this.cursorResponse.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.registerRequest != null) {
+      if (isSetRegisterRequest()) {
+        oprot.writeFieldBegin(REGISTER_REQUEST_FIELD_DESC);
+        this.registerRequest.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.registerResponse != null) {
+      if (isSetRegisterResponse()) {
+        oprot.writeFieldBegin(REGISTER_RESPONSE_FIELD_DESC);
+        this.registerResponse.write(oprot);
+        oprot.writeFieldEnd();
+      }
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("SyncMessage(");
+    boolean first = true;
+
+    sb.append("type:");
+    if (this.type == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.type);
+    }
+    first = false;
+    if (isSetHello()) {
+      if (!first) sb.append(", ");
+      sb.append("hello:");
+      if (this.hello == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.hello);
+      }
+      first = false;
+    }
+    if (isSetError()) {
+      if (!first) sb.append(", ");
+      sb.append("error:");
+      if (this.error == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.error);
+      }
+      first = false;
+    }
+    if (isSetEchoRequest()) {
+      if (!first) sb.append(", ");
+      sb.append("echoRequest:");
+      if (this.echoRequest == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.echoRequest);
+      }
+      first = false;
+    }
+    if (isSetEchoReply()) {
+      if (!first) sb.append(", ");
+      sb.append("echoReply:");
+      if (this.echoReply == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.echoReply);
+      }
+      first = false;
+    }
+    if (isSetGetRequest()) {
+      if (!first) sb.append(", ");
+      sb.append("getRequest:");
+      if (this.getRequest == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.getRequest);
+      }
+      first = false;
+    }
+    if (isSetGetResponse()) {
+      if (!first) sb.append(", ");
+      sb.append("getResponse:");
+      if (this.getResponse == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.getResponse);
+      }
+      first = false;
+    }
+    if (isSetPutRequest()) {
+      if (!first) sb.append(", ");
+      sb.append("putRequest:");
+      if (this.putRequest == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.putRequest);
+      }
+      first = false;
+    }
+    if (isSetPutResponse()) {
+      if (!first) sb.append(", ");
+      sb.append("putResponse:");
+      if (this.putResponse == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.putResponse);
+      }
+      first = false;
+    }
+    if (isSetDeleteRequest()) {
+      if (!first) sb.append(", ");
+      sb.append("deleteRequest:");
+      if (this.deleteRequest == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.deleteRequest);
+      }
+      first = false;
+    }
+    if (isSetDeleteResponse()) {
+      if (!first) sb.append(", ");
+      sb.append("deleteResponse:");
+      if (this.deleteResponse == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.deleteResponse);
+      }
+      first = false;
+    }
+    if (isSetSyncValue()) {
+      if (!first) sb.append(", ");
+      sb.append("syncValue:");
+      if (this.syncValue == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.syncValue);
+      }
+      first = false;
+    }
+    if (isSetSyncValueResponse()) {
+      if (!first) sb.append(", ");
+      sb.append("syncValueResponse:");
+      if (this.syncValueResponse == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.syncValueResponse);
+      }
+      first = false;
+    }
+    if (isSetSyncOffer()) {
+      if (!first) sb.append(", ");
+      sb.append("syncOffer:");
+      if (this.syncOffer == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.syncOffer);
+      }
+      first = false;
+    }
+    if (isSetSyncRequest()) {
+      if (!first) sb.append(", ");
+      sb.append("syncRequest:");
+      if (this.syncRequest == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.syncRequest);
+      }
+      first = false;
+    }
+    if (isSetFullSyncRequest()) {
+      if (!first) sb.append(", ");
+      sb.append("fullSyncRequest:");
+      if (this.fullSyncRequest == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.fullSyncRequest);
+      }
+      first = false;
+    }
+    if (isSetCursorRequest()) {
+      if (!first) sb.append(", ");
+      sb.append("cursorRequest:");
+      if (this.cursorRequest == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.cursorRequest);
+      }
+      first = false;
+    }
+    if (isSetCursorResponse()) {
+      if (!first) sb.append(", ");
+      sb.append("cursorResponse:");
+      if (this.cursorResponse == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.cursorResponse);
+      }
+      first = false;
+    }
+    if (isSetRegisterRequest()) {
+      if (!first) sb.append(", ");
+      sb.append("registerRequest:");
+      if (this.registerRequest == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.registerRequest);
+      }
+      first = false;
+    }
+    if (isSetRegisterResponse()) {
+      if (!first) sb.append(", ");
+      sb.append("registerResponse:");
+      if (this.registerResponse == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.registerResponse);
+      }
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (type == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'type' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/SyncOffer.java b/lib/gen-java/org/sdnplatform/sync/thrift/SyncOffer.java
new file mode 100644
index 0000000000000000000000000000000000000000..504200e457fe102b54689753ba75d559db9719a1
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/SyncOffer.java
@@ -0,0 +1,323 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class SyncOffer implements org.apache.thrift.TBase<SyncOffer, SyncOffer._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("SyncOffer");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+
+  public AsyncMessageHeader header; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(SyncOffer.class, metaDataMap);
+  }
+
+  public SyncOffer() {
+  }
+
+  public SyncOffer(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public SyncOffer(SyncOffer other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+  }
+
+  public SyncOffer deepCopy() {
+    return new SyncOffer(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public SyncOffer setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof SyncOffer)
+      return this.equals((SyncOffer)that);
+    return false;
+  }
+
+  public boolean equals(SyncOffer that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(SyncOffer other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    SyncOffer typedOther = (SyncOffer)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("SyncOffer(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/SyncOfferMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/SyncOfferMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..9685230fb8d13a0816f929b62fbf1da3ce81a7d2
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/SyncOfferMessage.java
@@ -0,0 +1,543 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class SyncOfferMessage implements org.apache.thrift.TBase<SyncOfferMessage, SyncOfferMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("SyncOfferMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField STORE_FIELD_DESC = new org.apache.thrift.protocol.TField("store", org.apache.thrift.protocol.TType.STRUCT, (short)2);
+  private static final org.apache.thrift.protocol.TField VERSIONS_FIELD_DESC = new org.apache.thrift.protocol.TField("versions", org.apache.thrift.protocol.TType.LIST, (short)3);
+
+  public AsyncMessageHeader header; // required
+  public Store store; // required
+  public List<KeyedVersions> versions; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    STORE((short)2, "store"),
+    VERSIONS((short)3, "versions");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // STORE
+          return STORE;
+        case 3: // VERSIONS
+          return VERSIONS;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.STORE, new org.apache.thrift.meta_data.FieldMetaData("store", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Store.class)));
+    tmpMap.put(_Fields.VERSIONS, new org.apache.thrift.meta_data.FieldMetaData("versions", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, KeyedVersions.class))));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(SyncOfferMessage.class, metaDataMap);
+  }
+
+  public SyncOfferMessage() {
+  }
+
+  public SyncOfferMessage(
+    AsyncMessageHeader header,
+    Store store,
+    List<KeyedVersions> versions)
+  {
+    this();
+    this.header = header;
+    this.store = store;
+    this.versions = versions;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public SyncOfferMessage(SyncOfferMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetStore()) {
+      this.store = new Store(other.store);
+    }
+    if (other.isSetVersions()) {
+      List<KeyedVersions> __this__versions = new ArrayList<KeyedVersions>();
+      for (KeyedVersions other_element : other.versions) {
+        __this__versions.add(new KeyedVersions(other_element));
+      }
+      this.versions = __this__versions;
+    }
+  }
+
+  public SyncOfferMessage deepCopy() {
+    return new SyncOfferMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.store = null;
+    this.versions = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public SyncOfferMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public Store getStore() {
+    return this.store;
+  }
+
+  public SyncOfferMessage setStore(Store store) {
+    this.store = store;
+    return this;
+  }
+
+  public void unsetStore() {
+    this.store = null;
+  }
+
+  /** Returns true if field store is set (has been assigned a value) and false otherwise */
+  public boolean isSetStore() {
+    return this.store != null;
+  }
+
+  public void setStoreIsSet(boolean value) {
+    if (!value) {
+      this.store = null;
+    }
+  }
+
+  public int getVersionsSize() {
+    return (this.versions == null) ? 0 : this.versions.size();
+  }
+
+  public java.util.Iterator<KeyedVersions> getVersionsIterator() {
+    return (this.versions == null) ? null : this.versions.iterator();
+  }
+
+  public void addToVersions(KeyedVersions elem) {
+    if (this.versions == null) {
+      this.versions = new ArrayList<KeyedVersions>();
+    }
+    this.versions.add(elem);
+  }
+
+  public List<KeyedVersions> getVersions() {
+    return this.versions;
+  }
+
+  public SyncOfferMessage setVersions(List<KeyedVersions> versions) {
+    this.versions = versions;
+    return this;
+  }
+
+  public void unsetVersions() {
+    this.versions = null;
+  }
+
+  /** Returns true if field versions is set (has been assigned a value) and false otherwise */
+  public boolean isSetVersions() {
+    return this.versions != null;
+  }
+
+  public void setVersionsIsSet(boolean value) {
+    if (!value) {
+      this.versions = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case STORE:
+      if (value == null) {
+        unsetStore();
+      } else {
+        setStore((Store)value);
+      }
+      break;
+
+    case VERSIONS:
+      if (value == null) {
+        unsetVersions();
+      } else {
+        setVersions((List<KeyedVersions>)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case STORE:
+      return getStore();
+
+    case VERSIONS:
+      return getVersions();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case STORE:
+      return isSetStore();
+    case VERSIONS:
+      return isSetVersions();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof SyncOfferMessage)
+      return this.equals((SyncOfferMessage)that);
+    return false;
+  }
+
+  public boolean equals(SyncOfferMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_store = true && this.isSetStore();
+    boolean that_present_store = true && that.isSetStore();
+    if (this_present_store || that_present_store) {
+      if (!(this_present_store && that_present_store))
+        return false;
+      if (!this.store.equals(that.store))
+        return false;
+    }
+
+    boolean this_present_versions = true && this.isSetVersions();
+    boolean that_present_versions = true && that.isSetVersions();
+    if (this_present_versions || that_present_versions) {
+      if (!(this_present_versions && that_present_versions))
+        return false;
+      if (!this.versions.equals(that.versions))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(SyncOfferMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    SyncOfferMessage typedOther = (SyncOfferMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetStore()).compareTo(typedOther.isSetStore());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStore()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.store, typedOther.store);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetVersions()).compareTo(typedOther.isSetVersions());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetVersions()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.versions, typedOther.versions);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // STORE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.store = new Store();
+            this.store.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // VERSIONS
+          if (field.type == org.apache.thrift.protocol.TType.LIST) {
+            {
+              org.apache.thrift.protocol.TList _list20 = iprot.readListBegin();
+              this.versions = new ArrayList<KeyedVersions>(_list20.size);
+              for (int _i21 = 0; _i21 < _list20.size; ++_i21)
+              {
+                KeyedVersions _elem22; // required
+                _elem22 = new KeyedVersions();
+                _elem22.read(iprot);
+                this.versions.add(_elem22);
+              }
+              iprot.readListEnd();
+            }
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.store != null) {
+      oprot.writeFieldBegin(STORE_FIELD_DESC);
+      this.store.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.versions != null) {
+      oprot.writeFieldBegin(VERSIONS_FIELD_DESC);
+      {
+        oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, this.versions.size()));
+        for (KeyedVersions _iter23 : this.versions)
+        {
+          _iter23.write(oprot);
+        }
+        oprot.writeListEnd();
+      }
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("SyncOfferMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("store:");
+    if (this.store == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.store);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("versions:");
+    if (this.versions == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.versions);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+    if (store == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'store' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/SyncRequestMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/SyncRequestMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc869a030d248a1a70ebe9ed3e721b4b4f58e678
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/SyncRequestMessage.java
@@ -0,0 +1,546 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class SyncRequestMessage implements org.apache.thrift.TBase<SyncRequestMessage, SyncRequestMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("SyncRequestMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField STORE_FIELD_DESC = new org.apache.thrift.protocol.TField("store", org.apache.thrift.protocol.TType.STRUCT, (short)2);
+  private static final org.apache.thrift.protocol.TField KEYS_FIELD_DESC = new org.apache.thrift.protocol.TField("keys", org.apache.thrift.protocol.TType.LIST, (short)3);
+
+  public AsyncMessageHeader header; // required
+  public Store store; // required
+  public List<ByteBuffer> keys; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    STORE((short)2, "store"),
+    KEYS((short)3, "keys");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // STORE
+          return STORE;
+        case 3: // KEYS
+          return KEYS;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.STORE, new org.apache.thrift.meta_data.FieldMetaData("store", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Store.class)));
+    tmpMap.put(_Fields.KEYS, new org.apache.thrift.meta_data.FieldMetaData("keys", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING            , true))));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(SyncRequestMessage.class, metaDataMap);
+  }
+
+  public SyncRequestMessage() {
+  }
+
+  public SyncRequestMessage(
+    AsyncMessageHeader header,
+    Store store)
+  {
+    this();
+    this.header = header;
+    this.store = store;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public SyncRequestMessage(SyncRequestMessage other) {
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetStore()) {
+      this.store = new Store(other.store);
+    }
+    if (other.isSetKeys()) {
+      List<ByteBuffer> __this__keys = new ArrayList<ByteBuffer>();
+      for (ByteBuffer other_element : other.keys) {
+        ByteBuffer temp_binary_element = org.apache.thrift.TBaseHelper.copyBinary(other_element);
+;
+        __this__keys.add(temp_binary_element);
+      }
+      this.keys = __this__keys;
+    }
+  }
+
+  public SyncRequestMessage deepCopy() {
+    return new SyncRequestMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.store = null;
+    this.keys = null;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public SyncRequestMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public Store getStore() {
+    return this.store;
+  }
+
+  public SyncRequestMessage setStore(Store store) {
+    this.store = store;
+    return this;
+  }
+
+  public void unsetStore() {
+    this.store = null;
+  }
+
+  /** Returns true if field store is set (has been assigned a value) and false otherwise */
+  public boolean isSetStore() {
+    return this.store != null;
+  }
+
+  public void setStoreIsSet(boolean value) {
+    if (!value) {
+      this.store = null;
+    }
+  }
+
+  public int getKeysSize() {
+    return (this.keys == null) ? 0 : this.keys.size();
+  }
+
+  public java.util.Iterator<ByteBuffer> getKeysIterator() {
+    return (this.keys == null) ? null : this.keys.iterator();
+  }
+
+  public void addToKeys(ByteBuffer elem) {
+    if (this.keys == null) {
+      this.keys = new ArrayList<ByteBuffer>();
+    }
+    this.keys.add(elem);
+  }
+
+  public List<ByteBuffer> getKeys() {
+    return this.keys;
+  }
+
+  public SyncRequestMessage setKeys(List<ByteBuffer> keys) {
+    this.keys = keys;
+    return this;
+  }
+
+  public void unsetKeys() {
+    this.keys = null;
+  }
+
+  /** Returns true if field keys is set (has been assigned a value) and false otherwise */
+  public boolean isSetKeys() {
+    return this.keys != null;
+  }
+
+  public void setKeysIsSet(boolean value) {
+    if (!value) {
+      this.keys = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case STORE:
+      if (value == null) {
+        unsetStore();
+      } else {
+        setStore((Store)value);
+      }
+      break;
+
+    case KEYS:
+      if (value == null) {
+        unsetKeys();
+      } else {
+        setKeys((List<ByteBuffer>)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case STORE:
+      return getStore();
+
+    case KEYS:
+      return getKeys();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case STORE:
+      return isSetStore();
+    case KEYS:
+      return isSetKeys();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof SyncRequestMessage)
+      return this.equals((SyncRequestMessage)that);
+    return false;
+  }
+
+  public boolean equals(SyncRequestMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_store = true && this.isSetStore();
+    boolean that_present_store = true && that.isSetStore();
+    if (this_present_store || that_present_store) {
+      if (!(this_present_store && that_present_store))
+        return false;
+      if (!this.store.equals(that.store))
+        return false;
+    }
+
+    boolean this_present_keys = true && this.isSetKeys();
+    boolean that_present_keys = true && that.isSetKeys();
+    if (this_present_keys || that_present_keys) {
+      if (!(this_present_keys && that_present_keys))
+        return false;
+      if (!this.keys.equals(that.keys))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(SyncRequestMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    SyncRequestMessage typedOther = (SyncRequestMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetStore()).compareTo(typedOther.isSetStore());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStore()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.store, typedOther.store);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetKeys()).compareTo(typedOther.isSetKeys());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetKeys()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.keys, typedOther.keys);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // STORE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.store = new Store();
+            this.store.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // KEYS
+          if (field.type == org.apache.thrift.protocol.TType.LIST) {
+            {
+              org.apache.thrift.protocol.TList _list24 = iprot.readListBegin();
+              this.keys = new ArrayList<ByteBuffer>(_list24.size);
+              for (int _i25 = 0; _i25 < _list24.size; ++_i25)
+              {
+                ByteBuffer _elem26; // required
+                _elem26 = iprot.readBinary();
+                this.keys.add(_elem26);
+              }
+              iprot.readListEnd();
+            }
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.store != null) {
+      oprot.writeFieldBegin(STORE_FIELD_DESC);
+      this.store.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.keys != null) {
+      if (isSetKeys()) {
+        oprot.writeFieldBegin(KEYS_FIELD_DESC);
+        {
+          oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING, this.keys.size()));
+          for (ByteBuffer _iter27 : this.keys)
+          {
+            oprot.writeBinary(_iter27);
+          }
+          oprot.writeListEnd();
+        }
+        oprot.writeFieldEnd();
+      }
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("SyncRequestMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("store:");
+    if (this.store == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.store);
+    }
+    first = false;
+    if (isSetKeys()) {
+      if (!first) sb.append(", ");
+      sb.append("keys:");
+      if (this.keys == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.keys);
+      }
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+    if (store == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'store' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/SyncValueMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/SyncValueMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..b44935c9384c6529186a3fc38b05b5bcd0787ecc
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/SyncValueMessage.java
@@ -0,0 +1,633 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class SyncValueMessage implements org.apache.thrift.TBase<SyncValueMessage, SyncValueMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("SyncValueMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField STORE_FIELD_DESC = new org.apache.thrift.protocol.TField("store", org.apache.thrift.protocol.TType.STRUCT, (short)2);
+  private static final org.apache.thrift.protocol.TField VALUES_FIELD_DESC = new org.apache.thrift.protocol.TField("values", org.apache.thrift.protocol.TType.LIST, (short)3);
+  private static final org.apache.thrift.protocol.TField RESPONSE_TO_FIELD_DESC = new org.apache.thrift.protocol.TField("responseTo", org.apache.thrift.protocol.TType.I32, (short)4);
+
+  public AsyncMessageHeader header; // required
+  public Store store; // required
+  public List<KeyedValues> values; // required
+  public int responseTo; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    STORE((short)2, "store"),
+    VALUES((short)3, "values"),
+    RESPONSE_TO((short)4, "responseTo");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // STORE
+          return STORE;
+        case 3: // VALUES
+          return VALUES;
+        case 4: // RESPONSE_TO
+          return RESPONSE_TO;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __RESPONSETO_ISSET_ID = 0;
+  private BitSet __isset_bit_vector = new BitSet(1);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.STORE, new org.apache.thrift.meta_data.FieldMetaData("store", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, Store.class)));
+    tmpMap.put(_Fields.VALUES, new org.apache.thrift.meta_data.FieldMetaData("values", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, KeyedValues.class))));
+    tmpMap.put(_Fields.RESPONSE_TO, new org.apache.thrift.meta_data.FieldMetaData("responseTo", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(SyncValueMessage.class, metaDataMap);
+  }
+
+  public SyncValueMessage() {
+  }
+
+  public SyncValueMessage(
+    AsyncMessageHeader header,
+    Store store,
+    List<KeyedValues> values)
+  {
+    this();
+    this.header = header;
+    this.store = store;
+    this.values = values;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public SyncValueMessage(SyncValueMessage other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    if (other.isSetStore()) {
+      this.store = new Store(other.store);
+    }
+    if (other.isSetValues()) {
+      List<KeyedValues> __this__values = new ArrayList<KeyedValues>();
+      for (KeyedValues other_element : other.values) {
+        __this__values.add(new KeyedValues(other_element));
+      }
+      this.values = __this__values;
+    }
+    this.responseTo = other.responseTo;
+  }
+
+  public SyncValueMessage deepCopy() {
+    return new SyncValueMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    this.store = null;
+    this.values = null;
+    setResponseToIsSet(false);
+    this.responseTo = 0;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public SyncValueMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public Store getStore() {
+    return this.store;
+  }
+
+  public SyncValueMessage setStore(Store store) {
+    this.store = store;
+    return this;
+  }
+
+  public void unsetStore() {
+    this.store = null;
+  }
+
+  /** Returns true if field store is set (has been assigned a value) and false otherwise */
+  public boolean isSetStore() {
+    return this.store != null;
+  }
+
+  public void setStoreIsSet(boolean value) {
+    if (!value) {
+      this.store = null;
+    }
+  }
+
+  public int getValuesSize() {
+    return (this.values == null) ? 0 : this.values.size();
+  }
+
+  public java.util.Iterator<KeyedValues> getValuesIterator() {
+    return (this.values == null) ? null : this.values.iterator();
+  }
+
+  public void addToValues(KeyedValues elem) {
+    if (this.values == null) {
+      this.values = new ArrayList<KeyedValues>();
+    }
+    this.values.add(elem);
+  }
+
+  public List<KeyedValues> getValues() {
+    return this.values;
+  }
+
+  public SyncValueMessage setValues(List<KeyedValues> values) {
+    this.values = values;
+    return this;
+  }
+
+  public void unsetValues() {
+    this.values = null;
+  }
+
+  /** Returns true if field values is set (has been assigned a value) and false otherwise */
+  public boolean isSetValues() {
+    return this.values != null;
+  }
+
+  public void setValuesIsSet(boolean value) {
+    if (!value) {
+      this.values = null;
+    }
+  }
+
+  public int getResponseTo() {
+    return this.responseTo;
+  }
+
+  public SyncValueMessage setResponseTo(int responseTo) {
+    this.responseTo = responseTo;
+    setResponseToIsSet(true);
+    return this;
+  }
+
+  public void unsetResponseTo() {
+    __isset_bit_vector.clear(__RESPONSETO_ISSET_ID);
+  }
+
+  /** Returns true if field responseTo is set (has been assigned a value) and false otherwise */
+  public boolean isSetResponseTo() {
+    return __isset_bit_vector.get(__RESPONSETO_ISSET_ID);
+  }
+
+  public void setResponseToIsSet(boolean value) {
+    __isset_bit_vector.set(__RESPONSETO_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case STORE:
+      if (value == null) {
+        unsetStore();
+      } else {
+        setStore((Store)value);
+      }
+      break;
+
+    case VALUES:
+      if (value == null) {
+        unsetValues();
+      } else {
+        setValues((List<KeyedValues>)value);
+      }
+      break;
+
+    case RESPONSE_TO:
+      if (value == null) {
+        unsetResponseTo();
+      } else {
+        setResponseTo((Integer)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case STORE:
+      return getStore();
+
+    case VALUES:
+      return getValues();
+
+    case RESPONSE_TO:
+      return Integer.valueOf(getResponseTo());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case STORE:
+      return isSetStore();
+    case VALUES:
+      return isSetValues();
+    case RESPONSE_TO:
+      return isSetResponseTo();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof SyncValueMessage)
+      return this.equals((SyncValueMessage)that);
+    return false;
+  }
+
+  public boolean equals(SyncValueMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_store = true && this.isSetStore();
+    boolean that_present_store = true && that.isSetStore();
+    if (this_present_store || that_present_store) {
+      if (!(this_present_store && that_present_store))
+        return false;
+      if (!this.store.equals(that.store))
+        return false;
+    }
+
+    boolean this_present_values = true && this.isSetValues();
+    boolean that_present_values = true && that.isSetValues();
+    if (this_present_values || that_present_values) {
+      if (!(this_present_values && that_present_values))
+        return false;
+      if (!this.values.equals(that.values))
+        return false;
+    }
+
+    boolean this_present_responseTo = true && this.isSetResponseTo();
+    boolean that_present_responseTo = true && that.isSetResponseTo();
+    if (this_present_responseTo || that_present_responseTo) {
+      if (!(this_present_responseTo && that_present_responseTo))
+        return false;
+      if (this.responseTo != that.responseTo)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(SyncValueMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    SyncValueMessage typedOther = (SyncValueMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetStore()).compareTo(typedOther.isSetStore());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStore()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.store, typedOther.store);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetValues()).compareTo(typedOther.isSetValues());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetValues()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.values, typedOther.values);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetResponseTo()).compareTo(typedOther.isSetResponseTo());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetResponseTo()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.responseTo, typedOther.responseTo);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // STORE
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.store = new Store();
+            this.store.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 3: // VALUES
+          if (field.type == org.apache.thrift.protocol.TType.LIST) {
+            {
+              org.apache.thrift.protocol.TList _list16 = iprot.readListBegin();
+              this.values = new ArrayList<KeyedValues>(_list16.size);
+              for (int _i17 = 0; _i17 < _list16.size; ++_i17)
+              {
+                KeyedValues _elem18; // required
+                _elem18 = new KeyedValues();
+                _elem18.read(iprot);
+                this.values.add(_elem18);
+              }
+              iprot.readListEnd();
+            }
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 4: // RESPONSE_TO
+          if (field.type == org.apache.thrift.protocol.TType.I32) {
+            this.responseTo = iprot.readI32();
+            setResponseToIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.store != null) {
+      oprot.writeFieldBegin(STORE_FIELD_DESC);
+      this.store.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (this.values != null) {
+      oprot.writeFieldBegin(VALUES_FIELD_DESC);
+      {
+        oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, this.values.size()));
+        for (KeyedValues _iter19 : this.values)
+        {
+          _iter19.write(oprot);
+        }
+        oprot.writeListEnd();
+      }
+      oprot.writeFieldEnd();
+    }
+    if (isSetResponseTo()) {
+      oprot.writeFieldBegin(RESPONSE_TO_FIELD_DESC);
+      oprot.writeI32(this.responseTo);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("SyncValueMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("store:");
+    if (this.store == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.store);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("values:");
+    if (this.values == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.values);
+    }
+    first = false;
+    if (isSetResponseTo()) {
+      if (!first) sb.append(", ");
+      sb.append("responseTo:");
+      sb.append(this.responseTo);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+    if (store == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'store' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/SyncValueResponseMessage.java b/lib/gen-java/org/sdnplatform/sync/thrift/SyncValueResponseMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..20754c3ce22593867a668785f9921977451f908a
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/SyncValueResponseMessage.java
@@ -0,0 +1,413 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class SyncValueResponseMessage implements org.apache.thrift.TBase<SyncValueResponseMessage, SyncValueResponseMessage._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("SyncValueResponseMessage");
+
+  private static final org.apache.thrift.protocol.TField HEADER_FIELD_DESC = new org.apache.thrift.protocol.TField("header", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+  private static final org.apache.thrift.protocol.TField COUNT_FIELD_DESC = new org.apache.thrift.protocol.TField("count", org.apache.thrift.protocol.TType.I32, (short)2);
+
+  public AsyncMessageHeader header; // required
+  public int count; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    HEADER((short)1, "header"),
+    COUNT((short)2, "count");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // HEADER
+          return HEADER;
+        case 2: // COUNT
+          return COUNT;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __COUNT_ISSET_ID = 0;
+  private BitSet __isset_bit_vector = new BitSet(1);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.HEADER, new org.apache.thrift.meta_data.FieldMetaData("header", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, AsyncMessageHeader.class)));
+    tmpMap.put(_Fields.COUNT, new org.apache.thrift.meta_data.FieldMetaData("count", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(SyncValueResponseMessage.class, metaDataMap);
+  }
+
+  public SyncValueResponseMessage() {
+  }
+
+  public SyncValueResponseMessage(
+    AsyncMessageHeader header)
+  {
+    this();
+    this.header = header;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public SyncValueResponseMessage(SyncValueResponseMessage other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    if (other.isSetHeader()) {
+      this.header = new AsyncMessageHeader(other.header);
+    }
+    this.count = other.count;
+  }
+
+  public SyncValueResponseMessage deepCopy() {
+    return new SyncValueResponseMessage(this);
+  }
+
+  @Override
+  public void clear() {
+    this.header = null;
+    setCountIsSet(false);
+    this.count = 0;
+  }
+
+  public AsyncMessageHeader getHeader() {
+    return this.header;
+  }
+
+  public SyncValueResponseMessage setHeader(AsyncMessageHeader header) {
+    this.header = header;
+    return this;
+  }
+
+  public void unsetHeader() {
+    this.header = null;
+  }
+
+  /** Returns true if field header is set (has been assigned a value) and false otherwise */
+  public boolean isSetHeader() {
+    return this.header != null;
+  }
+
+  public void setHeaderIsSet(boolean value) {
+    if (!value) {
+      this.header = null;
+    }
+  }
+
+  public int getCount() {
+    return this.count;
+  }
+
+  public SyncValueResponseMessage setCount(int count) {
+    this.count = count;
+    setCountIsSet(true);
+    return this;
+  }
+
+  public void unsetCount() {
+    __isset_bit_vector.clear(__COUNT_ISSET_ID);
+  }
+
+  /** Returns true if field count is set (has been assigned a value) and false otherwise */
+  public boolean isSetCount() {
+    return __isset_bit_vector.get(__COUNT_ISSET_ID);
+  }
+
+  public void setCountIsSet(boolean value) {
+    __isset_bit_vector.set(__COUNT_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case HEADER:
+      if (value == null) {
+        unsetHeader();
+      } else {
+        setHeader((AsyncMessageHeader)value);
+      }
+      break;
+
+    case COUNT:
+      if (value == null) {
+        unsetCount();
+      } else {
+        setCount((Integer)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case HEADER:
+      return getHeader();
+
+    case COUNT:
+      return Integer.valueOf(getCount());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case HEADER:
+      return isSetHeader();
+    case COUNT:
+      return isSetCount();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof SyncValueResponseMessage)
+      return this.equals((SyncValueResponseMessage)that);
+    return false;
+  }
+
+  public boolean equals(SyncValueResponseMessage that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_header = true && this.isSetHeader();
+    boolean that_present_header = true && that.isSetHeader();
+    if (this_present_header || that_present_header) {
+      if (!(this_present_header && that_present_header))
+        return false;
+      if (!this.header.equals(that.header))
+        return false;
+    }
+
+    boolean this_present_count = true && this.isSetCount();
+    boolean that_present_count = true && that.isSetCount();
+    if (this_present_count || that_present_count) {
+      if (!(this_present_count && that_present_count))
+        return false;
+      if (this.count != that.count)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(SyncValueResponseMessage other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    SyncValueResponseMessage typedOther = (SyncValueResponseMessage)other;
+
+    lastComparison = Boolean.valueOf(isSetHeader()).compareTo(typedOther.isSetHeader());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetHeader()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.header, typedOther.header);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetCount()).compareTo(typedOther.isSetCount());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetCount()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.count, typedOther.count);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // HEADER
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.header = new AsyncMessageHeader();
+            this.header.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // COUNT
+          if (field.type == org.apache.thrift.protocol.TType.I32) {
+            this.count = iprot.readI32();
+            setCountIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.header != null) {
+      oprot.writeFieldBegin(HEADER_FIELD_DESC);
+      this.header.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    if (isSetCount()) {
+      oprot.writeFieldBegin(COUNT_FIELD_DESC);
+      oprot.writeI32(this.count);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("SyncValueResponseMessage(");
+    boolean first = true;
+
+    sb.append("header:");
+    if (this.header == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.header);
+    }
+    first = false;
+    if (isSetCount()) {
+      if (!first) sb.append(", ");
+      sb.append("count:");
+      sb.append(this.count);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (header == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'header' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/VectorClock.java b/lib/gen-java/org/sdnplatform/sync/thrift/VectorClock.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c26bc81088ec600c034a0bb84a2f6f37df0ebbf
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/VectorClock.java
@@ -0,0 +1,444 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class VectorClock implements org.apache.thrift.TBase<VectorClock, VectorClock._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("VectorClock");
+
+  private static final org.apache.thrift.protocol.TField VERSIONS_FIELD_DESC = new org.apache.thrift.protocol.TField("versions", org.apache.thrift.protocol.TType.LIST, (short)1);
+  private static final org.apache.thrift.protocol.TField TIMESTAMP_FIELD_DESC = new org.apache.thrift.protocol.TField("timestamp", org.apache.thrift.protocol.TType.I64, (short)2);
+
+  public List<ClockEntry> versions; // required
+  public long timestamp; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    VERSIONS((short)1, "versions"),
+    TIMESTAMP((short)2, "timestamp");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // VERSIONS
+          return VERSIONS;
+        case 2: // TIMESTAMP
+          return TIMESTAMP;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  private static final int __TIMESTAMP_ISSET_ID = 0;
+  private BitSet __isset_bit_vector = new BitSet(1);
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.VERSIONS, new org.apache.thrift.meta_data.FieldMetaData("versions", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
+            new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, ClockEntry.class))));
+    tmpMap.put(_Fields.TIMESTAMP, new org.apache.thrift.meta_data.FieldMetaData("timestamp", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(VectorClock.class, metaDataMap);
+  }
+
+  public VectorClock() {
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public VectorClock(VectorClock other) {
+    __isset_bit_vector.clear();
+    __isset_bit_vector.or(other.__isset_bit_vector);
+    if (other.isSetVersions()) {
+      List<ClockEntry> __this__versions = new ArrayList<ClockEntry>();
+      for (ClockEntry other_element : other.versions) {
+        __this__versions.add(new ClockEntry(other_element));
+      }
+      this.versions = __this__versions;
+    }
+    this.timestamp = other.timestamp;
+  }
+
+  public VectorClock deepCopy() {
+    return new VectorClock(this);
+  }
+
+  @Override
+  public void clear() {
+    this.versions = null;
+    setTimestampIsSet(false);
+    this.timestamp = 0;
+  }
+
+  public int getVersionsSize() {
+    return (this.versions == null) ? 0 : this.versions.size();
+  }
+
+  public java.util.Iterator<ClockEntry> getVersionsIterator() {
+    return (this.versions == null) ? null : this.versions.iterator();
+  }
+
+  public void addToVersions(ClockEntry elem) {
+    if (this.versions == null) {
+      this.versions = new ArrayList<ClockEntry>();
+    }
+    this.versions.add(elem);
+  }
+
+  public List<ClockEntry> getVersions() {
+    return this.versions;
+  }
+
+  public VectorClock setVersions(List<ClockEntry> versions) {
+    this.versions = versions;
+    return this;
+  }
+
+  public void unsetVersions() {
+    this.versions = null;
+  }
+
+  /** Returns true if field versions is set (has been assigned a value) and false otherwise */
+  public boolean isSetVersions() {
+    return this.versions != null;
+  }
+
+  public void setVersionsIsSet(boolean value) {
+    if (!value) {
+      this.versions = null;
+    }
+  }
+
+  public long getTimestamp() {
+    return this.timestamp;
+  }
+
+  public VectorClock setTimestamp(long timestamp) {
+    this.timestamp = timestamp;
+    setTimestampIsSet(true);
+    return this;
+  }
+
+  public void unsetTimestamp() {
+    __isset_bit_vector.clear(__TIMESTAMP_ISSET_ID);
+  }
+
+  /** Returns true if field timestamp is set (has been assigned a value) and false otherwise */
+  public boolean isSetTimestamp() {
+    return __isset_bit_vector.get(__TIMESTAMP_ISSET_ID);
+  }
+
+  public void setTimestampIsSet(boolean value) {
+    __isset_bit_vector.set(__TIMESTAMP_ISSET_ID, value);
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case VERSIONS:
+      if (value == null) {
+        unsetVersions();
+      } else {
+        setVersions((List<ClockEntry>)value);
+      }
+      break;
+
+    case TIMESTAMP:
+      if (value == null) {
+        unsetTimestamp();
+      } else {
+        setTimestamp((Long)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case VERSIONS:
+      return getVersions();
+
+    case TIMESTAMP:
+      return Long.valueOf(getTimestamp());
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case VERSIONS:
+      return isSetVersions();
+    case TIMESTAMP:
+      return isSetTimestamp();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof VectorClock)
+      return this.equals((VectorClock)that);
+    return false;
+  }
+
+  public boolean equals(VectorClock that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_versions = true && this.isSetVersions();
+    boolean that_present_versions = true && that.isSetVersions();
+    if (this_present_versions || that_present_versions) {
+      if (!(this_present_versions && that_present_versions))
+        return false;
+      if (!this.versions.equals(that.versions))
+        return false;
+    }
+
+    boolean this_present_timestamp = true && this.isSetTimestamp();
+    boolean that_present_timestamp = true && that.isSetTimestamp();
+    if (this_present_timestamp || that_present_timestamp) {
+      if (!(this_present_timestamp && that_present_timestamp))
+        return false;
+      if (this.timestamp != that.timestamp)
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(VectorClock other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    VectorClock typedOther = (VectorClock)other;
+
+    lastComparison = Boolean.valueOf(isSetVersions()).compareTo(typedOther.isSetVersions());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetVersions()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.versions, typedOther.versions);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetTimestamp()).compareTo(typedOther.isSetTimestamp());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetTimestamp()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.timestamp, typedOther.timestamp);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // VERSIONS
+          if (field.type == org.apache.thrift.protocol.TType.LIST) {
+            {
+              org.apache.thrift.protocol.TList _list0 = iprot.readListBegin();
+              this.versions = new ArrayList<ClockEntry>(_list0.size);
+              for (int _i1 = 0; _i1 < _list0.size; ++_i1)
+              {
+                ClockEntry _elem2; // required
+                _elem2 = new ClockEntry();
+                _elem2.read(iprot);
+                this.versions.add(_elem2);
+              }
+              iprot.readListEnd();
+            }
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // TIMESTAMP
+          if (field.type == org.apache.thrift.protocol.TType.I64) {
+            this.timestamp = iprot.readI64();
+            setTimestampIsSet(true);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.versions != null) {
+      if (isSetVersions()) {
+        oprot.writeFieldBegin(VERSIONS_FIELD_DESC);
+        {
+          oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRUCT, this.versions.size()));
+          for (ClockEntry _iter3 : this.versions)
+          {
+            _iter3.write(oprot);
+          }
+          oprot.writeListEnd();
+        }
+        oprot.writeFieldEnd();
+      }
+    }
+    if (isSetTimestamp()) {
+      oprot.writeFieldBegin(TIMESTAMP_FIELD_DESC);
+      oprot.writeI64(this.timestamp);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("VectorClock(");
+    boolean first = true;
+
+    if (isSetVersions()) {
+      sb.append("versions:");
+      if (this.versions == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.versions);
+      }
+      first = false;
+    }
+    if (isSetTimestamp()) {
+      if (!first) sb.append(", ");
+      sb.append("timestamp:");
+      sb.append(this.timestamp);
+      first = false;
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+      __isset_bit_vector = new BitSet(1);
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/gen-java/org/sdnplatform/sync/thrift/VersionedValue.java b/lib/gen-java/org/sdnplatform/sync/thrift/VersionedValue.java
new file mode 100644
index 0000000000000000000000000000000000000000..9ff855f4181815d02c14fc514eec3b5b125fa8c5
--- /dev/null
+++ b/lib/gen-java/org/sdnplatform/sync/thrift/VersionedValue.java
@@ -0,0 +1,425 @@
+/**
+ * Autogenerated by Thrift Compiler (0.7.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ */
+package org.sdnplatform.sync.thrift;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.EnumMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.EnumSet;
+import java.util.Collections;
+import java.util.BitSet;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("all") public class VersionedValue implements org.apache.thrift.TBase<VersionedValue, VersionedValue._Fields>, java.io.Serializable, Cloneable {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("VersionedValue");
+
+  private static final org.apache.thrift.protocol.TField VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("value", org.apache.thrift.protocol.TType.STRING, (short)1);
+  private static final org.apache.thrift.protocol.TField VERSION_FIELD_DESC = new org.apache.thrift.protocol.TField("version", org.apache.thrift.protocol.TType.STRUCT, (short)2);
+
+  public ByteBuffer value; // required
+  public VectorClock version; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    VALUE((short)1, "value"),
+    VERSION((short)2, "version");
+
+    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();
+
+    static {
+      for (_Fields field : EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // VALUE
+          return VALUE;
+        case 2: // VERSION
+          return VERSION;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final String _fieldName;
+
+    _Fields(short thriftId, String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+
+  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.VALUE, new org.apache.thrift.meta_data.FieldMetaData("value", org.apache.thrift.TFieldRequirementType.OPTIONAL, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING        , true)));
+    tmpMap.put(_Fields.VERSION, new org.apache.thrift.meta_data.FieldMetaData("version", org.apache.thrift.TFieldRequirementType.REQUIRED, 
+        new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, VectorClock.class)));
+    metaDataMap = Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(VersionedValue.class, metaDataMap);
+  }
+
+  public VersionedValue() {
+  }
+
+  public VersionedValue(
+    VectorClock version)
+  {
+    this();
+    this.version = version;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public VersionedValue(VersionedValue other) {
+    if (other.isSetValue()) {
+      this.value = org.apache.thrift.TBaseHelper.copyBinary(other.value);
+;
+    }
+    if (other.isSetVersion()) {
+      this.version = new VectorClock(other.version);
+    }
+  }
+
+  public VersionedValue deepCopy() {
+    return new VersionedValue(this);
+  }
+
+  @Override
+  public void clear() {
+    this.value = null;
+    this.version = null;
+  }
+
+  public byte[] getValue() {
+    setValue(org.apache.thrift.TBaseHelper.rightSize(value));
+    return value == null ? null : value.array();
+  }
+
+  public ByteBuffer bufferForValue() {
+    return value;
+  }
+
+  public VersionedValue setValue(byte[] value) {
+    setValue(value == null ? (ByteBuffer)null : ByteBuffer.wrap(value));
+    return this;
+  }
+
+  public VersionedValue setValue(ByteBuffer value) {
+    this.value = value;
+    return this;
+  }
+
+  public void unsetValue() {
+    this.value = null;
+  }
+
+  /** Returns true if field value is set (has been assigned a value) and false otherwise */
+  public boolean isSetValue() {
+    return this.value != null;
+  }
+
+  public void setValueIsSet(boolean value) {
+    if (!value) {
+      this.value = null;
+    }
+  }
+
+  public VectorClock getVersion() {
+    return this.version;
+  }
+
+  public VersionedValue setVersion(VectorClock version) {
+    this.version = version;
+    return this;
+  }
+
+  public void unsetVersion() {
+    this.version = null;
+  }
+
+  /** Returns true if field version is set (has been assigned a value) and false otherwise */
+  public boolean isSetVersion() {
+    return this.version != null;
+  }
+
+  public void setVersionIsSet(boolean value) {
+    if (!value) {
+      this.version = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, Object value) {
+    switch (field) {
+    case VALUE:
+      if (value == null) {
+        unsetValue();
+      } else {
+        setValue((ByteBuffer)value);
+      }
+      break;
+
+    case VERSION:
+      if (value == null) {
+        unsetVersion();
+      } else {
+        setVersion((VectorClock)value);
+      }
+      break;
+
+    }
+  }
+
+  public Object getFieldValue(_Fields field) {
+    switch (field) {
+    case VALUE:
+      return getValue();
+
+    case VERSION:
+      return getVersion();
+
+    }
+    throw new IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new IllegalArgumentException();
+    }
+
+    switch (field) {
+    case VALUE:
+      return isSetValue();
+    case VERSION:
+      return isSetVersion();
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof VersionedValue)
+      return this.equals((VersionedValue)that);
+    return false;
+  }
+
+  public boolean equals(VersionedValue that) {
+    if (that == null)
+      return false;
+
+    boolean this_present_value = true && this.isSetValue();
+    boolean that_present_value = true && that.isSetValue();
+    if (this_present_value || that_present_value) {
+      if (!(this_present_value && that_present_value))
+        return false;
+      if (!this.value.equals(that.value))
+        return false;
+    }
+
+    boolean this_present_version = true && this.isSetVersion();
+    boolean that_present_version = true && that.isSetVersion();
+    if (this_present_version || that_present_version) {
+      if (!(this_present_version && that_present_version))
+        return false;
+      if (!this.version.equals(that.version))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return 0;
+  }
+
+  public int compareTo(VersionedValue other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+    VersionedValue typedOther = (VersionedValue)other;
+
+    lastComparison = Boolean.valueOf(isSetValue()).compareTo(typedOther.isSetValue());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetValue()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.value, typedOther.value);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = Boolean.valueOf(isSetVersion()).compareTo(typedOther.isSetVersion());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetVersion()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.version, typedOther.version);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    org.apache.thrift.protocol.TField field;
+    iprot.readStructBegin();
+    while (true)
+    {
+      field = iprot.readFieldBegin();
+      if (field.type == org.apache.thrift.protocol.TType.STOP) { 
+        break;
+      }
+      switch (field.id) {
+        case 1: // VALUE
+          if (field.type == org.apache.thrift.protocol.TType.STRING) {
+            this.value = iprot.readBinary();
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        case 2: // VERSION
+          if (field.type == org.apache.thrift.protocol.TType.STRUCT) {
+            this.version = new VectorClock();
+            this.version.read(iprot);
+          } else { 
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+          }
+          break;
+        default:
+          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);
+      }
+      iprot.readFieldEnd();
+    }
+    iprot.readStructEnd();
+
+    // check for required fields of primitive type, which can't be checked in the validate method
+    validate();
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    validate();
+
+    oprot.writeStructBegin(STRUCT_DESC);
+    if (this.value != null) {
+      if (isSetValue()) {
+        oprot.writeFieldBegin(VALUE_FIELD_DESC);
+        oprot.writeBinary(this.value);
+        oprot.writeFieldEnd();
+      }
+    }
+    if (this.version != null) {
+      oprot.writeFieldBegin(VERSION_FIELD_DESC);
+      this.version.write(oprot);
+      oprot.writeFieldEnd();
+    }
+    oprot.writeFieldStop();
+    oprot.writeStructEnd();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("VersionedValue(");
+    boolean first = true;
+
+    if (isSetValue()) {
+      sb.append("value:");
+      if (this.value == null) {
+        sb.append("null");
+      } else {
+        org.apache.thrift.TBaseHelper.toString(this.value, sb);
+      }
+      first = false;
+    }
+    if (!first) sb.append(", ");
+    sb.append("version:");
+    if (this.version == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.version);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    if (version == null) {
+      throw new org.apache.thrift.protocol.TProtocolException("Required field 'version' was not present! Struct: " + toString());
+    }
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+}
+
diff --git a/lib/jackson-annotations-2.1.4.jar b/lib/jackson-annotations-2.1.4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..143edf44b0daa4cef1a452ecccac21aee22a8d77
Binary files /dev/null and b/lib/jackson-annotations-2.1.4.jar differ
diff --git a/lib/jackson-core-2.1.4.jar b/lib/jackson-core-2.1.4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..0f144685f7140d2694eeba5609322b4cd79f0bf8
Binary files /dev/null and b/lib/jackson-core-2.1.4.jar differ
diff --git a/lib/jackson-core-asl-1.8.6.jar b/lib/jackson-core-asl-1.8.6.jar
deleted file mode 100644
index fe6c6606fe88b7eecd3568674f2cbf7ffb7eacc2..0000000000000000000000000000000000000000
Binary files a/lib/jackson-core-asl-1.8.6.jar and /dev/null differ
diff --git a/lib/jackson-databind-2.1.4.jar b/lib/jackson-databind-2.1.4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..ce125d1df292a1d8fa4b86cf39770d0d9c4636cc
Binary files /dev/null and b/lib/jackson-databind-2.1.4.jar differ
diff --git a/lib/jackson-dataformat-csv-2.1.4.jar b/lib/jackson-dataformat-csv-2.1.4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..e97503cb161c44ca54d096ec669b707e522f0004
Binary files /dev/null and b/lib/jackson-dataformat-csv-2.1.4.jar differ
diff --git a/lib/jackson-dataformat-smile-2.1.4.jar b/lib/jackson-dataformat-smile-2.1.4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..e907c7726c5679a572e098293581bba28ac63fa1
Binary files /dev/null and b/lib/jackson-dataformat-smile-2.1.4.jar differ
diff --git a/lib/jackson-dataformat-xml-2.1.4.jar b/lib/jackson-dataformat-xml-2.1.4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..15c82e69c86703363e16d47cb8cb3c3de1919c43
Binary files /dev/null and b/lib/jackson-dataformat-xml-2.1.4.jar differ
diff --git a/lib/jackson-dataformat-yaml-2.1.4.jar b/lib/jackson-dataformat-yaml-2.1.4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..444ef88653e9574fb90246679835448e02170ee8
Binary files /dev/null and b/lib/jackson-dataformat-yaml-2.1.4.jar differ
diff --git a/lib/jackson-mapper-asl-1.8.6.jar b/lib/jackson-mapper-asl-1.8.6.jar
deleted file mode 100644
index f3dd1a6ba56faf5dabd6bcfda96eb7f00d4557be..0000000000000000000000000000000000000000
Binary files a/lib/jackson-mapper-asl-1.8.6.jar and /dev/null differ
diff --git a/lib/org.restlet-2.1-RC1.jar b/lib/org.restlet-2.1-RC1.jar
deleted file mode 100644
index 6b931a034dbff19839dd80852c8f55f598aee904..0000000000000000000000000000000000000000
Binary files a/lib/org.restlet-2.1-RC1.jar and /dev/null differ
diff --git a/lib/org.restlet-2.2M3.jar b/lib/org.restlet-2.2M3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..a23f783d8312bd2a9a8728f96c3379454fb7a1ac
Binary files /dev/null and b/lib/org.restlet-2.2M3.jar differ
diff --git a/lib/org.restlet.ext.jackson-2.1-RC1.jar b/lib/org.restlet.ext.jackson-2.1-RC1.jar
deleted file mode 100644
index 5ff6ce7b3b7f23a54095da740e5bb851d72ae2af..0000000000000000000000000000000000000000
Binary files a/lib/org.restlet.ext.jackson-2.1-RC1.jar and /dev/null differ
diff --git a/lib/org.restlet.ext.jackson-2.2M3.jar b/lib/org.restlet.ext.jackson-2.2M3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f4f95781d05a8af3e1e96fb0647e148f7a0a7f6a
Binary files /dev/null and b/lib/org.restlet.ext.jackson-2.2M3.jar differ
diff --git a/lib/org.restlet.ext.simple-2.1-RC1.jar b/lib/org.restlet.ext.simple-2.1-RC1.jar
deleted file mode 100644
index 3cd9f7ffa88f5e3c6384d8b8deaa2e157064f56c..0000000000000000000000000000000000000000
Binary files a/lib/org.restlet.ext.simple-2.1-RC1.jar and /dev/null differ
diff --git a/lib/org.restlet.ext.simple-2.2M3.jar b/lib/org.restlet.ext.simple-2.2M3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..c870c7cf50c075b1daf0be3db1a6fb8fe231d948
Binary files /dev/null and b/lib/org.restlet.ext.simple-2.2M3.jar differ
diff --git a/lib/org.restlet.ext.slf4j-2.1-RC1.jar b/lib/org.restlet.ext.slf4j-2.1-RC1.jar
deleted file mode 100644
index 99346d15b26dda49ead5575f0e47d2a050486e97..0000000000000000000000000000000000000000
Binary files a/lib/org.restlet.ext.slf4j-2.1-RC1.jar and /dev/null differ
diff --git a/lib/org.restlet.ext.slf4j-2.2M3.jar b/lib/org.restlet.ext.slf4j-2.2M3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..83eed674e2fde6deed5e67593ff909ba0001fc29
Binary files /dev/null and b/lib/org.restlet.ext.slf4j-2.2M3.jar differ
diff --git a/lib/simple-4.1.21.jar b/lib/simple-4.1.21.jar
deleted file mode 100644
index b7fb53c756d88910cbd149b416a419391b3d77f4..0000000000000000000000000000000000000000
Binary files a/lib/simple-4.1.21.jar and /dev/null differ
diff --git a/lib/simple-5.1.1.jar b/lib/simple-5.1.1.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b536f4098086a35ccecd50035c728b02b5e11cfb
Binary files /dev/null and b/lib/simple-5.1.1.jar differ
diff --git a/logback.xml b/logback.xml
index b85b463f507fd1b4037f809bc1ed566394a67195..aad2c58da2c1c276b866e1f3cfebc109d034fd07 100644
--- a/logback.xml
+++ b/logback.xml
@@ -11,4 +11,5 @@
   <logger name="LogService" level="WARN"/> <!-- Restlet access logging -->
   <logger name="net.floodlightcontroller" level="INFO"/>
   <logger name="net.floodlightcontroller.logging" level="WARN"/>
+  <logger name="org.sdnplatform" level="INFO"/>
 </configuration>
diff --git a/setup-eclipse.sh b/setup-eclipse.sh
index 4224a3c5c982f4fee5768a0e5bd788ea36f0b765..3be048a516d4bad794267f68f8182d234bc8b5db 100755
--- a/setup-eclipse.sh
+++ b/setup-eclipse.sh
@@ -56,6 +56,22 @@ cat > "$d/Floodlight-Quantum-Conf.launch" << EOF
 </launchConfiguration>
 EOF
 
+cat >"$d/SyncClient.launch" << EOF
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/floodlight/src/main/java/org/sdnplatform/sync/client/SyncClient.java"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="1"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.sdnplatform.sync.client.SyncClient"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="--hostname localhost --port 6642"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="bigfloodlight"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea"/>
+</launchConfiguration>
+EOF
+
 cat >"$d/.classpath" <<EOF
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
diff --git a/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java b/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java
index 8b201b325c55fce36f800697a2739a324b28e9b7..3da2f4e9d2cfd7d0f2e27ad013ed5cafeebdfe3b 100644
--- a/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java
+++ b/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java
@@ -49,10 +49,10 @@ import net.floodlightcontroller.threadpool.IThreadPoolService;
 import net.floodlightcontroller.util.MACAddress;
 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 com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 import org.jboss.netty.channel.Channel;
 import org.openflow.protocol.OFBarrierRequest;
 import org.openflow.protocol.OFFeaturesReply;
diff --git a/src/main/java/net/floodlightcontroller/core/RoleInfo.java b/src/main/java/net/floodlightcontroller/core/RoleInfo.java
index 26c1a510bd556e5cedb5eb0b91f45e45ebe0e456..09ac98b1fb3d4c0b116fa1e6b5a692cf09df22a8 100644
--- a/src/main/java/net/floodlightcontroller/core/RoleInfo.java
+++ b/src/main/java/net/floodlightcontroller/core/RoleInfo.java
@@ -22,7 +22,7 @@ import java.util.TimeZone;
 
 import net.floodlightcontroller.core.IFloodlightProviderService.Role;
 
-import org.codehaus.jackson.annotate.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonProperty;
 
 
 public class RoleInfo {
diff --git a/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/main/java/net/floodlightcontroller/core/internal/Controller.java
index 3b90773ae7b261a1de21d04a8da2f3252fb2a4d2..8999f92ed4af1bfc964384d67d07abf5132b74c3 100644
--- a/src/main/java/net/floodlightcontroller/core/internal/Controller.java
+++ b/src/main/java/net/floodlightcontroller/core/internal/Controller.java
@@ -141,7 +141,7 @@ public class Controller implements IFloodlightProviderService,
 
     protected static Logger log = LoggerFactory.getLogger(Controller.class);
 
-    private static final String ERROR_DATABASE =
+    static final String ERROR_DATABASE =
             "The controller could not communicate with the system database.";
 
     protected BasicFactory factory;
@@ -1817,7 +1817,7 @@ public class Controller implements IFloodlightProviderService,
         this.connectedSwitches = new HashSet<IOFSwitch>();
         this.controllerNodeIPsCache = new HashMap<String, String>();
         this.updates = new LinkedBlockingQueue<IUpdate>();
-        this.factory = new BasicFactory();
+        this.factory = BasicFactory.getInstance();
         this.providerMap = new HashMap<String, List<IInfoProvider>>();
         setConfigParams(configParams);
         this.role = getInitialRole(configParams);
diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java b/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java
index 295e9679db82dd4c65148adb65e4f3ed2c507f7a..25edf396c943dfce728f79610910162ebd608a12 100644
--- a/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java
+++ b/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java
@@ -34,7 +34,7 @@ import org.openflow.protocol.factory.OFMessageFactory;
  */
 public class OFMessageDecoder extends FrameDecoder {
 
-    OFMessageFactory factory = new BasicFactory();
+    OFMessageFactory factory = BasicFactory.getInstance();
     
     @Override
     protected Object decode(ChannelHandlerContext ctx, Channel channel,
diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java
index a3809a1b80e7cfbc6b1e2f86d880ff95e3a37a35..d924ce255353a5e67ccfefa9fa933c3c35d15cab 100644
--- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java
+++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java
@@ -19,7 +19,7 @@ package net.floodlightcontroller.core.internal;
 
 import java.util.List;
 
-import org.codehaus.jackson.annotate.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import org.openflow.protocol.statistics.OFDescriptionStatistics;
 
 import net.floodlightcontroller.core.OFSwitchBase;
diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java
index 66c33f55bfbf5262049c9cf60c2c730fcd69a04a..ce4d3ba3c51480ad3ab3b488c398727efb6d85b4 100644
--- a/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java
+++ b/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java
@@ -19,10 +19,10 @@ 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 import org.openflow.util.HexString;
 
 /**
diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java
index e74cc01c1001d576148c0adc80eacf8e6547451c..6df067ead245a209b12ce20347c237913a8f7b29 100644
--- a/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java
+++ b/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java
@@ -19,10 +19,10 @@ 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 import org.openflow.util.HexString;
 
 /**
diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java
index f4a58778656144be714ad25e4a11e7697e39bd62..db93b66ac46af312c37f59c36febe4357389e188 100644
--- a/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java
+++ b/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java
@@ -21,10 +21,10 @@ 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;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 
 /**
  * Serialize an integer as an IPv4 Address in dotted decimal format
diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java
index a7c9fb7d909438326d767f445d7c4e4c7df641ed..c797681edf2e8779365293188e89f51b9b4c30a1 100644
--- a/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java
+++ b/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java
@@ -19,10 +19,10 @@ 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 import org.openflow.util.HexString;
 
 /**
diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java
index c125c76fc6138924f4ac04ceb918ca7dcbe80d90..c5f1fa9a1ec6d39b0ce35ef777fc6a1d80a80771 100644
--- a/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java
+++ b/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java
@@ -19,10 +19,10 @@ 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 
 /**
  * Serialize a short value as an unsigned short
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java b/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java
index 742616371e09fe8031cbd819ec873bff8d22aa79..d4ee9459ee59ae67b1e2c26069d06e2ee2c0a74f 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java
@@ -19,8 +19,8 @@ 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;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 
 /**
  * A simple switch DPID/port pair
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
index ebdc8b11ff4061a283af39559c11c0f273512e04..e7e103221916291f3eb7420d47efd2c8e8886523 100755
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
@@ -29,7 +29,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.TreeSet;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.openflow.util.HexString;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
index 5949a19e22d40542b83eef1660478a4655f8cce8..db8d8e9fb3ff92256e0e9db55751e9345e6765fa 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
@@ -24,8 +24,8 @@ import net.floodlightcontroller.core.web.serializers.MACSerializer;
 import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
 import net.floodlightcontroller.packet.IPv4;
 
-import org.codehaus.jackson.annotate.JsonIgnore;
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.openflow.util.HexString;
 
 /**
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
index e946d7b8aa7674ad598ba022e75e556ca9780297..26b2027aaa34ece55d1541a3f60bb058d27e74e3 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
@@ -23,10 +23,10 @@ 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 import org.openflow.util.HexString;
 
 /**
diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java b/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java
index 34a7771672c0f6dda39726d13e6022c4edfa3041..2f731db49e1911849745b43982ee9d74edba91d2 100644
--- a/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java
+++ b/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java
@@ -19,10 +19,10 @@ 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 com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
 import org.restlet.resource.Post;
 import org.restlet.resource.Get;
 import org.restlet.resource.ServerResource;
diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java b/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java
index 1b187f310901b521ba7897d25917e36f7359f357..f457ce8d9e85510f91fdefe2bb2f3ac84276eec4 100644
--- a/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java
+++ b/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java
@@ -17,7 +17,7 @@
 
 package net.floodlightcontroller.firewall;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.openflow.protocol.OFMatch;
 
 import net.floodlightcontroller.packet.Ethernet;
diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallRuleSerializer.java b/src/main/java/net/floodlightcontroller/firewall/FirewallRuleSerializer.java
index b8fa6322e87e05618b739efc592339fa9a93b5f7..3180968d1dd1786ef183933bc8584a5b896b123d 100644
--- a/src/main/java/net/floodlightcontroller/firewall/FirewallRuleSerializer.java
+++ b/src/main/java/net/floodlightcontroller/firewall/FirewallRuleSerializer.java
@@ -22,10 +22,10 @@ import java.io.IOException;
 import net.floodlightcontroller.packet.IPv4;
 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;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 import org.openflow.util.HexString;
 
 /**
diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java b/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java
index 66d9dce6248cf42bde73992c83b629e679789abe..b72f983dd3edafe2a92521f7cdd35afd5494e990 100644
--- a/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java
+++ b/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java
@@ -21,10 +21,10 @@ 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 com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
 import org.openflow.util.HexString;
 import org.restlet.resource.Delete;
 import org.restlet.resource.Post;
diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java
index a7d996d022a0cbbd94f3ddfad3def876a2122568..e4e109ca46fea5d8605109aea8f497c6300edcb6 100644
--- a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java
+++ b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java
@@ -16,8 +16,8 @@
 
 package net.floodlightcontroller.linkdiscovery;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
-import org.codehaus.jackson.map.ser.ToStringSerializer;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 import org.openflow.util.HexString;
 
 public interface ILinkDiscovery {
diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java
index cb52ba6dc63b62031d6e4712a28ff3e8b1dbb14c..5cfabd821f167bfb0d112323de301bfb24991398 100644
--- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java
+++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java
@@ -18,8 +18,8 @@ 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;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 /***
  * Topology Cluster merge/split event history related classes and members
diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java
index dc061c6f4c11733a8813e79a1e5ff8f05bb8ddee..b8d3e143c31b8529134f63ee5575a1559113229f 100644
--- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java
+++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java
@@ -18,8 +18,8 @@ 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;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 /***
  * Topology link up/down event history related classes and members
diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java
index 3b27c8eef9e82ed8a2ecd30986a1c13d0a39eb36..f8c79837f68cc55c10f174e9aadf0d7bc951cc57 100644
--- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java
+++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java
@@ -19,8 +19,8 @@ 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;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 /***
  * Topology Switch event history related classes and members
diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java b/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java
index a09cc12ab01a8941d72fbfd8a86cbe0957cf2af8..8de425b168a166aa8d14435a27616b19d8f0ec55 100644
--- a/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java
+++ b/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java
@@ -18,11 +18,11 @@ 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.openflow.util.HexString;
 
 import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkDirection;
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java
index c3976e3c3f8dfd7361bf6f4ac8ad8cb9916af94e..40569b478bd171f212d969b6e61cf9d1da3c5c12 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java
@@ -16,7 +16,7 @@
 
 package net.floodlightcontroller.loadbalancer;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 /**
  * Data structure for Load Balancer based on
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java
index 1653b00d04ee173df4cdad81fa3955ccfb46b06d..bb723e01ab4b87fb6adc49ac2ba8cc5cfc0a8179 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java
@@ -18,10 +18,10 @@ package net.floodlightcontroller.loadbalancer;
 
 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 
 public class LBMemberSerializer extends JsonSerializer<LBMember>{
 
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java
index 07961150e0feb8e7c6f6910c1d8bcc8233a9ff31..2e06fb123267b6f6e57139b686569ab0a8b6d15c 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java
@@ -18,7 +18,7 @@ package net.floodlightcontroller.loadbalancer;
 
 import java.util.ArrayList;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 import net.floodlightcontroller.loadbalancer.LoadBalancer.IPClient;
 
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBPoolSerializer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBPoolSerializer.java
index 042c13ccbef1b5658ec409c143c84f3503e62372..4cb3ae1eaecef9685786103015ad18e0baf38ff9 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/LBPoolSerializer.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBPoolSerializer.java
@@ -18,10 +18,10 @@ package net.floodlightcontroller.loadbalancer;
 
 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 
 public class LBPoolSerializer extends JsonSerializer<LBPool>{
 
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java
index 10e3de375bcfae4056573f640f53c536709dcf5a..1d0a9bdadd16cca75dccb00cb3251cd4b160e59b 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java
@@ -18,7 +18,7 @@ package net.floodlightcontroller.loadbalancer;
 
 import java.util.ArrayList;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 import net.floodlightcontroller.loadbalancer.LoadBalancer.IPClient;
 import net.floodlightcontroller.util.MACAddress;
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBVipSerializer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBVipSerializer.java
index 4421a9c3b4c6ba41a40a1dee60cf9af9aa9b2d3b..9a853b37d317abe899a8888b4c0e42c4e29fa00f 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/LBVipSerializer.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBVipSerializer.java
@@ -17,10 +17,10 @@
 package net.floodlightcontroller.loadbalancer;
 
 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 
 public class LBVipSerializer extends JsonSerializer<LBVip>{
 
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java
index 43c808bd1cbd5c146f3c28925c8f34c572945e66..b18ad040f4e7bdbb1b07b42176413573893d0b5e 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java
@@ -21,10 +21,10 @@ 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 com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
 import org.restlet.resource.Delete;
 import org.restlet.resource.Get;
 import org.restlet.resource.Post;
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/MonitorsResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/MonitorsResource.java
index 3f378ef334bb112fc7a596f0d49a30981ad2904a..928039c1f00f695d502261273192623c270a7f68 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/MonitorsResource.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/MonitorsResource.java
@@ -19,10 +19,11 @@ package net.floodlightcontroller.loadbalancer;
 import java.io.IOException;
 import java.util.Collection;
 
-import org.codehaus.jackson.JsonParseException;
-import org.codehaus.jackson.JsonParser;
-import org.codehaus.jackson.JsonToken;
-import org.codehaus.jackson.map.MappingJsonFactory;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
+
 import org.restlet.resource.Delete;
 import org.restlet.resource.Get;
 import org.restlet.resource.Post;
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java
index 42d0477b5511bd54577692ed2452cf69e1e82297..8feb2eb03d1668eb23d0589ac3f1f8f21e58597d 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java
@@ -21,10 +21,10 @@ 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 com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
 import org.restlet.resource.Delete;
 import org.restlet.resource.Get;
 import org.restlet.resource.Post;
diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/VipsResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/VipsResource.java
index da64998b5e9c814881fb6c26071f683d63d5510e..9fe63886e9016317b6cb46090bfa13b9f71ce001 100644
--- a/src/main/java/net/floodlightcontroller/loadbalancer/VipsResource.java
+++ b/src/main/java/net/floodlightcontroller/loadbalancer/VipsResource.java
@@ -21,10 +21,10 @@ 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 com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
 import org.restlet.resource.Delete;
 import org.restlet.resource.Get;
 import org.restlet.resource.Post;
diff --git a/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java b/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java
index 0db2aa89156e9969d393778b54a144a0933d08b3..9d6531889becf37f29ee9df1894d0a0401016c12 100644
--- a/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java
+++ b/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java
@@ -21,7 +21,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 import net.floodlightcontroller.core.IOFMessageListener;
 
diff --git a/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java b/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java
index bd2b98447482f7db6b027d3d3e4687b618bb246a..8dbeba100644899b523d034fe0a0a198d5169b0e 100644
--- a/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java
+++ b/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java
@@ -20,10 +20,10 @@ 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;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 
 public class CumulativeTimeBucketJSONSerializer
                                 extends JsonSerializer<CumulativeTimeBucket> {
diff --git a/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java b/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java
index 02e56920d7fedcacb22f5b8219e2544a91d14804..b14c18c41b7b3be5c86914fcda676cb7ed225ee6 100644
--- a/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java
+++ b/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java
@@ -16,7 +16,7 @@
 
 package net.floodlightcontroller.perfmon;
 
-import org.codehaus.jackson.annotate.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonProperty;
 
 import net.floodlightcontroller.core.IOFMessageListener;
 
diff --git a/src/main/java/net/floodlightcontroller/routing/Link.java b/src/main/java/net/floodlightcontroller/routing/Link.java
index b79b9a9bc942aeb425a046f42aa790b0d29f011d..abe88cfc1d8edf9bf1ddf22543cae8b18035170b 100755
--- a/src/main/java/net/floodlightcontroller/routing/Link.java
+++ b/src/main/java/net/floodlightcontroller/routing/Link.java
@@ -20,8 +20,8 @@ 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 com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.openflow.util.HexString;
 
 public class Link implements Comparable<Link> {
diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java
index fc12b3edb165ed47330ad0bb62649d8f3a863d8f..1eb337e2b2f7e6abfb42982138bc7c2efb4f258b 100644
--- a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java
+++ b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java
@@ -34,10 +34,10 @@ 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 com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
 import org.openflow.protocol.OFFlowMod;
 import org.openflow.protocol.OFMatch;
 import org.openflow.protocol.OFPacketOut;
diff --git a/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java b/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java
index fcfa96f40d6b0e27271f48b9a91524d5b6a52a0e..a439809ba8236c36e0c78a9ff7a1bb4e5266b0e7 100644
--- a/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java
+++ b/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java
@@ -24,8 +24,8 @@ 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 com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.type.TypeReference;
 import org.restlet.resource.Post;
 import org.restlet.resource.ServerResource;
 import org.slf4j.Logger;
diff --git a/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java b/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java
index 5b0c3c7133a54a10214b0a03bf7c15bc43e840da..0970b36f692a5148dd3bb313df8864f61e4fe858 100644
--- a/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java
+++ b/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java
@@ -22,6 +22,8 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import net.floodlightcontroller.core.module.FloodlightModuleContext;
 import net.floodlightcontroller.core.module.FloodlightModuleException;
@@ -70,7 +72,17 @@ public class ThreadPool implements IThreadPoolService, IFloodlightModule {
     @Override
     public void init(FloodlightModuleContext context)
                                  throws FloodlightModuleException {
-        executor = Executors.newScheduledThreadPool(15);
+        final ThreadGroup tg = new ThreadGroup("Scheduled Task Threads");
+        ThreadFactory f = new ThreadFactory() {
+            AtomicInteger id = new AtomicInteger();
+            
+            @Override
+            public Thread newThread(Runnable runnable) {
+                return new Thread(tg, runnable, 
+                                  "Scheduled-" + id.getAndIncrement());
+            }
+        };
+        executor = Executors.newScheduledThreadPool(5, f);
     }
 
     @Override
diff --git a/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java b/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java
index 8711551b82e321b8aa261845dae3e24fefba3552..8c5645143ac0edc54df86582d9b83273bd30ed56 100644
--- a/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java
+++ b/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java
@@ -19,8 +19,8 @@ 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 com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.openflow.util.HexString;
 
 /**
diff --git a/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java b/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java
index f0de370d992bad0a1817e1f6b9a93e5c2e2777c3..8568d0d576484361db2ab45f8f2d091e34ebec10 100644
--- a/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java
+++ b/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java
@@ -16,7 +16,7 @@
 
 package net.floodlightcontroller.util;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 @JsonSerialize(using=EventHistoryBaseInfoJSONSerializer.class)
 public class EventHistoryBaseInfo {
diff --git a/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java b/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java
index 6f1d1ff8bdf677bbe3c59fcdaec9cdac0985f0a2..ad14372607371e0aa70357092b02430b7b99651d 100644
--- a/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java
+++ b/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java
@@ -21,10 +21,10 @@ 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;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 
 
 /**
diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java b/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java
index dec2319b2cd0569c9e6f9771c7923a3c62602b3b..03f0a63ae3b93f20a1b370048fd81884bbd83cf5 100644
--- a/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java
+++ b/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java
@@ -20,10 +20,10 @@ 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 com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
 import org.restlet.data.Status;
 import org.restlet.resource.Delete;
 import org.restlet.resource.Put;
diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java b/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java
index 746490bc6b2897a560c8d654f20c4cd8e93ce115..35ff629f4c557f5185585c3440fbaa22e77d43c9 100644
--- a/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java
+++ b/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java
@@ -21,10 +21,10 @@ 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 com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
 import org.restlet.data.Status;
 import org.restlet.resource.Delete;
 import org.restlet.resource.Get;
diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java
index 03eedd3382cff0483e4bd2743a60452e99dafc81..a73c72ef7f889bcd8ef2253f4bfdb6d2c81818cf 100644
--- a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java
+++ b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java
@@ -19,7 +19,7 @@ package net.floodlightcontroller.virtualnetwork;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 import net.floodlightcontroller.util.MACAddress;
 
diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java
index 487fb8dbe9bf2816f3400d63889c88541240f3b7..6f61b3859a8083838f7a052bb536913f5bc47a5b 100644
--- a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java
+++ b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java
@@ -21,10 +21,10 @@ 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;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 
 /**
  * Serialize a VirtualNetwork object
diff --git a/src/main/java/org/openflow/protocol/OFFeaturesReply.java b/src/main/java/org/openflow/protocol/OFFeaturesReply.java
index d3af5741a325a559f0aa5609bdd841d82f87a053..6152bed7ee8ce699fadbd96fce21660a49ac6edc 100644
--- a/src/main/java/org/openflow/protocol/OFFeaturesReply.java
+++ b/src/main/java/org/openflow/protocol/OFFeaturesReply.java
@@ -21,7 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.openflow.protocol.serializers.OFFeaturesReplyJSONSerializer;
 import org.openflow.util.U16;
diff --git a/src/main/java/org/openflow/protocol/OFMatch.java b/src/main/java/org/openflow/protocol/OFMatch.java
index c17bc0d29c0dd2febf7dc58ed792d244b6c5ee4b..86b9a28f13dcfd3717ad3400ac05af30c66d7161 100644
--- a/src/main/java/org/openflow/protocol/OFMatch.java
+++ b/src/main/java/org/openflow/protocol/OFMatch.java
@@ -23,7 +23,7 @@ import java.util.Arrays;
 
 import net.floodlightcontroller.packet.Ethernet;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.openflow.protocol.serializers.OFMatchJSONSerializer;
 import org.openflow.util.HexString;
diff --git a/src/main/java/org/openflow/protocol/OFPhysicalPort.java b/src/main/java/org/openflow/protocol/OFPhysicalPort.java
index c8c9a6b9e3a05801b14995b566bc618977e4716d..06a98e6f7b91b43e671cd74ea3f03de3ab684c21 100644
--- a/src/main/java/org/openflow/protocol/OFPhysicalPort.java
+++ b/src/main/java/org/openflow/protocol/OFPhysicalPort.java
@@ -24,7 +24,7 @@ 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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.openflow.util.HexString;
 
diff --git a/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java b/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java
index 68327280da6dad4ef3a8dcfae298e4c6038529ba..de6a92bb6dd51ef9018f31fab94cf376bb722c16 100644
--- a/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java
+++ b/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java
@@ -24,7 +24,7 @@ import java.util.Arrays;
 
 import net.floodlightcontroller.core.web.serializers.ByteArrayMACSerializer;
 
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.openflow.protocol.OFPhysicalPort;
 
diff --git a/src/main/java/org/openflow/protocol/factory/BasicFactory.java b/src/main/java/org/openflow/protocol/factory/BasicFactory.java
index c4d148337526ecbae5ea8d9aa67d89e657404da7..f484a167f5cd64f1eaec92dd366a4f0ff02a8ece 100644
--- a/src/main/java/org/openflow/protocol/factory/BasicFactory.java
+++ b/src/main/java/org/openflow/protocol/factory/BasicFactory.java
@@ -43,15 +43,21 @@ import org.openflow.protocol.vendor.OFVendorId;
  * @author Rob Sherwood (rob.sherwood@stanford.edu)
  *
  */
-public class BasicFactory implements OFMessageFactory, OFActionFactory,
+public enum BasicFactory implements OFMessageFactory, OFActionFactory,
         OFStatisticsFactory, OFVendorDataFactory {
+    SINGLETON_INSTANCE;
+
 
     private final OFVendorActionRegistry vendorActionRegistry;
 
-    public BasicFactory() {
+    private BasicFactory() {
         vendorActionRegistry = OFVendorActionRegistry.getInstance();
     }
 
+    public static BasicFactory getInstance() {
+        return SINGLETON_INSTANCE;
+    }
+
     /**
      * create and return a new instance of a message for OFType t. Also injects
      * factories for those message types that implement the *FactoryAware
diff --git a/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java b/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java
index ad57312f11a7bea45050d1dcf1ea65a6d7479521..1102dc78653263a2e33a4967b8d4a3935f44df67 100644
--- a/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java
+++ b/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java
@@ -19,10 +19,10 @@ 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 import org.openflow.protocol.OFFeaturesReply;
 import org.openflow.util.HexString;
 
diff --git a/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java b/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java
index 69312feb5667855d36c3419f2749658b4ef43eee..2ae2d1c467d095491b88d8c78a15479ed04bdbae 100644
--- a/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java
+++ b/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java
@@ -19,10 +19,10 @@ 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 com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
 import org.openflow.protocol.OFMatch;
 import org.openflow.util.HexString;
 
diff --git a/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java b/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java
index 7dec16b69d8d295985363703585fb82ab08f3da1..0c86006b0919e1a87120ab96dec2caed96cd48da 100644
--- a/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java
+++ b/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java
@@ -18,7 +18,7 @@
 package org.openflow.protocol.statistics;
 
 
-import org.codehaus.jackson.annotate.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import org.jboss.netty.buffer.ChannelBuffer;
 
 /**
diff --git a/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java b/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java
index bea7f1e3b064a947a95d1398696462ae886cf697..8907e74b355593dfe607152180c56295a46d667f 100644
--- a/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java
+++ b/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java
@@ -19,7 +19,7 @@ package org.openflow.protocol.statistics;
 
 import java.util.List;
 
-import org.codehaus.jackson.annotate.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.openflow.protocol.OFMatch;
 import org.openflow.protocol.action.OFAction;
diff --git a/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java b/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java
index 9a84aad1edfba2357480c27dcde53f68e45ef595..15192bbc4d495fa4fd1f8f064adf0058f348d581 100644
--- a/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java
+++ b/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java
@@ -20,8 +20,8 @@ package org.openflow.protocol.statistics;
 
 import net.floodlightcontroller.core.web.serializers.UShortSerializer;
 
-import org.codehaus.jackson.annotate.JsonIgnore;
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.jboss.netty.buffer.ChannelBuffer;
 
 /**
diff --git a/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java b/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java
index 03cbb9cef0c87b3056a57fef8c309e2ad2d1ed23..34a9e18200bc73ff5a7d628c671bb641cd243068 100644
--- a/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java
+++ b/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java
@@ -18,7 +18,7 @@
 package org.openflow.protocol.statistics;
 
 
-import org.codehaus.jackson.annotate.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import org.jboss.netty.buffer.ChannelBuffer;
 
 /**
diff --git a/src/main/java/org/sdnplatform/sync/IClosableIterator.java b/src/main/java/org/sdnplatform/sync/IClosableIterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2c2f7797c7e316c61d46a3a6befd6b1d0f86af6
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/IClosableIterator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync;
+
+import java.io.Closeable;
+import java.util.Iterator;
+
+/**
+ * An iterator that must be closed after use
+ *
+ *
+ * @param <T> The type being iterated over
+ */
+public interface IClosableIterator<T> extends Iterator<T>,Closeable {
+
+    /**
+     * Close the iterator
+     */
+    public void close();
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/IInconsistencyResolver.java b/src/main/java/org/sdnplatform/sync/IInconsistencyResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..67ed911a660eda236f20f17052c2e5b73c7954c9
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/IInconsistencyResolver.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync;
+
+import java.util.List;
+
+/**
+ * A method for resolving inconsistent object values into a single value.
+ * Applications can implement this to provide a method for reconciling conflicts
+ * that cannot be resolved simply by the version information.
+ *
+ *
+ */
+public interface IInconsistencyResolver<T> {
+
+    /**
+     * Take two different versions of an object and combine them into a single
+     * version of the object Implementations must maintain the contract that
+     * <ol>
+     * <li>
+     * {@code resolveConflict([null, null]) == null}</li>
+     * <li>
+     * if {@code t != null}, then
+     *
+     * {@code resolveConflict([null, t]) == resolveConflict([t, null]) == t}</li>
+     *
+     * @param items The items to be resolved
+     * @return The united object
+     */
+    public List<T> resolveConflicts(List<T> items);
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/IStoreClient.java b/src/main/java/org/sdnplatform/sync/IStoreClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb2a64212e36c2dabba03ef1af4d90ad03b2a4a0
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/IStoreClient.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import org.sdnplatform.sync.error.ObsoleteVersionException;
+import org.sdnplatform.sync.error.SyncException;
+
+
+/**
+ * The user-facing interface to a sync store. Gives basic put/get/delete
+ * plus helper functions.
+ *
+ * @param <K> The type of the key being stored
+ * @param <V> The type of the value being stored
+ */
+public interface IStoreClient<K, V> {
+
+    /**
+     * Get the value associated with the given key or null if there is no value
+     * associated with this key. This method strips off all version information
+     * and is only useful when no further storage operations will be done on
+     * this key. In general, you should prefer the get() method that returns
+     * version information unless.
+     *
+     * @param key The key
+     * @throws SyncException
+     */
+    public V getValue(K key) throws SyncException;
+
+    /**
+     * Get the value associated with the given key or defaultValue if there is
+     * no value associated with the key. This method strips off all version
+     * information and is only useful when no further storage operations will be
+     * done on this key.In general, you should prefer the get() method that returns
+     * version information unless.
+     *
+     * @param key The key for which to fetch the associated value
+     * @param defaultValue A value to return if there is no value associated
+     *        with this key
+     * @return Either the value stored for the key or the default value.
+     * @throws SyncException
+     */
+    public V getValue(K key, V defaultValue) throws SyncException;
+
+    /**
+     * Get the versioned value associated with the given key.  Note that while 
+     * this function will never return null, the {@link Versioned} returned 
+     * can have a null value (i.e. {@link Versioned#getValue() can be null} 
+     * if the key is not present. 
+     *
+     * @param key The key for which to fetch the value.
+     * @return The versioned value
+     * @throws SyncException
+     */
+    public Versioned<V> get(K key) throws SyncException;
+
+    /**
+     * Get the versioned value associated with the given key or the defaultValue
+     * if no value is associated with the key.
+     *
+     * @param key The key for which to fetch the value.
+     * @return The versioned value, or the defaultValue if no value is stored
+     *         for this key.
+     * @throws SyncException
+     */
+    public Versioned<V> get(K key, Versioned<V> defaultValue)
+            throws SyncException;
+
+    /**
+     * Get an iterator that will get all the entries in the store.  Note
+     * that this has the potential to miss any values added while you're 
+     * iterating through the collection, and it's possible that items will
+     * be deleted before you get to the end.
+     * 
+     * Note that you *must* close the {@link IClosableIterator} when you are
+     * finished with it or there may be resource leaks.  An example of how you
+     * should use this iterator to ensure that it is closed even if there are
+     * exceptions follows:
+     * <code>
+     * IClosableIterator iter = store.entries();
+     * try {
+     *     // do your iteration
+     * } finally {
+     *     iter.close();
+     * }
+     * </code>
+     * 
+     * Another important caveat is that because {@link IClosableIterator} 
+     * extends {@link Iterator}, there is no checked exception declared in
+     * {@link Iterator#next()}.  Because of this, calling 
+     * {@link Iterator#next()} on the iterator returned here may throw a 
+     * SyncRuntimeException wrapping a SyncException such as might be
+     * returned by {@link IStoreClient#get(Object)}
+     * @return 
+     * @throws SyncException
+     */
+    public IClosableIterator<Entry<K, Versioned<V>>> entries() 
+            throws SyncException;
+    
+    /**
+     * Associated the given value to the key, clobbering any existing values
+     * stored for the key.
+     *
+     * @param key The key
+     * @param value The value
+     * @return version The version of the object
+     * @throws SyncException
+     */
+    public IVersion put(K key, V value) throws SyncException;
+
+    /**
+     * Put the given Versioned value into the store for the given key if the
+     * version is greater to or concurrent with existing values. Throw an
+     * ObsoleteVersionException otherwise.
+     *
+     * @param key The key
+     * @param versioned The value and its versioned
+     * @throws ObsoleteVersionException
+     * @throws SyncException
+     */
+    public IVersion put(K key, Versioned<V> versioned)
+            throws SyncException;
+
+    /**
+     * Put the versioned value to the key, ignoring any ObsoleteVersionException
+     * that may be thrown
+     *
+     * @param key The key
+     * @param versioned The versioned value
+     * @return true if the put succeeded
+     * @throws SyncException
+     */
+    public boolean putIfNotObsolete(K key, Versioned<V> versioned) 
+            throws SyncException;
+
+    /**
+     * Delete the key by writing a null tombstone to the store
+     *
+     * @param key The key
+     * @throws SyncException
+     */
+    public void delete(K key) throws SyncException;
+
+    /**
+     * Delete the key by writing a null tombstone to the store using the
+     * provided {@link IVersion}.
+     *
+     * @param key The key to delete
+     * @param version The version of the key
+     * @throws SyncException
+     */
+    public void delete(K key, IVersion version) throws SyncException;
+    
+    /**
+     * Add a listener that will be notified about changes to the given store.
+     * @param listener the {@link IStoreListener} that will receive the 
+     * notifications
+     */
+    public void addStoreListener(IStoreListener<K> listener); 
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/IStoreListener.java b/src/main/java/org/sdnplatform/sync/IStoreListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..48a12f25d025356730be7a13741356c8e619b6fb
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/IStoreListener.java
@@ -0,0 +1,34 @@
+package org.sdnplatform.sync;
+
+import java.util.Iterator;
+
+/**
+ * A listener interface that will receive updates on a particular store
+ * @author readams
+ * @param <K> the key type for the store
+ */
+public interface IStoreListener<K> {
+    /**
+     * The origin of the update
+     * @author readams
+     */
+    public enum UpdateType {
+        /**
+         * An update that originated from a write to the local store
+         */
+        LOCAL,
+        /**
+         * An update that originated from a value synchronized from a remote
+         * node.  Note that it is still possible that this includes only
+         * information that originated from the current node.
+         */
+        REMOTE
+    };
+    
+    /**
+     * Called when keys in the store are modified or deleted.
+     * @param type the type of the update
+     * @see UpdateType
+     */
+    public void keysModified(Iterator<K> keys, UpdateType type);
+}
diff --git a/src/main/java/org/sdnplatform/sync/ISyncService.java b/src/main/java/org/sdnplatform/sync/ISyncService.java
new file mode 100644
index 0000000000000000000000000000000000000000..6518420603b2bc5f47f75124334355ce2e4fa019
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/ISyncService.java
@@ -0,0 +1,121 @@
+package org.sdnplatform.sync;
+
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.error.UnknownStoreException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+/**
+ * The sync service provides a high-performance in-memory database for
+ * fault and partition-tolerant replication of state data.  It provides
+ * eventually consistent semantics with versioning through vector clocks
+ * and allows custom handling of resolution of inconsistent writes.
+ * 
+ * An important caveat to keep in mind is that keys should not be constructed
+ * using any hash tables, because the serialized version will contain a
+ * a potentially-inconsistent ordering of elements resulting in a failure
+ * for keys to match.  Using a java bean or a {@link JsonNode} will avoid this
+ * problem.  Using strings as keys also avoids this problem.
+ * @author readams
+ */
+public interface ISyncService extends IFloodlightService {
+    public enum Scope {
+        GLOBAL,
+        LOCAL
+    }
+
+    /**
+     * Create a store with the given store name and scope
+     * @param storeName the name of the store
+     * @param scope the distribution scope for the data
+     * @throws SyncException 
+     */
+    public void registerStore(String storeName, Scope scope) 
+            throws SyncException;
+
+    /**
+     * Create a store with the given store name and scope that will be 
+     * persistent across reboots.  The performance will be dramatically slower
+     * @param storeName the name of the store
+     * @param scope the distribution scope for the data
+     */
+    public void registerPersistentStore(String storeName, Scope scope) 
+            throws SyncException;
+
+    /**
+     * Get a store client for the given store.  The store client will use
+     * a default inconsistency resolution strategy which will use the
+     * timestamps of any concurrent updates and choose the later update
+     * @param storeName the name of the store to retrieve
+     * @param keyClass the class for the underlying key needed for
+     * deserialization
+     * @param valueClass the class for the underlying value needed for
+     * deserialization
+     * @return the store client
+     * @throws UnknownStoreException
+     */
+    public <K, V> IStoreClient<K, V> getStoreClient(String storeName,
+                                                    Class<K> keyClass,
+                                                    Class<V> valueClass)
+                               throws UnknownStoreException;
+
+    /**
+     * Get a store client that will use the provided inconsistency resolver
+     * to resolve concurrent updates.
+     * @param storeName the name of the store to retrieve
+     * @param keyClass the class for the underlying key needed for
+     * deserialization
+     * @param valueClass the class for the underlying value needed for
+     * deserialization
+     * @param resolver the inconsistency resolver to use for the store
+     * @return the store client
+     * @throws UnknownStoreException
+     */
+    public <K, V> IStoreClient<K, V>
+        getStoreClient(String storeName,
+                       Class<K> keyClass,
+                       Class<V> valueClass,
+                       IInconsistencyResolver<Versioned<V>> resolver)
+                               throws UnknownStoreException;
+
+    /**
+     * Get a store client for the given store.  The store client will use
+     * a default inconsistency resolution strategy which will use the
+     * timestamps of any concurrent updates and choose the later update
+     * @param storeName the name of the store to retrieve
+     * @param keyType the type reference for the underlying key needed for
+     * deserialization
+     * @param valueType the type reference for the underlying value needed for
+     * deserialization
+     * @return the store client
+     * @throws UnknownStoreException
+     */
+    public <K, V> IStoreClient<K, V> getStoreClient(String storeName,
+                                                    TypeReference<K> keyType,
+                                                    TypeReference<V> valueType)
+                               throws UnknownStoreException;
+
+    /**
+     * Get a store client that will use the provided inconsistency resolver
+     * to resolve concurrent updates.
+     * @param storeName the name of the store to retrieve
+     * @param keyType the type reference for the underlying key needed for
+     * deserialization
+     * @param valueType the type reference for the underlying value needed for
+     * deserialization
+     * @param resolver the inconsistency resolver to use for the store
+     * @return the store client
+     * @throws UnknownStoreException
+     */
+    public <K, V> IStoreClient<K, V>
+        getStoreClient(String storeName,
+                       TypeReference<K> keyType,
+                       TypeReference<V> valueType,
+                       IInconsistencyResolver<Versioned<V>> resolver)
+                               throws UnknownStoreException;
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/IVersion.java b/src/main/java/org/sdnplatform/sync/IVersion.java
new file mode 100644
index 0000000000000000000000000000000000000000..3af0e802271333361944fcf9f723bf918d2acf72
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/IVersion.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008-2009 LinkedIn, Inc
+ * Copyright 2013 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 org.sdnplatform.sync;
+
+/**
+ * An interface that allows us to determine if a given version happened before
+ * or after another version.
+ *
+ * This could have been done using the comparable interface but that is
+ * confusing, because the numeric codes are easily confused, and because
+ * concurrent versions are not necessarily "equal" in the normal sense.
+ *
+ *
+ */
+
+public interface IVersion {
+    /**
+     * The result of comparing two times--either t1 is BEFORE t2,
+     * t1 is AFTER t2, or t1 happens CONCURRENTLY to t2.
+     */
+    public enum Occurred {
+        BEFORE,
+        AFTER,
+        CONCURRENTLY
+    }
+
+    /**
+     * Return whether or not the given version preceeded this one, succeeded it,
+     * or is concurrant with it
+     *
+     * @param v The other version
+     */
+    public Occurred compare(IVersion v);
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/Versioned.java b/src/main/java/org/sdnplatform/sync/Versioned.java
new file mode 100644
index 0000000000000000000000000000000000000000..73617a7e2c18f416baba3f89878f25579cb260fa
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/Versioned.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.sdnplatform.sync.IVersion.Occurred;
+import org.sdnplatform.sync.internal.version.VectorClock;
+
+import com.google.common.base.Objects;
+
+/**
+ * A wrapper for an object that adds a Version.
+ *
+ *
+ */
+public class Versioned<T> implements Serializable {
+
+    private static final long serialVersionUID = 1;
+
+    private volatile VectorClock version;
+    private volatile T value;
+
+    public Versioned(T object) {
+        this(object, new VectorClock());
+    }
+
+    public Versioned(T object, 
+                     IVersion version) {
+        this.version = version == null ? new VectorClock() : (VectorClock) version;
+        this.value = object;
+    }
+
+    public IVersion getVersion() {
+        return version;
+    }
+
+    public void increment(int nodeId, long time) {
+        this.version = version.incremented(nodeId, time);
+    }
+
+    public T getValue() {
+        return value;
+    }
+
+    public void setValue(T object) {
+        this.value = object;
+    }
+
+    /**
+     * Determines if two objects are equal as determined by
+     * {@link Object#equals(Object)}, or "deeply equal" if both are arrays.
+     * <p>
+     * If both objects are null, true is returned; if both objects are array,
+     * the corresponding {@link Arrays#deepEquals(Object[], Object[])}, or
+     * {@link Arrays#equals(int[], int[])} or the like are called to determine
+     * equality.
+     * <p>
+     * Note that this method does not "deeply" compare the fields of the
+     * objects.
+     */
+    private static boolean deepEquals(Object o1, Object o2) {
+        if(o1 == o2) {
+            return true;
+        }
+        if(o1 == null || o2 == null) {
+            return false;
+        }
+
+        Class<?> type1 = o1.getClass();
+        Class<?> type2 = o2.getClass();
+        if(!(type1.isArray() && type2.isArray())) {
+            return o1.equals(o2);
+        }
+        if(o1 instanceof Object[] && o2 instanceof Object[]) {
+            return Arrays.deepEquals((Object[]) o1, (Object[]) o2);
+        }
+        if(type1 != type2) {
+            return false;
+        }
+        if(o1 instanceof boolean[]) {
+            return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+        }
+        if(o1 instanceof char[]) {
+            return Arrays.equals((char[]) o1, (char[]) o2);
+        }
+        if(o1 instanceof byte[]) {
+            return Arrays.equals((byte[]) o1, (byte[]) o2);
+        }
+        if(o1 instanceof short[]) {
+            return Arrays.equals((short[]) o1, (short[]) o2);
+        }
+        if(o1 instanceof int[]) {
+            return Arrays.equals((int[]) o1, (int[]) o2);
+        }
+        if(o1 instanceof long[]) {
+            return Arrays.equals((long[]) o1, (long[]) o2);
+        }
+        if(o1 instanceof float[]) {
+            return Arrays.equals((float[]) o1, (float[]) o2);
+        }
+        if(o1 instanceof double[]) {
+            return Arrays.equals((double[]) o1, (double[]) o2);
+        }
+        throw new AssertionError();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if(o == this)
+            return true;
+        else if(!(o instanceof Versioned<?>))
+            return false;
+
+        Versioned<?> versioned = (Versioned<?>) o;
+        return Objects.equal(getVersion(), versioned.getVersion())
+               && deepEquals(getValue(), versioned.getValue());
+    }
+
+    @Override
+    public int hashCode() {
+        int v = 31 + version.hashCode();
+        if(value != null) {
+            v += 31 * value.hashCode();
+        }
+        return v;
+    }
+
+    @Override
+    public String toString() {
+        return "[" + value + ", " + version + "]";
+    }
+
+    /**
+     * Create a clone of this Versioned object such that the object pointed to
+     * is the same, but the VectorClock and Versioned wrapper is a shallow copy.
+     */
+    public Versioned<T> cloneVersioned() {
+        return new Versioned<T>(this.getValue(), this.version.clone());
+    }
+
+    public static <S> Versioned<S> value(S s) {
+        return new Versioned<S>(s, new VectorClock());
+    }
+
+    public static <S> Versioned<S> value(S s, IVersion v) {
+        return new Versioned<S>(s, v);
+    }
+
+    public static <S> Versioned<S> emptyVersioned() {
+        return new Versioned<S>(null, new VectorClock(0));
+    }
+    
+    public static final class HappenedBeforeComparator<S> implements Comparator<Versioned<S>> {
+
+        public int compare(Versioned<S> v1, Versioned<S> v2) {
+            Occurred occurred = v1.getVersion().compare(v2.getVersion());
+            if(occurred == Occurred.BEFORE)
+                return -1;
+            else if(occurred == Occurred.AFTER)
+                return 1;
+            else
+                return 0;
+        }
+    }
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/client/ShellCommand.java b/src/main/java/org/sdnplatform/sync/client/ShellCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d3cfbf5d8041231862190f27e3ea6a33d3f161a
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/client/ShellCommand.java
@@ -0,0 +1,68 @@
+package org.sdnplatform.sync.client;
+
+import java.io.IOException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.MappingJsonFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+/**
+ * A user command for the command line client
+ * @author readams
+ */
+public abstract class ShellCommand {
+
+    protected static final ObjectMapper mapper = new ObjectMapper();
+    protected static final MappingJsonFactory mjf = 
+            new MappingJsonFactory(mapper);
+    
+    static {
+        mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,
+                         true);
+    }
+    
+    /**
+     * Execute the command on the given tokens
+     * @param tokens the argument tokens.  The first token will be the command
+     * @param line the whole command line
+     * @return whether to exit the shell after the command
+     */
+    public abstract boolean execute(String[] tokens, 
+                                    String line) throws Exception;
+    
+    /**
+     * Return syntax description
+     * @return the syntax string
+     */
+    public abstract String syntaxString();
+    
+    /**
+     * Parse a JSON object
+     * @param jp the JSON parse
+     * @return the JSON node
+     * @throws IOException
+     */
+    protected JsonNode validateJson(JsonParser jp) throws IOException {
+        JsonNode parsed = null;
+        
+        try {
+            parsed = jp.readValueAsTree();
+        } catch (JsonProcessingException e) {
+            System.err.println("Could not parse JSON: " + e.getMessage());
+            return null;
+        }  
+        return parsed;
+    }
+    
+    /**
+     * Serialize a JSON object as bytes
+     * @param value the object to serialize
+     * @return the serialized bytes
+     * @throws Exception
+     */
+    protected byte[] serializeJson(JsonNode value) throws Exception {
+        return mapper.writeValueAsBytes(value);
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/client/SyncClient.java b/src/main/java/org/sdnplatform/sync/client/SyncClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc4cca161fed53395e305a856dd4fea3bd580858
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/client/SyncClient.java
@@ -0,0 +1,449 @@
+package org.sdnplatform.sync.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map.Entry;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.threadpool.ThreadPool;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.core.JsonParser;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+import org.sdnplatform.sync.IStoreClient;
+import org.sdnplatform.sync.ISyncService;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.ISyncService.Scope;
+import org.sdnplatform.sync.error.UnknownStoreException;
+import org.sdnplatform.sync.internal.remote.RemoteSyncManager;
+
+
+public class SyncClient {
+    RemoteSyncManager syncManager;
+    IStoreClient<JsonNode, JsonNode> storeClient;
+
+    /**
+     * Shell commands
+     */
+    protected HashMap<String, ShellCommand> commands;
+
+    /**
+     * Command-line settings
+     */
+    protected SyncClientSettings settings;
+
+    /**
+     * Stream to use for output
+     */
+    protected PrintStream out = System.out;
+
+    /**
+     * Stream to use for errors
+     */
+    protected PrintStream err = System.err;
+    
+    public SyncClient(SyncClientSettings settings) {
+        this.settings = settings;
+        
+        commands = new HashMap<String, ShellCommand>();
+        commands.put("quit", new QuitCommand());
+        commands.put("help", new HelpCommand());
+        commands.put("put", new PutCommand());
+        commands.put("delete", new DeleteCommand());
+        commands.put("get", new GetCommand());
+        commands.put("getfull", new GetFullCommand());
+        commands.put("store", new StoreCommand());
+        commands.put("register", new RegisterCommand());
+    }
+    
+    protected boolean connect() 
+            throws Exception {
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        ThreadPool tp = new ThreadPool();
+        syncManager = new RemoteSyncManager();
+        fmc.addService(IThreadPoolService.class, tp);
+        fmc.addService(ISyncService.class, syncManager);
+        fmc.addConfigParam(syncManager, "hostname", settings.hostname);
+        fmc.addConfigParam(syncManager, "port", 
+                           Integer.toString(settings.port));
+        tp.init(fmc);
+        syncManager.init(fmc);
+        tp.startUp(fmc);
+        syncManager.startUp(fmc);
+        
+        if (settings.storeName != null)
+            getStoreClient();
+        
+        out.println("Connected to " + 
+                settings.hostname + ":" + settings.port);
+        return true;
+    }
+    
+    protected void getStoreClient() 
+            throws UnknownStoreException {
+        storeClient = syncManager.getStoreClient(settings.storeName, 
+                                                 JsonNode.class, 
+                                                 JsonNode.class);
+    }
+    
+    protected boolean checkStoreSettings() {
+        if (settings.storeName == null) {
+            err.println("No store selected.  Select using \"store\" command.");
+            return false;
+        }
+        return true;
+    }
+    
+    /**
+     * Quit command
+     * @author readams
+     *
+     */
+    protected static class QuitCommand extends ShellCommand {
+        @Override
+        public boolean execute(String[] tokens, String line) {
+            return true;
+        }
+
+        @Override
+        public String syntaxString() {
+            return "quit";
+        }
+    }
+    
+    /**
+     * Help command
+     * @author readams
+     *
+     */
+    protected class HelpCommand extends ShellCommand {
+        @Override
+        public boolean execute(String[] tokens, String line) {
+            out.println("Commands: ");
+            for (Entry<String, ShellCommand> entry : commands.entrySet()) {
+                out.println(entry.getValue().syntaxString());
+            }
+            return false;
+        }
+
+        @Override
+        public String syntaxString() {
+            return "help";
+        }
+    }
+
+    /**
+     * Get command
+     * @author readams
+     *
+     */
+    protected class GetCommand extends ShellCommand {
+        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
+
+        @Override
+        public boolean execute(String[] tokens, String line) throws Exception {
+            if (tokens.length < 2) {
+                err.println("Usage: " + syntaxString());
+                return false;
+            }
+            if (!checkStoreSettings()) return false;
+            
+            StringReader sr = new StringReader(line);
+            while (sr.read() != ' ');
+            JsonParser jp = mjf.createJsonParser(sr);
+                     
+            JsonNode keyNode = validateJson(jp);
+            if (keyNode == null) return false;
+            
+            out.println("Getting Key:");
+            out.println(writer.writeValueAsString(keyNode));
+            out.println("");
+            Versioned<JsonNode> value = storeClient.get(keyNode);
+            display(value);
+            
+            return false;
+        }
+        
+        protected void display(Versioned<JsonNode> value) throws Exception {
+            if (value.getValue() == null) {
+                out.println("Not found");
+            } else {
+                out.println("Value:");
+                out.println(writer.writeValueAsString(value.getValue()));
+            }
+        }
+
+        @Override
+        public String syntaxString() {
+            return "get [key]";
+        }
+    }
+    
+    protected class GetFullCommand extends GetCommand {
+        @Override
+        protected void display(Versioned<JsonNode> value) throws Exception {
+            if (value.getValue() == null) {
+                out.println("Not found");
+            } else {
+                out.println("Version:");
+                out.println(value.getVersion());
+                out.println("Value:");
+                out.println(writer.writeValueAsString(value.getValue()));
+            }
+        }
+        
+        @Override
+        public String syntaxString() {
+            return "getfull [key]";
+        }
+    }
+    
+    /**
+     * Put command
+     * @author readams
+     *
+     */
+    protected class PutCommand extends ShellCommand {
+        @Override
+        public boolean execute(String[] tokens, String line) throws Exception {
+            if (tokens.length < 3) {
+                err.println("Usage: " + syntaxString());
+                return false;
+
+            }
+            if (!checkStoreSettings()) return false;
+
+            StringReader sr = new StringReader(line);
+            while (sr.read() != ' ');
+            JsonParser jp = mjf.createJsonParser(sr);
+            
+            JsonNode keyNode = validateJson(jp);
+            if (keyNode == null) return false;
+            JsonNode valueNode = validateJson(jp);
+            if (valueNode == null) return false;
+
+            ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
+            out.println("Putting Key:");
+            out.println(writer.writeValueAsString(keyNode));
+            out.println("\nValue:");
+            out.println(writer.writeValueAsString(valueNode));
+            out.flush();
+
+            storeClient.put(keyNode, valueNode);
+            out.println("Success");
+                        
+            return false;
+        }
+
+        @Override
+        public String syntaxString() {
+            return "put [key] [value]";
+        }
+    }
+    
+    /**
+     * Delete command
+     * @author readams
+     *
+     */
+    protected class DeleteCommand extends ShellCommand {
+        @Override
+        public boolean execute(String[] tokens, String line) throws Exception {
+            if (tokens.length < 2) {
+                err.println("Usage: " + syntaxString());
+                return false;
+            }
+            if (!checkStoreSettings()) return false;
+
+            StringReader sr = new StringReader(line);
+            while (sr.read() != ' ');
+            JsonParser jp = mjf.createJsonParser(sr);
+                     
+            JsonNode keyNode = validateJson(jp);
+            if (keyNode == null) return false;
+            
+            ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
+            out.println("Deleting Key:");
+            out.println(writer.writeValueAsString(keyNode));
+            out.println("");
+            
+            storeClient.delete(keyNode);
+            out.println("Success");
+
+            return false;
+        }
+
+        @Override
+        public String syntaxString() {
+            return "delete [key]";
+        }
+    }
+    
+    /**
+     * Choose the store
+     * @author readams
+     */
+    protected class StoreCommand extends ShellCommand {
+
+        @Override
+        public boolean execute(String[] tokens, String line)
+                throws Exception {
+            if (tokens.length < 2) {
+                err.println("Usage: " + syntaxString());
+                return false;
+            }
+            
+            settings.storeName = tokens[1];
+            getStoreClient();
+            return false;
+        }
+
+        @Override
+        public String syntaxString() {
+            return "store [storeName]";
+        }
+        
+    }
+    
+    /**
+     * Register a new store
+     * @author readams
+     */
+    protected class RegisterCommand extends ShellCommand {
+
+        @Override
+        public boolean execute(String[] tokens, String line)
+                throws Exception {
+            if (tokens.length < 3) {
+                err.println("Usage: " + syntaxString());
+                return false;
+            }
+            Scope scope = Scope.LOCAL;
+            if ("global".equals(tokens[2]))
+                scope = Scope.GLOBAL;
+
+            settings.storeName = tokens[1];
+            syncManager.registerStore(settings.storeName, scope);
+            getStoreClient();
+            return false;
+        }
+
+        @Override
+        public String syntaxString() {
+            return "register [storeName] [local|global]";
+        }
+        
+    }
+
+    protected void cleanup() throws InterruptedException {
+        syncManager.shutdown();
+    }
+    
+    protected boolean executeCommandLine(String line) {
+        String[] tokens = line.split("\\s+");
+        if (tokens.length > 0) {
+            ShellCommand command = commands.get(tokens[0]);
+            if (command != null) {
+                try {
+                    if (command.execute(tokens, line))
+                        return true;
+                } catch (Exception e) {
+                    err.println("Failed to execute command: " + 
+                                       line);
+                    if (settings.debug)
+                        e.printStackTrace(err);
+                    else
+                        err.println(e.getClass().getSimpleName() + 
+                                    ": " + e.getMessage());
+                }
+            } else {
+                err.println("Unrecognized command: \"" + 
+                        tokens[0] + "\""); 
+            }
+        }
+        return false;
+    }
+    
+    protected void startShell(SyncClientSettings settings) 
+            throws InterruptedException {
+        BufferedReader br = 
+                new BufferedReader(new InputStreamReader(System.in));
+        String line;
+        try {
+            while (true) {
+                err.print("> ");
+                line = br.readLine();
+                if (line == null) break;
+                if (executeCommandLine(line)) break;
+            }
+        } catch (IOException e) {
+            err.println("Could not read input: " + e.getMessage());
+        }
+    }
+    
+    protected static class SyncClientSettings {
+        @Option(name="--help", 
+                usage="Server hostname")
+        protected boolean help;
+        
+        @Option(name="--hostname", aliases="-h", 
+                usage="Server hostname", required=true)
+        protected String hostname;
+
+        @Option(name="--port", aliases="-p", usage="Server port", required=true)
+        protected int port;
+        
+        @Option(name="--store", aliases="-s", 
+                usage="Store name to access")
+        protected String storeName;
+        
+        @Option(name="--command", aliases="-c", 
+                usage="If set, execute a command")
+        protected String command;
+        
+        @Option(name="--debug", 
+                usage="Show full error information")
+        protected boolean debug;
+    }
+
+    /**
+     * @param args
+     * @throws InterruptedException 
+     */
+    public static void main(String[] args) throws Exception {
+        SyncClientSettings settings = new SyncClientSettings();
+        CmdLineParser parser = new CmdLineParser(settings);
+        try {
+            parser.parseArgument(args);
+        } catch (CmdLineException e) {
+            System.err.println(e.getMessage());
+            parser.printUsage(System.err);
+            System.exit(1);
+        }
+        if (settings.help) {
+            parser.printUsage(System.err);
+            System.exit(1);
+        }
+        
+        SyncClient client = new SyncClient(settings);
+        try {
+            if (false == client.connect()) {
+                return;
+            }
+            if (settings.command == null) {
+                client.startShell(settings);
+            } else {
+                client.executeCommandLine(settings.command);
+            }
+        } finally {
+            client.cleanup();
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/error/HandshakeTimeoutException.java b/src/main/java/org/sdnplatform/sync/error/HandshakeTimeoutException.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a2d6b8ecb6cfdf98872d2d31e5174c7292a1aa3
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/error/HandshakeTimeoutException.java
@@ -0,0 +1,16 @@
+/**
+*    Copyright 2011,2013, Big Switch Networks, Inc. 
+**/
+
+package org.sdnplatform.sync.error;
+
+/**
+ * Exception is thrown when the handshake fails to complete 
+ * before a specified time
+ * @author readams
+ */
+public class HandshakeTimeoutException extends SyncException {
+
+    private static final long serialVersionUID = 6859880268940337312L;
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/error/InconsistentDataException.java b/src/main/java/org/sdnplatform/sync/error/InconsistentDataException.java
new file mode 100644
index 0000000000000000000000000000000000000000..f3644b3bcacfdaa514b5eb66e806146da7c7118d
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/error/InconsistentDataException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008-2009 LinkedIn, Inc
+ * Copyright 2013 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 org.sdnplatform.sync.error;
+
+import java.util.List;
+
+/**
+ * Thrown when the inconsistency resolver fails to resolve down to a single
+ * value
+ */
+public class InconsistentDataException extends SyncException {
+
+    private static final long serialVersionUID = 1050277622160468516L;
+    List<?> unresolvedVersions;
+
+    public InconsistentDataException(String message, List<?> versions) {
+        super(message);
+        this.unresolvedVersions = versions;
+    }
+
+    public List<?> getUnresolvedVersions() {
+        return unresolvedVersions;
+    }
+    
+    @Override
+    public ErrorType getErrorCode() {
+        return ErrorType.INCONSISTENT_DATA;
+    }
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/error/ObsoleteVersionException.java b/src/main/java/org/sdnplatform/sync/error/ObsoleteVersionException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ec532abed4f0344aa46adedaa5ab3aa429ee37c
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/error/ObsoleteVersionException.java
@@ -0,0 +1,33 @@
+package org.sdnplatform.sync.error;
+
+/**
+ * This exception is thrown when attempting to write a value into a store
+ * that is older than the value already in the store.  If you get
+ * this exception, you need to redo your read/modify/write operation.
+ */
+public class ObsoleteVersionException extends SyncException {
+
+    private static final long serialVersionUID = 7128132048300845832L;
+
+    public ObsoleteVersionException() {
+        super();
+    }
+
+    public ObsoleteVersionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ObsoleteVersionException(String message) {
+        super(message);
+    }
+
+    public ObsoleteVersionException(Throwable cause) {
+        super(cause);
+    }
+    
+    @Override
+    public ErrorType getErrorCode() {
+        return ErrorType.OBSOLETE_VERSION;
+    }
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/error/PersistException.java b/src/main/java/org/sdnplatform/sync/error/PersistException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d557e8fe29c108d6ea0da77d6d6454e70247a77
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/error/PersistException.java
@@ -0,0 +1,31 @@
+package org.sdnplatform.sync.error;
+
+/**
+ * An error with a persistence layer
+ * @author readams
+ *
+ */
+public class PersistException extends SyncException {
+    private static final long serialVersionUID = -1374782534201553648L;
+
+    public PersistException() {
+        super();
+    }
+
+    public PersistException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PersistException(String message) {
+        super(message);
+    }
+
+    public PersistException(Throwable cause) {
+        super(cause);
+    }
+    
+    @Override
+    public ErrorType getErrorCode() {
+        return ErrorType.PERSIST;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/error/RemoteStoreException.java b/src/main/java/org/sdnplatform/sync/error/RemoteStoreException.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6d2353417b4bbfe71809c7fbe14bf5ca4c0f21b
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/error/RemoteStoreException.java
@@ -0,0 +1,27 @@
+package org.sdnplatform.sync.error;
+
+/**
+ * An exception related to retrieving data from a remote store
+ * @author readams
+ */
+public class RemoteStoreException extends SyncException {
+
+    private static final long serialVersionUID = -8098015934951853774L;
+
+    public RemoteStoreException() {
+        super();
+    }
+
+    public RemoteStoreException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public RemoteStoreException(String message) {
+        super(message);
+    }
+
+    public RemoteStoreException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/error/SerializationException.java b/src/main/java/org/sdnplatform/sync/error/SerializationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..dcf0524cf929eeb11e489543a0b0fb66eabe2c93
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/error/SerializationException.java
@@ -0,0 +1,31 @@
+package org.sdnplatform.sync.error;
+
+/**
+ * An error occurred while serializing or deserializing objects
+ * @author readams
+ */
+public class SerializationException extends SyncException {
+
+    private static final long serialVersionUID = 6633759330354187L;
+
+    public SerializationException() {
+        super();
+    }
+
+    public SerializationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public SerializationException(String message) {
+        super(message);
+    }
+
+    public SerializationException(Throwable cause) {
+        super(cause);
+    }
+    
+    @Override
+    public ErrorType getErrorCode() {
+        return ErrorType.SERIALIZATION;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/error/SyncException.java b/src/main/java/org/sdnplatform/sync/error/SyncException.java
new file mode 100644
index 0000000000000000000000000000000000000000..a364dae30d451a2ad111873e6792656ce0207232
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/error/SyncException.java
@@ -0,0 +1,75 @@
+package org.sdnplatform.sync.error;
+
+/**
+ * Generic exception type for sync service exceptions
+ * @author readams
+ */
+public class SyncException extends Exception {
+
+    private static final long serialVersionUID = -6150348258087759055L;
+
+    public enum ErrorType {
+        SUCCESS(0),
+        GENERIC(1),
+        INCONSISTENT_DATA(2),
+        OBSOLETE_VERSION(3),
+        UNKNOWN_STORE(4),
+        SERIALIZATION(5),
+        PERSIST(6),
+        HANDSHAKE_TIMEOUT(7),
+        REMOTE_STORE(8);
+        
+        private final int value;
+        
+        ErrorType(int value) {
+            this.value = value;
+        }
+        
+        public int getValue() {
+            return value;
+        }
+    }
+    
+    public SyncException() {
+        super();
+    }
+
+    public SyncException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public SyncException(String message) {
+        super(message);
+    }
+
+    public SyncException(Throwable cause) {
+        super(cause);
+    }
+    
+    public ErrorType getErrorCode() {
+        return ErrorType.GENERIC;
+    }
+    
+    public static SyncException newInstance(ErrorType type,
+                                            String message, Throwable cause) {
+        switch (type) {
+            case INCONSISTENT_DATA:
+                return new InconsistentDataException(message, null);
+            case OBSOLETE_VERSION:
+                return new ObsoleteVersionException(message, cause);
+            case UNKNOWN_STORE:
+                return new UnknownStoreException(message, cause);
+            case SERIALIZATION:
+                return new SerializationException(message, cause);
+            case PERSIST:
+                return new PersistException(message, cause);
+            case HANDSHAKE_TIMEOUT:
+                return new HandshakeTimeoutException();
+            case REMOTE_STORE:
+                return new RemoteStoreException(message, cause);
+            case GENERIC:
+            default:
+                return new SyncException(message, cause);
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/error/SyncRuntimeException.java b/src/main/java/org/sdnplatform/sync/error/SyncRuntimeException.java
new file mode 100644
index 0000000000000000000000000000000000000000..54f762b9c6de156ed8c06720a3c0c9318a82902b
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/error/SyncRuntimeException.java
@@ -0,0 +1,19 @@
+package org.sdnplatform.sync.error;
+
+/**
+ * A runtime exception that wraps a SyncException.  This is thrown from
+ * standard interfaces that don't support an appropriate exceptional type.
+ * @author readams
+ */
+public class SyncRuntimeException extends RuntimeException {
+
+    private static final long serialVersionUID = -5357245946596447913L;
+
+    public SyncRuntimeException(String message, SyncException cause) {
+        super(message, cause);
+    }
+
+    public SyncRuntimeException(SyncException cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/error/UnknownStoreException.java b/src/main/java/org/sdnplatform/sync/error/UnknownStoreException.java
new file mode 100644
index 0000000000000000000000000000000000000000..055715e798e47fd2b5db59efc35eb0e959d16517
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/error/UnknownStoreException.java
@@ -0,0 +1,31 @@
+package org.sdnplatform.sync.error;
+
+/**
+ * Thrown when attempting to perform an operation on an unknown store
+ * @author readams
+ */
+public class UnknownStoreException extends SyncException {
+
+    private static final long serialVersionUID = 6633759330354187L;
+
+    public UnknownStoreException() {
+        super();
+    }
+
+    public UnknownStoreException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public UnknownStoreException(String message) {
+        super(message);
+    }
+
+    public UnknownStoreException(Throwable cause) {
+        super(cause);
+    }
+    
+    @Override
+    public ErrorType getErrorCode() {
+        return ErrorType.UNKNOWN_STORE;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/AbstractStoreClient.java b/src/main/java/org/sdnplatform/sync/internal/AbstractStoreClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d767d66836b45d451acd0cef08863a69a989459
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/AbstractStoreClient.java
@@ -0,0 +1,79 @@
+package org.sdnplatform.sync.internal;
+
+import java.util.List;
+
+import org.sdnplatform.sync.IStoreClient;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.ObsoleteVersionException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.version.VectorClock;
+
+
+public abstract class AbstractStoreClient<K,V> implements IStoreClient<K, V> {
+
+    @Override
+    public V getValue(K key) throws SyncException {
+        return getValue(key, null);
+    }
+
+    @Override
+    public V getValue(K key, V defaultValue) throws SyncException {
+        Versioned<V> val = get(key);
+        if (val == null || val.getValue() == null) return defaultValue;
+        return val.getValue();
+    }
+    
+    /**
+     * Get the versions for a key
+     * @param key the key
+     * @return the versions
+     * @throws SyncException
+     */
+    protected abstract List<IVersion> getVersions(K key) throws SyncException;
+    
+    @Override
+    public Versioned<V> get(K key) throws SyncException {
+        return get(key, null);
+    }
+
+    @Override
+    public IVersion put(K key, V value) throws SyncException {
+        List<IVersion> versions = getVersions(key);
+        Versioned<V> versioned;
+        if(versions.isEmpty())
+            versioned = Versioned.value(value, new VectorClock());
+        else if(versions.size() == 1)
+            versioned = Versioned.value(value, versions.get(0));
+        else {
+            versioned = get(key, null);
+            if(versioned == null)
+                versioned = Versioned.value(value, new VectorClock());
+            else
+                versioned.setValue(value);
+        }
+        return put(key, versioned);
+    }
+
+    @Override
+    public boolean putIfNotObsolete(K key, Versioned<V> versioned)
+            throws SyncException {
+        try {
+            put(key, versioned);
+            return true;
+        } catch (ObsoleteVersionException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public void delete(K key) throws SyncException {
+        put(key, (V)null);
+    }
+
+    @Override
+    public void delete(K key, IVersion version) throws SyncException {
+        put(key, new Versioned<V>((V)null, version));
+    }
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/AbstractSyncManager.java b/src/main/java/org/sdnplatform/sync/internal/AbstractSyncManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b20aeb1cfa7123d81ecec4a39da95d11e6ab17c
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/AbstractSyncManager.java
@@ -0,0 +1,176 @@
+package org.sdnplatform.sync.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.sdnplatform.sync.IInconsistencyResolver;
+import org.sdnplatform.sync.IStoreClient;
+import org.sdnplatform.sync.ISyncService;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.UnknownStoreException;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.store.JacksonStore;
+import org.sdnplatform.sync.internal.store.MappingStoreListener;
+import org.sdnplatform.sync.internal.util.ByteArray;
+
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+
+/**
+ * An abstract base class for modules providing {@link ISyncService}
+ * @author readams
+ */
+public abstract class AbstractSyncManager 
+    implements ISyncService, IFloodlightModule {
+
+    // ************
+    // ISyncService
+    // ************
+
+    @Override
+    public <K, V> IStoreClient<K, V> 
+        getStoreClient(String storeName, 
+                       Class<K> keyClass, 
+                       Class<V> valueClass)
+                               throws  UnknownStoreException {
+        return getStoreClient(storeName, keyClass, null, 
+                              valueClass, null, null);
+    }
+
+    @Override
+    public <K, V>IStoreClient<K, V>
+        getStoreClient(String storeName, 
+                       TypeReference<K> keyType, 
+                       TypeReference<V> valueType)
+                               throws UnknownStoreException {
+        return getStoreClient(storeName, null, keyType, 
+                              null, valueType, null);
+    }
+
+    @Override
+    public <K, V> IStoreClient<K, V>
+        getStoreClient(String storeName, 
+                       TypeReference<K> keyType, 
+                       TypeReference<V> valueType, 
+                       IInconsistencyResolver<Versioned<V>> resolver)
+                               throws UnknownStoreException {
+        return getStoreClient(storeName, null, keyType, 
+                              null, valueType, resolver);
+    }
+
+    @Override
+    public <K, V> IStoreClient<K, V>
+    getStoreClient(String storeName, 
+                   Class<K> keyClass, 
+                   Class<V> valueClass, 
+                   IInconsistencyResolver<Versioned<V>> resolver)
+                           throws UnknownStoreException {
+        return getStoreClient(storeName, keyClass, null,
+                              valueClass, null, resolver);
+    }
+
+    // *****************
+    // IFloodlightModule
+    // *****************
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l =
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(ISyncService.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(ISyncService.class, this);
+        return m;
+    }
+
+    // *******************
+    // AbstractSyncManager
+    // *******************
+    
+    /**
+     * The "real" version of getStoreClient that will be called by all
+     * the others
+     * @param storeName the store name
+     * @param keyClass the key class
+     * @param keyType the key type
+     * @param valueClass the value class
+     * @param valueType the value type
+     * @param resolver the inconsistency resolver
+     * @return a {@link DefaultStoreClient} using the given parameters.
+     * @throws UnknownStoreException
+     */
+    public <K, V> IStoreClient<K, V>
+            getStoreClient(String storeName, 
+                           Class<K> keyClass, 
+                           TypeReference<K> keyType,
+                           Class<V> valueClass, 
+                           TypeReference<V> valueType, 
+                           IInconsistencyResolver<Versioned<V>> resolver)
+                                   throws UnknownStoreException {
+        IStore<ByteArray,byte[]> store = getStore(storeName);
+        IStore<K, V> serializingStore;
+        if (valueType != null && keyType != null) {
+            serializingStore = 
+                    new JacksonStore<K, V>(store, keyType, valueType);
+        } else if (valueClass != null && keyClass != null) {
+            serializingStore = 
+                    new JacksonStore<K, V>(store, keyClass, valueClass);
+        } else {
+            throw new IllegalArgumentException("Must include type reference" +
+                    " or value class");
+        }
+
+        DefaultStoreClient<K, V> storeClient =
+                new DefaultStoreClient<K, V>(serializingStore,
+                        resolver,
+                        this,
+                        keyClass,
+                        keyType);
+        return storeClient;
+    }
+    
+    /**
+     * Get a store object corresponding to the given store name
+     * @param storeName the store name
+     * @return the {@link IStore}
+     * @throws UnknownStoreException
+     */
+    public abstract IStore<ByteArray,byte[]> getStore(String storeName)
+            throws UnknownStoreException; 
+
+    /**
+     * Get the local ID of the local node
+     * @return the node ID
+     */
+    public abstract short getLocalNodeId();
+
+    /**
+     * Add a listener to the specified store
+     * @param storeName the name of the store
+     * @param listener the listener to add
+     * @throws UnknownStoreException
+     */
+    public abstract void addListener(String storeName, 
+                                     MappingStoreListener listener) 
+            throws UnknownStoreException;
+
+    /**
+     * Shut down the sync manager.  Tear down any communicating threads
+     */
+    public abstract void shutdown();
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/Cursor.java b/src/main/java/org/sdnplatform/sync/internal/Cursor.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9fbc224bd75791a82dfb02ff787bdae29a4f1bb
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/Cursor.java
@@ -0,0 +1,49 @@
+package org.sdnplatform.sync.internal;
+
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.internal.util.ByteArray;
+
+
+public class Cursor implements
+    IClosableIterator<Entry<ByteArray, List<Versioned<byte[]>>>> {
+    private final int cursorId;
+    private final 
+        IClosableIterator<Entry<ByteArray, 
+                                List<Versioned<byte[]>>>> delegate;
+    
+    public Cursor(int cursorId,
+                  IClosableIterator<Entry<ByteArray, 
+                                          List<Versioned<byte[]>>>> delegate) {
+        super();
+        this.cursorId = cursorId;
+        this.delegate = delegate;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return delegate.hasNext();
+    }
+
+    @Override
+    public Entry<ByteArray, List<Versioned<byte[]>>> next() {
+        return delegate.next();
+    }
+
+    @Override
+    public void remove() {
+        delegate.remove();
+    }
+
+    @Override
+    public void close() {
+        delegate.close();
+    }
+    
+    public int getCursorId() {
+        return this.cursorId;
+    }        
+}
\ No newline at end of file
diff --git a/src/main/java/org/sdnplatform/sync/internal/DefaultStoreClient.java b/src/main/java/org/sdnplatform/sync/internal/DefaultStoreClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..56d000d4f74a6000de763ea3f8efa9a4e2f732b3
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/DefaultStoreClient.java
@@ -0,0 +1,191 @@
+package org.sdnplatform.sync.internal;
+
+import java.util.List;
+import java.util.Map.Entry;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IInconsistencyResolver;
+import org.sdnplatform.sync.IStoreListener;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.InconsistentDataException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.error.UnknownStoreException;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.store.MappingStoreListener;
+import org.sdnplatform.sync.internal.util.Pair;
+import org.sdnplatform.sync.internal.version.ChainedResolver;
+import org.sdnplatform.sync.internal.version.TimeBasedInconsistencyResolver;
+import org.sdnplatform.sync.internal.version.VectorClock;
+import org.sdnplatform.sync.internal.version.VectorClockInconsistencyResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Default implementation of a store client used for accessing a store
+ * locally in process.
+ * @author readams
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ */
+public class DefaultStoreClient<K, V> extends AbstractStoreClient<K, V> {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(DefaultStoreClient.class.getName());
+
+    private IStore<K, V> delegate;
+    private IInconsistencyResolver<Versioned<V>> resolver;
+    private AbstractSyncManager syncManager;
+    private Class<K> keyClass;
+    private TypeReference<K> keyType;
+
+    @SuppressWarnings("unchecked")
+    public DefaultStoreClient(IStore<K, V> delegate,
+                              IInconsistencyResolver<Versioned<V>> resolver,
+                              AbstractSyncManager syncManager,
+                              Class<K> keyClass,
+                              TypeReference<K> keyType) {
+        super();
+        this.delegate = delegate;
+        this.syncManager = syncManager;
+        this.keyClass = keyClass;
+        this.keyType = keyType;
+        
+        IInconsistencyResolver<Versioned<V>> vcir =
+                new VectorClockInconsistencyResolver<V>();
+        IInconsistencyResolver<Versioned<V>> secondary = resolver;
+        if (secondary == null)
+            secondary = new TimeBasedInconsistencyResolver<V>();
+        this.resolver = new ChainedResolver<Versioned<V>>(vcir, secondary);
+    }
+
+    // ******************
+    // IStoreClient<K,V>
+    // ******************
+
+    @Override
+    public Versioned<V> get(K key, Versioned<V> defaultValue) 
+            throws SyncException {
+        List<Versioned<V>> raw = delegate.get(key);
+        return handleGet(key, defaultValue, raw);
+    }
+
+    @Override
+    public IClosableIterator<Entry<K, Versioned<V>>> entries() throws SyncException {
+        return new StoreClientIterator(delegate.entries());
+    }
+
+    @Override
+    public IVersion put(K key, Versioned<V> versioned)
+            throws SyncException {
+        VectorClock vc = (VectorClock)versioned.getVersion();
+
+        vc = vc.incremented(syncManager.getLocalNodeId(),
+                            System.currentTimeMillis());
+        versioned = Versioned.value(versioned.getValue(), vc);
+
+        delegate.put(key, versioned);
+        return versioned.getVersion();
+    }
+
+    @Override
+    public void addStoreListener(IStoreListener<K> listener) {
+        if (listener == null)
+            throw new IllegalArgumentException("Must include listener");
+        MappingStoreListener msl = 
+                new MappingStoreListener(keyType, keyClass, listener);
+        try {
+            syncManager.addListener(delegate.getName(), msl);
+        } catch (UnknownStoreException e) {
+            // this shouldn't happen since we already have a store client,
+            // unless the store has been deleted somehow
+            logger.error("Unexpected internal state: unknown store " +
+                         "from store client.  Could not register listener", e);
+        }
+    }
+
+    // ************************
+    // AbstractStoreClient<K,V>
+    // ************************
+
+    @Override
+    protected List<IVersion> getVersions(K key) throws SyncException {
+        return delegate.getVersions(key);
+    }
+
+    // *********************
+    // Private local methods
+    // *********************
+
+    protected Versioned<V> handleGet(K key,
+                                     Versioned<V> defaultValue,
+                                     List<Versioned<V>> raw) 
+                                             throws InconsistentDataException {
+        if (raw == null) return defaultValue(defaultValue);
+        List<Versioned<V>> vs = resolver.resolveConflicts(raw);
+        return getItemOrThrow(key, defaultValue, vs);
+    }
+    
+    protected Versioned<V> defaultValue(Versioned<V> defaultValue) {
+        if (defaultValue == null)
+            return Versioned.emptyVersioned();
+        return defaultValue;
+    }
+    
+    protected Versioned<V> getItemOrThrow(K key,
+                                          Versioned<V> defaultValue,
+                                          List<Versioned<V>> items)
+             throws InconsistentDataException {
+        if(items.size() == 0)
+            return defaultValue(defaultValue);
+        else if(items.size() == 1)
+            return items.get(0);
+        else
+            throw new InconsistentDataException("Resolver failed to resolve" +
+                    " conflict: " + items.size() + " unresolved items", items);
+    }
+    
+    
+    protected class StoreClientIterator implements 
+        IClosableIterator<Entry<K, Versioned<V>>> {
+
+        IClosableIterator<Entry<K, List<Versioned<V>>>> delegate;
+        
+        public StoreClientIterator(IClosableIterator<Entry<K, 
+                                   List<Versioned<V>>>> delegate) {
+            super();
+            this.delegate = delegate;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return delegate.hasNext();
+        }
+
+        @Override
+        public Entry<K, Versioned<V>> next() {
+            Entry<K, List<Versioned<V>>> n = delegate.next();
+            try {
+                return new Pair<K, Versioned<V>>(n.getKey(),
+                                      handleGet(n.getKey(), null, n.getValue()));
+            } catch (SyncException e) {
+                logger.error("Failed to construct next value", e);
+                return null;
+            }
+        }
+
+        @Override
+        public void remove() {
+            delegate.remove();
+        }
+
+        @Override
+        public void close() {
+            delegate.close();
+        }
+        
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/StoreRegistry.java b/src/main/java/org/sdnplatform/sync/internal/StoreRegistry.java
new file mode 100644
index 0000000000000000000000000000000000000000..a23b1f75ded060ebce9b6a0b7604543944c77c84
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/StoreRegistry.java
@@ -0,0 +1,280 @@
+package org.sdnplatform.sync.internal;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.sql.ConnectionPoolDataSource;
+
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.ISyncService.Scope;
+import org.sdnplatform.sync.error.PersistException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.store.IStorageEngine;
+import org.sdnplatform.sync.internal.store.InMemoryStorageEngine;
+import org.sdnplatform.sync.internal.store.JavaDBStorageEngine;
+import org.sdnplatform.sync.internal.store.SynchronizingStorageEngine;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Manage registered stores and associated metadata
+ * @author readams
+ */
+public class StoreRegistry {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(StoreRegistry.class);
+
+    /**
+     * The associated syncManager
+     */
+    private final SyncManager syncManager;
+
+    /**
+     * A data source suitable for use in persistent stores
+     */
+    private ConnectionPoolDataSource persistentDataSource = 
+            JavaDBStorageEngine.getDataSource(false); 
+
+    /**
+     * The storage engines that contain the locally-stored data
+     */
+    private HashMap<String,SynchronizingStorageEngine> localStores =
+            new HashMap<String, SynchronizingStorageEngine>();
+
+    /**
+     * Undelivered hints associated with the stores
+     */
+    private InMemoryStorageEngine<HintKey,byte[]> hints;
+    
+    /**
+     * A queue containing pending hints.  
+     */
+    private ArrayDeque<HintKey> hintQueue = new ArrayDeque<HintKey>();
+    private Lock hintLock = new ReentrantLock();
+    private Condition hintsAvailable = hintLock.newCondition();
+    
+    /**
+     * Construct a new {@link StoreRegistry}
+     * @param syncManager The associated syncManager
+     */
+    public StoreRegistry(SyncManager syncManager) {
+        super();
+        this.syncManager = syncManager;
+        hints = new InMemoryStorageEngine<HintKey, byte[]>("system-hints");
+    }
+    
+    // **************
+    // public methods
+    // **************
+
+    /**
+     * Get the store associated with the given name, or null if there is no
+     * such store
+     * @param storeName
+     * @return a {@link SynchronizingStorageEngine}
+     */
+    public SynchronizingStorageEngine get(String storeName) {
+        return localStores.get(storeName);
+    }
+
+    /**
+     * Register a new store with the given name, scope and persistence
+     * @param storeName the name of the store
+     * @param scope the scope for the store
+     * @param persistent whether the store should be persistent
+     * @return the newly-allocated store
+     * @throws PersistException 
+     */
+    public synchronized SynchronizingStorageEngine register(String storeName, 
+                                                            Scope scope, 
+                                                            boolean persistent) 
+                                              throws PersistException {
+        SynchronizingStorageEngine store =
+                localStores.get(storeName);
+        if (store != null) {
+            return store;
+        }
+                
+        IStorageEngine<ByteArray, byte[]> dstore;
+        if (persistent) {
+            dstore = new JavaDBStorageEngine(storeName, persistentDataSource);
+        } else {
+            dstore = new InMemoryStorageEngine<ByteArray, byte[]>(storeName);
+        }
+        store = new SynchronizingStorageEngine(dstore, syncManager,
+                                               syncManager.debugCounter,
+                                               scope);
+        localStores.put(storeName, store);
+        return store;
+    }
+
+    /**
+     * Get a collection containing all the currently-registered stores
+     * @return the {@link Collection<SynchronizingStorageEngine>}
+     */
+    public Collection<SynchronizingStorageEngine> values() {
+        return localStores.values();
+    }
+    
+    /**
+     * Add a key/value to the hint store for the given store
+     * @param storeName the name of the store for the keyed value
+     * @param key the key
+     * @param value the value
+     */
+    @LogMessageDoc(level="ERROR",
+                   message="Failed to queue hint for store {storeName}",
+                   explanation="There was an error synchronizing data to " + 
+                               "remote nodes",
+                   recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    public void queueHint(String storeName, 
+                          ByteArray key, Versioned<byte[]> value) {
+        try {
+            HintKey hk = new HintKey(storeName,key);
+            hintLock.lock();
+            try {
+                boolean needed = !hints.containsKey(hk);
+                needed &= hints.doput(hk, value);
+                if (needed) {
+                    hintQueue.add(hk);
+                    hintsAvailable.signal();
+                }
+            } finally {
+                hintLock.unlock();
+            }
+        } catch (SyncException e) {
+            logger.error("Failed to queue hint for store " + storeName, e);
+        }
+    }
+
+    /**
+     * Drain up to the given number of hints to the provided collection.
+     * This method will block until at least one hint is available
+     * @param c the collection to which the hints should be copied
+     * @param maxElements the maximum number of hints to drain
+     * @throws InterruptedException
+     */
+    public void takeHints(Collection<Hint> c, int maxElements) 
+            throws InterruptedException {
+        int count = 0;
+        try {
+            while (count == 0) {
+                hintLock.lock();
+                while (hintQueue.isEmpty()) {
+                    hintsAvailable.await();
+                }
+                while (count < maxElements && !hintQueue.isEmpty()) {
+                    HintKey hintKey = hintQueue.pollFirst();
+                    if (hintKey != null) {
+                        List<Versioned<byte[]>> values = hints.remove(hintKey);
+                        if (values == null) {
+                            continue;
+                        }
+                        c.add(new Hint(hintKey, values));
+                        count += 1;
+                    }
+                }
+            }
+        } finally {
+            hintLock.unlock();
+        }
+    }
+
+    public void shutdown() {
+        hintQueue.clear();
+        hints.close();
+    }
+
+    /**
+     * A key in the hint store
+     * @author readams
+     */
+    public static class HintKey {
+        private final String storeName;
+        private final ByteArray key;
+        private final short nodeId;
+        
+        public HintKey(String storeName, 
+                       ByteArray key,
+                       short nodeId) {
+            super();
+            this.storeName = storeName;
+            this.key = key;
+            this.nodeId = nodeId;
+        }
+
+        public HintKey(String storeName, 
+                       ByteArray key) {
+            super();
+            this.storeName = storeName;
+            this.key = key;
+            this.nodeId = -1;
+        }
+        
+        public String getStoreName() {
+            return storeName;
+        }
+        public ByteArray getKey() {
+            return key;
+        }
+        public short getNodeId() {
+            return nodeId;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((key == null) ? 0 : key.hashCode());
+            result = prime * result + nodeId;
+            result = prime * result
+                     + ((storeName == null) ? 0 : storeName.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;
+            HintKey other = (HintKey) obj;
+            if (key == null) {
+                if (other.key != null) return false;
+            } else if (!key.equals(other.key)) return false;
+            if (nodeId != other.nodeId) return false;
+            if (storeName == null) {
+                if (other.storeName != null) return false;
+            } else if (!storeName.equals(other.storeName)) return false;
+            return true;
+        }
+    }
+    
+    /**
+     * A hint representing a hint key and a value
+     * @author readams
+     */
+    public static class Hint {
+        private HintKey hintKey;
+        private List<Versioned<byte[]>> values;
+        public Hint(HintKey hintKey, List<Versioned<byte[]>> values) {
+            super();
+            this.hintKey = hintKey;
+            this.values = values;
+        }
+        public HintKey getHintKey() {
+            return hintKey;
+        }
+        public List<Versioned<byte[]>> getValues() {
+            return values;
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/SyncManager.java b/src/main/java/org/sdnplatform/sync/internal/SyncManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..50098da72c37898d499bb164ed353df9c6dbc4c7
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/SyncManager.java
@@ -0,0 +1,807 @@
+package org.sdnplatform.sync.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.ISyncService;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.IVersion.Occurred;
+import org.sdnplatform.sync.error.PersistException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.error.SyncRuntimeException;
+import org.sdnplatform.sync.error.UnknownStoreException;
+import org.sdnplatform.sync.internal.StoreRegistry.Hint;
+import org.sdnplatform.sync.internal.config.ClusterConfig;
+import org.sdnplatform.sync.internal.config.DelegatingCCProvider;
+import org.sdnplatform.sync.internal.config.FallbackCCProvider;
+import org.sdnplatform.sync.internal.config.IClusterConfigProvider;
+import org.sdnplatform.sync.internal.config.Node;
+import org.sdnplatform.sync.internal.config.PropertyCCProvider;
+import org.sdnplatform.sync.internal.config.StorageCCProvider;
+import org.sdnplatform.sync.internal.rpc.RPCService;
+import org.sdnplatform.sync.internal.rpc.TProtocolUtil;
+import org.sdnplatform.sync.internal.store.IStorageEngine;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.store.MappingStoreListener;
+import org.sdnplatform.sync.internal.store.SynchronizingStorageEngine;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.internal.version.VectorClock;
+import org.sdnplatform.sync.thrift.SyncMessage;
+import org.sdnplatform.sync.thrift.KeyedValues;
+import org.sdnplatform.sync.thrift.KeyedVersions;
+import org.sdnplatform.sync.thrift.SyncOfferMessage;
+import org.sdnplatform.sync.thrift.SyncValueMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.IFloodlightService;
+import net.floodlightcontroller.core.util.SingletonTask;
+import net.floodlightcontroller.debugcounter.IDebugCounterService;
+import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterType;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+
+/**
+ * Implementation for {@link ISyncService} that keeps local copies of the data
+ * and will synchronize it to other nodes in the cluster
+ * @author readams
+ * @see ISyncService
+ */
+@LogMessageCategory("State Synchronization")
+public class SyncManager extends AbstractSyncManager {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(SyncManager.class.getName());
+
+    protected IThreadPoolService threadPool;
+    protected IDebugCounterService debugCounter;
+    
+    /**
+     * The store registry holds the storage engines that provide 
+     * access to the data
+     */
+    private StoreRegistry storeRegistry = new StoreRegistry(this);
+    
+    private IClusterConfigProvider clusterConfigProvider;
+    private ClusterConfig clusterConfig = new ClusterConfig();
+
+    protected RPCService rpcService = null;
+    
+    /**
+     * Interval between cleanup tasks in seconds
+     */
+    private static final int CLEANUP_INTERVAL = 60 * 60;
+
+    /**
+     * Interval between antientropy tasks in seconds
+     */
+    private static final int ANTIENTROPY_INTERVAL = 5 * 60;
+
+    /**
+     * Interval between configuration rescans
+     */
+    private static final int CONFIG_RESCAN_INTERVAL = 10;
+    
+    /**
+     * Task for performing periodic maintenance/cleanup on local stores
+     */
+    private SingletonTask cleanupTask;
+    
+    /**
+     * Task for periodic antientropy between nodes
+     */
+    private SingletonTask antientropyTask;
+
+    /**
+     * Task to periodically rescan configuration
+     */
+    private SingletonTask updateConfigTask;
+    
+    /**
+     * Number of {@link HintWorker} workers used to drain the queue of writes 
+     * that need to be sent to the connected nodes
+     */
+    private static final int SYNC_WORKER_POOL = 2;
+    
+    /**
+     * A thread pool for the {@link HintWorker} threads.
+     */
+    private ExecutorService hintThreadPool;
+
+    /**
+     * Random number generator
+     */
+    private Random random = new Random();
+
+    /**
+     * A map of the currently-allocated cursors
+     */
+    private Map<Integer, Cursor> cursorMap = 
+            new ConcurrentHashMap<Integer, Cursor>(); 
+    
+    private static final String PACKAGE = 
+            ISyncService.class.getPackage().getName();
+    public static final String COUNTER_HINTS = PACKAGE + "-hints";
+    public static final String COUNTER_SENT_VALUES = PACKAGE + "-sent_values";
+    public static final String COUNTER_RECEIVED_VALUES = 
+            PACKAGE + "-received_values";
+    public static final String COUNTER_PUTS = PACKAGE + "-puts";
+    public static final String COUNTER_GETS = PACKAGE + "-gets";
+    public static final String COUNTER_ITERATORS = PACKAGE + "-iterators";
+
+    // ************
+    // ISyncService
+    // ************
+
+    @Override
+    public void registerStore(String storeName, Scope scope) {
+        try {
+            storeRegistry.register(storeName, scope, false);
+        } catch (PersistException e) {
+            // not possible
+            throw new SyncRuntimeException(e);
+        }
+    }
+
+    @Override
+    public void registerPersistentStore(String storeName, Scope scope) 
+            throws PersistException {
+        storeRegistry.register(storeName, scope, true);
+    }
+
+    // **************************
+    // SyncManager public methods
+    // **************************
+
+    /**
+     * Get the cluster configuration object
+     * @return the {@link ClusterConfig} object
+     * @see ClusterConfig
+     */
+    public ClusterConfig getClusterConfig() {
+        return clusterConfig;
+    }
+
+    /**
+     * Perform periodic scheduled cleanup.  Note that this will be called
+     * automatically and you shouldn't generally call it directly except for
+     * testing
+     * @throws SyncException 
+     */
+    public void cleanup() throws SyncException {
+        for (SynchronizingStorageEngine store : storeRegistry.values()) {
+            store.cleanupTask();                
+        }
+    }
+
+    /**
+     * Perform a synchronization with the node specified
+     */
+    @LogMessageDoc(level="INFO",
+                   message="[{id}->{id}] Synchronizing local state to remote node",
+                   explanation="Normal state resynchronization is occurring")
+    public void antientropy(Node node) {
+        if (!rpcService.isConnected(node.getNodeId())) return;
+
+        logger.info("[{}->{}] Synchronizing local state to remote node", 
+                    getLocalNodeId(), node.getNodeId());
+
+        for (SynchronizingStorageEngine store : storeRegistry.values()) {
+            if (Scope.LOCAL.equals(store.getScope())) {
+                if (node.getDomainId() != 
+                        getClusterConfig().getNode().getDomainId())
+                    continue;
+            }
+            
+            IClosableIterator<Entry<ByteArray, 
+                                  List<Versioned<byte[]>>>> entries = 
+                    store.entries();
+            try {
+                SyncMessage bsm = 
+                        TProtocolUtil.getTSyncOfferMessage(store.getName(), 
+                                                           store.getScope(),
+                                                           store.isPersistent());
+                int count = 0;
+                while (entries.hasNext()) {
+                    if (!rpcService.isConnected(node.getNodeId())) return;
+
+                    Entry<ByteArray, List<Versioned<byte[]>>> pair = 
+                            entries.next();
+                    KeyedVersions kv = 
+                            TProtocolUtil.getTKeyedVersions(pair.getKey(), 
+                                                            pair.getValue());
+                    bsm.getSyncOffer().addToVersions(kv);
+                    count += 1;
+                    if (count >= 50) {
+                        sendSyncOffer(node.getNodeId(), bsm);
+                        bsm.getSyncOffer().unsetVersions();
+                        count = 0;
+                    }
+                }
+                sendSyncOffer(node.getNodeId(), bsm);
+            } catch (InterruptedException e) {
+                // This can't really happen
+                throw new RuntimeException(e);
+            } finally {
+                entries.close();
+            }
+        }
+    }
+
+    /**
+     * Communicate with a random node and do a full synchronization of the
+     * all the stores on each node that have the appropriate scope.
+     */
+    public void antientropy() {
+        ArrayList<Node> candidates = new ArrayList<Node>();
+        for (Node n : clusterConfig.getNodes())
+            if (rpcService.isConnected(n.getNodeId()))
+                candidates.add(n);
+
+        int numNodes = candidates.size();
+        if (numNodes == 0) return;
+        Node[] nodes = candidates.toArray(new Node[numNodes]);
+        int rn = random.nextInt(numNodes);
+        antientropy(nodes[rn]);
+    }
+    
+    /**
+     * Write a value synchronized from another node, bypassing some of the 
+     * usual logic when a client writes data.  If the store is not known,
+     * this will automatically register it
+     * @param storeName the store name
+     * @param scope the scope for the store
+     * @param persist TODO
+     * @param key the key to write
+     * @param values a list of versions for the key to write
+     * @throws PersistException 
+     */
+    public void writeSyncValue(String storeName, Scope scope,
+                               boolean persist,
+                               byte[] key, Iterable<Versioned<byte[]>> values) 
+                                       throws PersistException {
+        SynchronizingStorageEngine store = storeRegistry.get(storeName);
+        if (store == null) {
+            store = storeRegistry.register(storeName, scope, persist);
+        }
+        store.writeSyncValue(new ByteArray(key), values);
+    }
+
+    /**
+     * Check whether any of the specified versions for the key are not older
+     * than the versions we already have
+     * @param storeName the store to check
+     * @param key the key to check
+     * @param versions an iterable over the versions
+     * @return true if we'd like a copy of the data indicated
+     * @throws SyncException
+     */
+    public boolean handleSyncOffer(String storeName,
+                                   byte[] key,
+                                   Iterable<VectorClock> versions) 
+                                           throws SyncException {
+        SynchronizingStorageEngine store = storeRegistry.get(storeName);
+        if (store == null) return true;
+        
+        List<Versioned<byte[]>> values = store.get(new ByteArray(key));
+        if (values == null || values.size() == 0) return true;
+
+        // check whether any of the versions are not older than what we have
+        for (VectorClock vc : versions) {
+            for (Versioned<byte[]> value : values) {
+                VectorClock existingVc = (VectorClock)value.getVersion();
+                if (!vc.compare(existingVc).equals(Occurred.BEFORE))
+                    return true;
+            }
+        }
+        
+        return false;
+    }
+
+    /**
+     * Get access to the raw storage engine.  This is useful for some 
+     * on-the-wire communication
+     * @param storeName the store name to get
+     * @return the {@link IStorageEngine}
+     * @throws UnknownStoreException
+     */
+    public IStorageEngine<ByteArray, byte[]> getRawStore(String storeName) 
+            throws UnknownStoreException {
+        return getStoreInternal(storeName);
+    }
+    
+    /**
+     * Return the threadpool
+     * @return the {@link IThreadPoolService}
+     */
+    public IThreadPoolService getThreadPool() {
+        return threadPool;
+    }
+    
+    /**
+     * Queue a synchronization of the specified {@link KeyedValues} to all nodes
+     * assocatiated with the storage engine specified
+     * @param e the storage engine for the values
+     * @param kv the values to synchronize
+     */
+    @LogMessageDoc(level="WARN",
+            message="Sync task queue full and not emptying",
+            explanation="The synchronization service is overloaded",
+            recommendation=LogMessageDoc.CHECK_CONTROLLER)
+    public void queueSyncTask(SynchronizingStorageEngine e, 
+                              ByteArray key, Versioned<byte[]> value) {
+        storeRegistry.queueHint(e.getName(), key, value);
+    }
+    
+    @Override
+    public void addListener(String storeName, MappingStoreListener listener) 
+            throws UnknownStoreException {
+        SynchronizingStorageEngine store = getStoreInternal(storeName);
+        store.addListener(listener);
+    }
+    
+    /**
+     * Update the node configuration to add or remove nodes
+     * @throws FloodlightModuleException 
+     */
+    @LogMessageDocs({
+        @LogMessageDoc(level="INFO",
+                message="Updating sync configuration {config}",
+                explanation="The sync service cluster configuration has been updated"),
+        @LogMessageDoc(level="INFO",
+                message="Local node configuration changed; restarting sync" +
+                        "service",
+                explanation="The sync service must be restarted to update its configuration")
+    })
+    public void updateConfiguration()
+            throws FloodlightModuleException {
+        
+        try {
+            ClusterConfig oldConfig = clusterConfig;
+            clusterConfig = clusterConfigProvider.getConfig();
+            if (clusterConfig.equals(oldConfig)) return;
+
+            logger.info("Updating sync configuration {}", clusterConfig);
+            if (oldConfig.getNode() != null &&
+                !clusterConfig.getNode().equals(oldConfig.getNode())) {
+                logger.info("Local node configuration changed; restarting sync" +
+                        "service");
+                shutdown();
+                startUp(null);
+            }
+
+            for (Node n : clusterConfig.getNodes()) {
+                Node existing = oldConfig.getNode(n.getNodeId());
+                if (existing != null && !n.equals(existing)) {
+                    // we already had this node's configuration, but it's
+                    // changed.  Disconnect from the node and let it
+                    // reinitialize
+                    logger.debug("[{}->{}] Configuration for node has changed",
+                                 getLocalNodeId(), n.getNodeId());
+                    rpcService.disconnectNode(n.getNodeId());
+                }
+            }
+            for (Node n : oldConfig.getNodes()) {
+                Node nn = clusterConfig.getNode(n.getNodeId());
+                if (nn == null) {
+                    // n is a node that doesn't appear in the new config
+                    logger.debug("[{}->{}] Disconnecting deconfigured node",
+                                 getLocalNodeId(), n.getNodeId());
+                    rpcService.disconnectNode(n.getNodeId());
+                }
+            }
+        } catch (Exception e) {
+            throw new FloodlightModuleException("Could not update " +
+                                                "configuration", e);
+        }
+    }
+
+    /**
+     * Retrieve the cursor, if any, for the given cursor ID
+     * @param cursorId the cursor ID
+     * @return the {@link Cursor}
+     */
+    public Cursor getCursor(int cursorId) {
+        return cursorMap.get(Integer.valueOf(cursorId));
+    }
+
+    /**
+     * Allocate a new cursor for the given store name
+     * @param storeName the store name
+     * @return the {@link Cursor}
+     * @throws SyncException
+     */
+    public Cursor newCursor(String storeName) throws UnknownStoreException {
+        IStore<ByteArray, byte[]> store = getStore(storeName);
+        int cursorId = rpcService.getTransactionId();
+        Cursor cursor = new Cursor(cursorId, store.entries());
+        cursorMap.put(Integer.valueOf(cursorId), cursor);
+        return cursor;
+    }
+    
+    /**
+     * Close the given cursor and remove it from the map
+     * @param cursor the cursor to close
+     */
+    public void closeCursor(Cursor cursor) {
+        cursor.close();
+        cursorMap.remove(Integer.valueOf(cursor.getCursorId()));
+    }
+    
+    // *******************
+    // AbstractSyncManager
+    // *******************
+
+    @Override
+    public IStore<ByteArray,byte[]> getStore(String storeName) 
+            throws UnknownStoreException {
+        return getRawStore(storeName);
+    }
+    
+    @Override
+    public short getLocalNodeId() {
+        return clusterConfig.getNode().getNodeId();
+    }
+
+    @Override
+    public void shutdown() {
+        logger.debug("Shutting down Sync Manager: {} {}",
+                     clusterConfig.getNode().getHostname(),
+                     clusterConfig.getNode().getPort());
+
+        if (rpcService != null) {
+            rpcService.shutdown();
+        }
+        if (hintThreadPool != null) {
+            hintThreadPool.shutdown();
+        }
+        if (storeRegistry != null) {
+            storeRegistry.shutdown();
+        }
+        hintThreadPool = null;
+        rpcService = null;
+    }
+
+    // *****************
+    // IFloodlightModule
+    // *****************
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        threadPool = context.getServiceImpl(IThreadPoolService.class);
+        debugCounter = context.getServiceImpl(IDebugCounterService.class);
+        Map<String, String> config = context.getConfigParams(this);
+
+        String[] configProviders =
+             {StorageCCProvider.class.getName(),
+              PropertyCCProvider.class.getName(),
+              FallbackCCProvider.class.getName()};
+        try {
+
+            if (config.containsKey("configProviders")) {
+                configProviders = config.get("configProviders").split(",");
+            }
+            DelegatingCCProvider dprovider = new DelegatingCCProvider();
+            for (String configProvider : configProviders) {
+                Class<?> cClass = Class.forName(configProvider);
+                IClusterConfigProvider provider =
+                        (IClusterConfigProvider) cClass.newInstance();
+                dprovider.addProvider(provider);
+            }
+            dprovider.init(this, context);
+            clusterConfigProvider = dprovider;
+        } catch (Exception e) {
+            throw new FloodlightModuleException("Could not instantiate config" +
+                    "providers " + Arrays.toString(configProviders), e);
+        }
+
+        String manualStoreString = config.get("manualStores");
+        if (manualStoreString != null) {
+            List<String> manualStores = null;
+            try {
+                manualStores = 
+                        (new ObjectMapper()).readValue(manualStoreString, 
+                                         new TypeReference<List<String>>() {});
+            } catch (Exception e) {
+                throw new FloodlightModuleException("Failed to parse sync " +
+                        "manager manual stores: " + manualStoreString, e);
+            }
+            for (String s : manualStores) {
+                registerStore(s, Scope.GLOBAL);
+            }
+        }
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) 
+            throws FloodlightModuleException {
+        debugCounter.registerCounter(COUNTER_HINTS,
+                                     "Queued sync events processed",
+                                     CounterType.ALWAYS_COUNT);
+        debugCounter.registerCounter(COUNTER_SENT_VALUES,
+                                     "Values synced to remote node",
+                                     CounterType.ALWAYS_COUNT);
+        debugCounter.registerCounter(COUNTER_RECEIVED_VALUES,
+                                     "Values received from remote node",
+                                     CounterType.ALWAYS_COUNT);
+        debugCounter.registerCounter(COUNTER_PUTS,
+                                     "Local puts to store",
+                                     CounterType.ALWAYS_COUNT);        
+        debugCounter.registerCounter(COUNTER_GETS,
+                                     "Local gets from store",
+                                     CounterType.ALWAYS_COUNT);     
+        debugCounter.registerCounter(COUNTER_ITERATORS,
+                                     "Local iterators created over store",
+                                     CounterType.ALWAYS_COUNT);     
+        
+        updateConfiguration();
+        rpcService = new RPCService(this, debugCounter);
+        rpcService.run();
+
+        cleanupTask = new SingletonTask(threadPool.getScheduledExecutor(), 
+                                        new CleanupTask());
+        cleanupTask.reschedule(CLEANUP_INTERVAL + 
+                               random.nextInt(30), TimeUnit.SECONDS);
+        
+        antientropyTask = new SingletonTask(threadPool.getScheduledExecutor(),
+                                       new AntientropyTask());
+        antientropyTask.reschedule(ANTIENTROPY_INTERVAL + 
+                                   random.nextInt(30), TimeUnit.SECONDS);
+
+        updateConfigTask =
+                new SingletonTask(threadPool.getScheduledExecutor(),
+                                  new UpdateConfigTask());
+        updateConfigTask.reschedule(CONFIG_RESCAN_INTERVAL, TimeUnit.SECONDS);
+
+        final ThreadGroup tg = new ThreadGroup("Hint Workers");
+        tg.setMaxPriority(Thread.NORM_PRIORITY - 2);
+        ThreadFactory f = new ThreadFactory() {
+            AtomicInteger id = new AtomicInteger();
+            
+            @Override
+            public Thread newThread(Runnable runnable) {
+                return new Thread(tg, runnable, 
+                                  "HintWorker-" + id.getAndIncrement());
+            }
+        };
+        hintThreadPool = Executors.newCachedThreadPool(f);
+        for (int i = 0; i < SYNC_WORKER_POOL; i++) {
+            hintThreadPool.execute(new HintWorker());
+        }
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l =
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IThreadPoolService.class);
+        l.add(IStorageSourceService.class);
+        l.add(IDebugCounterService.class);
+        return l;
+    }
+
+    // ***************
+    // Local methods
+    // ***************
+
+    protected SynchronizingStorageEngine getStoreInternal(String storeName) 
+            throws UnknownStoreException {
+        SynchronizingStorageEngine store = storeRegistry.get(storeName);
+        if (store == null) {
+            throw new UnknownStoreException("Store " + storeName + 
+                                            " has not been registered");
+        }
+        return store;
+    }
+    
+    private void sendSyncOffer(short nodeId, SyncMessage bsm) 
+            throws InterruptedException {
+        SyncOfferMessage som = bsm.getSyncOffer();
+        if (!som.isSetVersions()) return;
+        if (logger.isTraceEnabled()) {
+            logger.trace("[{}->{}] Sending SyncOffer with {} elements", 
+                         new Object[]{getLocalNodeId(), nodeId, 
+                                      som.getVersionsSize()});
+        }
+
+        som.getHeader().setTransactionId(rpcService.getTransactionId());
+        rpcService.writeToNode(nodeId, bsm);
+    }
+    
+    /**
+     * Periodically perform cleanup
+     * @author readams
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Cleanup task failed",
+            explanation="Failed to clean up deleted data in the store",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    protected class CleanupTask implements Runnable {
+        @Override
+        public void run() {
+            try {
+                if (rpcService != null)
+                    cleanup();
+            } catch (Exception e) {
+                logger.error("Cleanup task failed", e);
+            }
+
+            if (rpcService != null) {
+                cleanupTask.reschedule(CLEANUP_INTERVAL + 
+                                       random.nextInt(30), TimeUnit.SECONDS);
+            }
+        }
+    }
+
+    /**
+     * Periodically perform antientropy
+     * @author readams
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Antientropy task failed",
+            explanation="Failed to synchronize state between two nodes",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    protected class AntientropyTask implements Runnable {
+        @Override
+        public void run() {
+            try {
+                if (rpcService != null)
+                    antientropy();
+            } catch (Exception e) {
+                logger.error("Antientropy task failed", e);
+            }
+
+            if (rpcService != null) {
+                antientropyTask.reschedule(ANTIENTROPY_INTERVAL + 
+                                           random.nextInt(30), 
+                                           TimeUnit.SECONDS);
+            }
+        }
+    }
+    
+    /**
+     * Worker task to periodically rescan the configuration
+     * @author readams
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Failed to update configuration",
+            explanation="An error occured while updating sync service configuration",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    protected class UpdateConfigTask implements Runnable {
+        @Override
+        public void run() {
+            try {
+                if (rpcService != null)
+                    updateConfiguration();
+            } catch (Exception e) {
+                logger.error("Failed to update configuration", e);
+            }
+            if (rpcService != null) {
+                updateConfigTask.reschedule(CONFIG_RESCAN_INTERVAL, 
+                                            TimeUnit.SECONDS);
+            }
+        }
+    }
+    
+    /**
+     * Worker thread that will drain the sync item queue and write the
+     * appropriate messages to the node I/O channels
+     * @author readams
+     */
+    @LogMessageDoc(level="ERROR",
+            message="Error occured in synchronization worker",
+            explanation="Failed to synchronize state to remote node",
+            recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    protected class HintWorker implements Runnable {
+        ArrayList<Hint> tasks = new ArrayList<Hint>(50);
+        protected HashMap<String, SyncMessage> messages = 
+                new HashMap<String, SyncMessage>();
+
+        @Override
+        public void run() {
+            while (rpcService != null) {
+                try {
+                    // Batch up sync tasks so we use fewer, larger messages
+                    // XXX - todo - handle hints targeted to specific nodes
+                    storeRegistry.takeHints(tasks, 50);
+                    for (Hint task : tasks) {
+                        debugCounter.updateCounter(COUNTER_HINTS);
+                        SynchronizingStorageEngine store = 
+                                storeRegistry.get(task.getHintKey().
+                                                  getStoreName());
+                        SyncMessage bsm = getMessage(store);
+                        KeyedValues kv = 
+                                TProtocolUtil.
+                                getTKeyedValues(task.getHintKey().getKey(), 
+                                                task.getValues());
+                        bsm.getSyncValue().addToValues(kv);
+                    }
+
+                    Iterable<Node> nodes = getClusterConfig().getNodes();
+                    short localDomainId = 
+                            getClusterConfig().getNode().getDomainId();
+                    short localNodeId = 
+                            getClusterConfig().getNode().getNodeId();
+                    for (Node n : nodes) {
+                        if (localNodeId == n.getNodeId())
+                            continue;
+                        for (SyncMessage bsm : messages.values()) {
+                            SyncValueMessage svm = bsm.getSyncValue();
+                            if (svm.getStore().getScope().
+                                    equals(org.sdnplatform.sync.thrift.
+                                           Scope.LOCAL) &&
+                                           n.getDomainId() != localDomainId) {
+                                // This message is only for local domain
+                                continue;
+                            }
+
+                            svm.getHeader().
+                            setTransactionId(rpcService.
+                                             getTransactionId());
+                            debugCounter.updateCounter(COUNTER_SENT_VALUES, 
+                                                       bsm.getSyncValue().
+                                                           getValuesSize());
+                            rpcService.writeToNode(n.getNodeId(), bsm);
+                        }
+                    }
+                    debugCounter.flushCounters();
+                    tasks.clear(); 
+                    clearMessages();
+
+                } catch (Exception e) {
+                    logger.error("Error occured in synchronization worker", e);
+                }
+            }
+        }
+        
+        /**
+         * Clear the current list of pending messages
+         */
+        private void clearMessages() {
+            for (SyncMessage bsm : messages.values()) {
+                bsm.getSyncValue().unsetValues();
+            }
+        }
+        
+        /**
+         * Allocate a partially-initialized {@link SyncMessage} object for 
+         * the given store
+         * @param store the store
+         * @return the {@link SyncMessage} object
+         */
+        private SyncMessage getMessage(SynchronizingStorageEngine store) {
+            String storeName = store.getName();
+            SyncMessage bsm = messages.get(storeName);
+            if (bsm == null) {
+                bsm = TProtocolUtil.getTSyncValueMessage(storeName, 
+                                                         store.getScope(),
+                                                         store.isPersistent());
+                messages.put(storeName, bsm);
+            }
+            return bsm;
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/SyncTorture.java b/src/main/java/org/sdnplatform/sync/internal/SyncTorture.java
new file mode 100644
index 0000000000000000000000000000000000000000..74e4ae96648089a28dd38758b817886a01f57f63
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/SyncTorture.java
@@ -0,0 +1,196 @@
+package org.sdnplatform.sync.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.sdnplatform.sync.IStoreClient;
+import org.sdnplatform.sync.ISyncService;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.ISyncService.Scope;
+import org.sdnplatform.sync.error.SyncException;
+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;
+import net.floodlightcontroller.debugcounter.IDebugCounterService;
+
+/**
+ * A floodlight module that will start up and start doing horrible,
+ * horrible things to the sync service.
+ * @author readams
+ */
+public class SyncTorture implements IFloodlightModule {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(SyncTorture.class);
+
+    private static final String SYNC_STORE_NAME =
+            SyncTorture.class.getCanonicalName() + ".torture";
+    
+    ISyncService syncService;
+    IDebugCounterService debugCounter;
+
+    int numWorkers = 2;
+    int keysPerWorker = 1024*1024;
+    int iterations = 0;
+    int delay = 0;
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleServices() {
+        return null;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService>
+            getServiceImpls() {
+        return null;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(ISyncService.class);
+        l.add(IDebugCounterService.class);
+
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        syncService = context.getServiceImpl(ISyncService.class);
+        debugCounter = context.getServiceImpl(IDebugCounterService.class);
+
+        try {
+            syncService.registerStore(SYNC_STORE_NAME, Scope.GLOBAL);
+        } catch (SyncException e) {
+            throw new FloodlightModuleException(e);
+        }
+        
+        Map<String,String> config = context.getConfigParams(this);
+        if (config.containsKey("numWorkers")) {
+            numWorkers = Integer.parseInt(config.get("numWorkers"));
+        }
+        if (config.containsKey("keysPerWorker")) {
+            keysPerWorker = Integer.parseInt(config.get("keysPerWorker"));
+        }
+        if (config.containsKey("iterations")) {
+            iterations = Integer.parseInt(config.get("iterations"));
+        }
+        if (config.containsKey("delay")) {
+            delay = Integer.parseInt(config.get("delay"));
+        }
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        try {
+            final IStoreClient<String, TortureValue> storeClient = 
+                    syncService.getStoreClient(SYNC_STORE_NAME, 
+                                               String.class, 
+                                               TortureValue.class);
+            for (int i = 0; i < numWorkers; i++) {
+                Thread thread = new Thread(new TortureWorker(storeClient, i),
+                                           "Torture-" + i);
+                thread.setPriority(Thread.MIN_PRIORITY);
+                thread.start();
+            }
+        } catch (Exception e) {
+            throw new FloodlightModuleException(e);
+        }
+    }
+
+    protected static class TortureValue {
+        private String string;
+        private int integer;
+        private boolean bool;
+
+        public TortureValue() {
+            super();
+        }
+
+        public TortureValue(String string, int integer, boolean bool) {
+            super();
+            this.string = string;
+            this.integer = integer;
+            this.bool = bool;
+        }
+        
+        public String getString() {
+            return string;
+        }
+        public void setString(String string) {
+            this.string = string;
+        }
+        public int getInteger() {
+            return integer;
+        }
+        public void setInteger(int integer) {
+            this.integer = integer;
+        }
+        public boolean isBool() {
+            return bool;
+        }
+        public void setBool(boolean bool) {
+            this.bool = bool;
+        }
+    }
+    
+    protected class TortureWorker implements Runnable {
+        final IStoreClient<String, TortureValue> storeClient;
+        final int workerId;
+        final List<TortureValue> values;
+
+        public TortureWorker(IStoreClient<String, TortureValue> storeClient,
+                             int workerId) {
+            super();
+            this.storeClient = storeClient;
+            this.workerId = workerId;
+            values = new ArrayList<TortureValue>();
+            for (int i = 0; i < keysPerWorker; i++) {
+                values.add(new TortureValue(workerId+":"+i, 0, true));
+            }
+        }
+
+        @Override
+        public void run() {
+            if (delay > 0) {
+                try {
+                    Thread.sleep(delay);
+                } catch (InterruptedException e) { }
+            }
+            int i = 0;
+            while (iterations == 0 || i++ < iterations) {
+                long start = System.currentTimeMillis();
+                try {
+                    for (TortureValue v : values) {
+                        Versioned<TortureValue> vv =
+                                storeClient.get(v.getString());
+                        v.setInteger(v.getInteger() + 1);
+                        v.setBool(!v.isBool());
+                        vv.setValue(v);
+                        storeClient.put(v.getString(), vv);
+                    }
+                } catch (Exception e) {
+                    logger.error("Error in worker: ", e);
+                }
+                long iterend = System.currentTimeMillis();
+                debugCounter.flushCounters();
+                logger.info("Completed iteration of {} values in {}ms" + 
+                            " ({}/s)", 
+                            new Object[]{values.size(), (iterend-start),
+                            1000*values.size()/(iterend-start)});
+            }
+            
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/config/ClusterConfig.java b/src/main/java/org/sdnplatform/sync/internal/config/ClusterConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..23b64f8428eb69e27518d42590d5a48f226101c7
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/config/ClusterConfig.java
@@ -0,0 +1,174 @@
+package org.sdnplatform.sync.internal.config;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import org.sdnplatform.sync.error.SyncException;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+
+/**
+ * Represent the configuration of a cluster in the sync manager
+ * @author readams
+ */
+public class ClusterConfig {
+    private HashMap<Short, Node> allNodes =
+            new HashMap<Short, Node>();
+    private HashMap<Short, List<Node>> localDomains =
+            new HashMap<Short, List<Node>>();
+    private Node thisNode;
+
+    public ClusterConfig() {
+        super();
+    }
+
+    /**
+     * Initialize a cluster config object using a JSON string containing
+     * the nodes
+     * @param nodeConfig the JSON-formatted cluster configurations
+     * @param thisNodeId the node ID for the current node
+     * @throws SyncException
+     */
+    public ClusterConfig(String nodeConfig,
+                         short thisNodeId) throws SyncException {
+        super();
+        ObjectMapper mapper = new ObjectMapper();
+        List<Node> nodes;
+        try {
+            nodes = mapper.readValue(nodeConfig,
+                                     new TypeReference<List<Node>>() { });
+        } catch (Exception e) {
+            throw new SyncException("Failed to initialize sync manager", e);
+        }
+        init(nodes, thisNodeId);
+    }
+
+    /**
+     * Initialize a cluster config using a list of nodes
+     * @param nodes the nodes to use
+     * @param thisNodeId the node ID for the current node
+     * @throws SyncException
+     */
+    public ClusterConfig(List<Node> nodes, short thisNodeId)
+            throws SyncException {
+        init(nodes, thisNodeId);
+    }
+
+    /**
+     * Get a collection containing all configured nodes
+     * @return the collection of nodes
+     */
+    public Collection<Node> getNodes() {
+        return Collections.unmodifiableCollection(allNodes.values());
+    }
+
+    /**
+     * A collection of the nodes in the local domain for the current node
+     * @return the list of nodes
+     */
+    public Collection<Node> getDomainNodes() {
+        return getDomainNodes(thisNode.getDomainId());
+    }
+
+    /**
+     * A collection of the nodes in the local domain specified
+     * @param domainId the domain ID
+     * @return the list of nodes
+     */
+    public Collection<Node> getDomainNodes(short domainId) {
+        List<Node> r = localDomains.get(domainId);
+        return Collections.unmodifiableCollection(r);
+    }
+
+    /**
+     * Get the {@link Node} object for the current node
+     */
+    public Node getNode() {
+        return thisNode;
+    }
+
+    /**
+     * The a list of the nodes in the local domain specified
+     * @param nodeId the node ID to retrieve
+     * @return the node (or null if there is no such node
+     */
+    public Node getNode(short nodeId) {
+        return allNodes.get(nodeId);
+    }
+
+    /**
+     * Add a new node to the cluster
+     * @param node the {@link Node} to add
+     * @throws SyncException if the node already exists
+     */
+    private void addNode(Node node) throws SyncException {
+        Short nodeId = node.getNodeId();
+        if (allNodes.get(nodeId) != null) {
+            throw new SyncException("Error adding node " + node +
+                    ": a node with that ID already exists");
+        }
+        allNodes.put(nodeId, node);
+
+        Short domainId = node.getDomainId();
+        List<Node> localDomain = localDomains.get(domainId);
+        if (localDomain == null) {
+            localDomains.put(domainId,
+                             localDomain = new ArrayList<Node>());
+        }
+        localDomain.add(node);
+    }
+
+    private void init(List<Node> nodes, short thisNodeId)
+            throws SyncException {
+        for (Node n : nodes) {
+            addNode(n);
+        }
+        thisNode = getNode(thisNodeId);
+        if (thisNode == null) {
+            throw new SyncException("Cannot set thisNode " +
+                    "node: No node with ID " + thisNodeId);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ClusterConfig [allNodes=" + allNodes + ", thisNode="
+               + thisNode.getNodeId() + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                 + ((allNodes == null) ? 0 : allNodes.hashCode());
+        result = prime * result
+                 + ((localDomains == null) ? 0 : localDomains.hashCode());
+        result = prime * result
+                 + ((thisNode == null) ? 0 : thisNode.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;
+        ClusterConfig other = (ClusterConfig) obj;
+        if (allNodes == null) {
+            if (other.allNodes != null) return false;
+        } else if (!allNodes.equals(other.allNodes)) return false;
+        if (localDomains == null) {
+            if (other.localDomains != null) return false;
+        } else if (!localDomains.equals(other.localDomains)) return false;
+        if (thisNode == null) {
+            if (other.thisNode != null) return false;
+        } else if (!thisNode.equals(other.thisNode)) return false;
+        return true;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/config/DelegatingCCProvider.java b/src/main/java/org/sdnplatform/sync/internal/config/DelegatingCCProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e793b7f68194385b46694ee9cbedefdbe28a1e4
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/config/DelegatingCCProvider.java
@@ -0,0 +1,49 @@
+package org.sdnplatform.sync.internal.config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+
+
+/**
+ * Delegate cluster configuration to a list of providers
+ * @author readams
+ */
+public class DelegatingCCProvider implements IClusterConfigProvider {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(DelegatingCCProvider.class.getName());
+
+    List<IClusterConfigProvider> providers =
+            new ArrayList<IClusterConfigProvider>();
+
+    public void addProvider(IClusterConfigProvider provider) {
+        this.providers.add(provider);
+    }
+
+    @Override
+    public void init(SyncManager syncManager,
+                     FloodlightModuleContext context) {
+        for (IClusterConfigProvider provider : providers)
+            provider.init(syncManager, context);
+    }
+
+    @Override
+    public ClusterConfig getConfig() throws SyncException {
+        for (IClusterConfigProvider provider : providers) {
+            try {
+                return provider.getConfig();
+            } catch (Exception e) {
+                logger.debug("ClusterConfig provider {} failed: {}",
+                             provider.getClass().getSimpleName(),
+                             e.getMessage());
+            }
+        }
+        throw new SyncException("All cluster config providers failed");
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/config/FallbackCCProvider.java b/src/main/java/org/sdnplatform/sync/internal/config/FallbackCCProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..4fc891902deaa95ceef5ba9390074eae66f25b17
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/config/FallbackCCProvider.java
@@ -0,0 +1,43 @@
+package org.sdnplatform.sync.internal.config;
+
+import java.util.Collections;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+import org.sdnplatform.sync.error.SyncException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Provide a fallback local configuration
+ * @author readams
+ */
+@LogMessageCategory("State Synchronization")
+public class FallbackCCProvider extends StaticCCProvider {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(FallbackCCProvider.class.getName());
+    protected volatile boolean warned = false;
+
+    public FallbackCCProvider() throws SyncException {
+        super(new ClusterConfig(Collections.
+                                singletonList(new Node("localhost",
+                                                       6642,
+                                                       Short.MAX_VALUE,
+                                                       Short.MAX_VALUE)),
+                                                       Short.MAX_VALUE));
+    }
+
+    @Override
+    @LogMessageDoc(level="WARN",
+        message="Using fallback local configuration",
+        explanation="No other nodes are known")
+    public ClusterConfig getConfig() throws SyncException {
+        if (!warned) {
+            logger.warn("Using fallback local configuration");
+            warned = true;
+        }
+        return super.getConfig();
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/config/IClusterConfigProvider.java b/src/main/java/org/sdnplatform/sync/internal/config/IClusterConfigProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..355d3df31cd6e47c3b52dbb9cee0d24d7aafcde6
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/config/IClusterConfigProvider.java
@@ -0,0 +1,27 @@
+package org.sdnplatform.sync.internal.config;
+
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.SyncManager;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+
+/**
+ * Provides configuration for the sync service
+ * @author readams
+ */
+public interface IClusterConfigProvider {
+    /**
+     * Initialize the provider with the configuration parameters from the
+     * Floodlight module context.
+     * @param config
+     */
+    public void init(SyncManager syncManager,
+                     FloodlightModuleContext context);
+
+    /**
+     * Get the {@link ClusterConfig} that represents the current cluster
+     * @return the {@link ClusterConfig} object
+     * @throws SyncException
+     */
+    public ClusterConfig getConfig() throws SyncException;
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/config/Node.java b/src/main/java/org/sdnplatform/sync/internal/config/Node.java
new file mode 100644
index 0000000000000000000000000000000000000000..5893355ef17f54c03e433ad5b371f503e7063663
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/config/Node.java
@@ -0,0 +1,95 @@
+package org.sdnplatform.sync.internal.config;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Represent a node in the synchronization system
+ * @author readams
+ */
+public class Node {
+    /**
+     * The host name to use for contacting this node from the
+     * other nodes
+     */
+    private String hostname;
+
+    /**
+     * The TCP port to use for contacting this node from the
+     * other nodes
+     */
+    private int port;
+
+    /**
+     * The node ID for this node
+     */
+    private short nodeId;
+
+    /**
+     * The ID for the local cluster domain.  Data with a local scope will
+     * be shared only among nodes that share the same domain ID.
+     */
+    private short domainId;
+
+    @JsonCreator
+    public Node(@JsonProperty("hostname") String hostname, 
+                @JsonProperty("port") int port,
+                @JsonProperty("nodeId") short nodeId,
+                @JsonProperty("domainId") short domainId) {
+        super();
+        this.hostname = hostname;
+        this.port = port;
+        this.nodeId = nodeId;
+        this.domainId = domainId;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public short getNodeId() {
+        return nodeId;
+    }
+
+    public short getDomainId() {
+        return domainId;
+    }
+
+    @Override
+    public String toString() {
+        return "Node [hostname=" + hostname + ", port=" + port + ", nodeId="
+                + nodeId + ", domainId=" + domainId + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + domainId;
+        result =
+                prime * result
+                        + ((hostname == null) ? 0 : hostname.hashCode());
+        result = prime * result + nodeId;
+        result = prime * result + port;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        Node other = (Node) obj;
+        if (domainId != other.domainId) return false;
+        if (hostname == null) {
+            if (other.hostname != null) return false;
+        } else if (!hostname.equals(other.hostname)) return false;
+        if (nodeId != other.nodeId) return false;
+        if (port != other.port) return false;
+        return true;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/config/PropertyCCProvider.java b/src/main/java/org/sdnplatform/sync/internal/config/PropertyCCProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..1732a3b9468d9024e36655ff0e65b70a6e266eb0
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/config/PropertyCCProvider.java
@@ -0,0 +1,44 @@
+package org.sdnplatform.sync.internal.config;
+
+import java.util.Map;
+
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+
+public class PropertyCCProvider implements IClusterConfigProvider {
+    protected static Logger logger =
+            LoggerFactory.getLogger(PropertyCCProvider.class.getName());
+
+    private Map<String, String> config;
+
+    @Override
+    public ClusterConfig getConfig() throws SyncException {
+        if (!config.containsKey("nodes") || !config.containsKey("thisNode"))
+            throw new SyncException("Configuration properties nodes or " +
+                    "thisNode not set");
+
+        Short thisNodeId;
+        try {
+            thisNodeId = Short.parseShort(config.get("thisNode"));
+        } catch (NumberFormatException e) {
+            throw new SyncException("Failed to parse thisNode " +
+                    "node ID: " + config.get("thisNode"), e);
+        }
+        try {
+            return new ClusterConfig(config.get("nodes"), thisNodeId);
+        } catch (Exception e) {
+            throw new SyncException("Could not update " +
+                    "configuration", e);
+        }
+    }
+
+    @Override
+    public void init(SyncManager syncManager,
+                     FloodlightModuleContext context) {
+        this.config = context.getConfigParams(syncManager);
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/config/StaticCCProvider.java b/src/main/java/org/sdnplatform/sync/internal/config/StaticCCProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..0028d9e0339abb0fa9903bf720b6cfdac4118a63
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/config/StaticCCProvider.java
@@ -0,0 +1,26 @@
+package org.sdnplatform.sync.internal.config;
+
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.SyncManager;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+
+public class StaticCCProvider implements IClusterConfigProvider {
+    public final ClusterConfig config;
+
+    public StaticCCProvider(ClusterConfig config) {
+        super();
+        this.config = config;
+    }
+
+    @Override
+    public void init(SyncManager syncManager,
+                     FloodlightModuleContext context) {
+
+    }
+
+    @Override
+    public ClusterConfig getConfig() throws SyncException {
+        return config;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/config/StorageCCProvider.java b/src/main/java/org/sdnplatform/sync/internal/config/StorageCCProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..22be6e03c7260a29c1a3a48d4fc2e96b5d062adc
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/config/StorageCCProvider.java
@@ -0,0 +1,163 @@
+package org.sdnplatform.sync.internal.config;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.FloodlightProvider;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceService;
+
+public class StorageCCProvider
+    implements IClusterConfigProvider {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(StorageCCProvider.class.getName());
+
+    private IStorageSourceService storageSource;
+
+    String thisControllerID;
+
+    protected static final String CONTROLLER_TABLE_NAME = "controller_controller";
+    protected static final String CONTROLLER_ID = "id";
+    protected static final String CONTROLLER_SYNC_ID = "sync_id";
+    protected static final String CONTROLLER_SYNC_DOMAIN_ID = "sync_domain_id";
+    protected static final String CONTROLLER_SYNC_PORT = "sync_port";
+
+    protected static final String CONTROLLER_INTERFACE_TABLE_NAME = "controller_controllerinterface";
+    protected static final String CONTROLLER_INTERFACE_CONTROLLER_ID = "controller_id";
+    protected static final String CONTROLLER_INTERFACE_DISCOVERED_IP = "discovered_ip";
+    protected static final String CONTROLLER_INTERFACE_TYPE = "type";
+    protected static final String CONTROLLER_INTERFACE_NUMBER = "number";
+
+    protected static final String BOOT_CONFIG =
+            "/opt/bigswitch/run/boot-config";
+
+    // **********************
+    // IClusterConfigProvider
+    // **********************
+
+    @Override
+    public void init(SyncManager syncManager,
+                     FloodlightModuleContext context) {
+        storageSource = context.getServiceImpl(IStorageSourceService.class);
+
+        // storageSource.addListener(CONTROLLER_TABLE_NAME, this);
+
+        Map<String, String> config =
+                context.getConfigParams(FloodlightProvider.class);
+        thisControllerID = config.get("controllerid");
+    }
+
+    @Override
+    public ClusterConfig getConfig() throws SyncException {
+        if (thisControllerID == null) {
+            Properties bootConfig = new Properties();
+            FileInputStream is = null;
+            try {
+                is = new FileInputStream(BOOT_CONFIG);
+                bootConfig.load(is);
+                thisControllerID = bootConfig.getProperty("controller-id");
+            } catch (Exception e) {
+                throw new SyncException("No controller ID configured and " +
+                                        "could not read " + BOOT_CONFIG);
+            } finally {
+                if (is != null) try {
+                    is.close();
+                } catch (IOException e) {
+                    throw new SyncException(e);
+                }
+            }
+        }
+        if (thisControllerID == null) {
+            throw new SyncException("No controller ID configured");
+        }
+        logger.debug("Using controller ID: {}", thisControllerID);
+
+        List<Node> nodes = new ArrayList<Node>();
+        short thisNodeId = -1;
+
+        String[] cols = {CONTROLLER_ID,
+                         CONTROLLER_SYNC_ID,
+                         CONTROLLER_SYNC_DOMAIN_ID,
+                         CONTROLLER_SYNC_PORT};
+        IResultSet res = null;
+        try {
+            res = storageSource.executeQuery(CONTROLLER_TABLE_NAME,
+                                             cols, null, null);
+            while (res.next()) {
+                String controllerId = res.getString(CONTROLLER_ID);
+                if (!res.containsColumn(CONTROLLER_SYNC_ID) ||
+                    !res.containsColumn(CONTROLLER_SYNC_DOMAIN_ID) ||
+                    !res.containsColumn(CONTROLLER_SYNC_PORT)) {
+                    logger.debug("No sync data found for {}", controllerId);
+                    continue;
+                }
+
+                short nodeId = res.getShort(CONTROLLER_SYNC_ID);
+                short domainId = res.getShort(CONTROLLER_SYNC_DOMAIN_ID);
+                int port = res.getInt(CONTROLLER_SYNC_PORT);
+                String syncIp = getNodeIP(controllerId);
+                if (syncIp == null) {
+                    logger.debug("No sync IP found for {}", controllerId);
+                    continue;
+                }
+                Node node = new Node(syncIp, port, nodeId, domainId);
+                nodes.add(node);
+
+                if (thisControllerID.equals(controllerId))
+                    thisNodeId = nodeId;
+            }
+        } finally {
+            if (res != null) res.close();
+        }
+
+        if (nodes.size() == 0)
+            throw new SyncException("No valid nodes found");
+        if (thisNodeId < 0)
+            throw new SyncException("Could not find a node for the local node");
+
+        return new ClusterConfig(nodes, thisNodeId);
+    }
+
+    // *************
+    // Local methods
+    // *************
+
+    private String getNodeIP(String controllerID) {
+
+        String[] cols = {CONTROLLER_INTERFACE_CONTROLLER_ID,
+                         CONTROLLER_INTERFACE_TYPE,
+                         CONTROLLER_INTERFACE_NUMBER,
+                         CONTROLLER_INTERFACE_DISCOVERED_IP};
+        IResultSet res = null;
+        try {
+            res = storageSource.executeQuery(CONTROLLER_INTERFACE_TABLE_NAME,
+                                             cols, null, null);
+            while (res.next()) {
+                logger.debug("{} {} {} {}",
+                             new Object[] {res.getString(CONTROLLER_INTERFACE_CONTROLLER_ID),
+                                           res.getString(CONTROLLER_INTERFACE_TYPE),
+                                           res.getIntegerObject(CONTROLLER_INTERFACE_NUMBER),
+                                           res.getString(CONTROLLER_INTERFACE_DISCOVERED_IP)});
+                if ("Ethernet".equals(res.getString(CONTROLLER_INTERFACE_TYPE)) &&
+                    Integer.valueOf(0).equals(res.getIntegerObject(CONTROLLER_INTERFACE_NUMBER)) &&
+                    controllerID.equals(res.getString(CONTROLLER_INTERFACE_CONTROLLER_ID)))
+                    return res.getString(CONTROLLER_INTERFACE_DISCOVERED_IP);
+            }
+            return null;
+
+        } finally {
+            if (res != null) res.close();
+        }
+
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteStore.java b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce19253f47118622019ee6bf7f0580329d48d942
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteStore.java
@@ -0,0 +1,234 @@
+package org.sdnplatform.sync.internal.remote;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.RemoteStoreException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.error.SyncRuntimeException;
+import org.sdnplatform.sync.internal.rpc.TProtocolUtil;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.store.StoreUtils;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.internal.util.Pair;
+import org.sdnplatform.sync.thrift.AsyncMessageHeader;
+import org.sdnplatform.sync.thrift.SyncMessage;
+import org.sdnplatform.sync.thrift.CursorRequestMessage;
+import org.sdnplatform.sync.thrift.GetRequestMessage;
+import org.sdnplatform.sync.thrift.KeyedValues;
+import org.sdnplatform.sync.thrift.MessageType;
+import org.sdnplatform.sync.thrift.PutRequestMessage;
+
+
+/**
+ * A store implementation that will connect to a remote sync instance
+ * @author readams
+ */
+public class RemoteStore implements IStore<ByteArray, byte[]> {
+
+    private String storeName;
+    private RemoteSyncManager syncManager;
+
+    public RemoteStore(String storeName, RemoteSyncManager syncManager) {
+        super();
+        this.storeName = storeName;
+        this.syncManager = syncManager;
+    }
+
+    // *************************
+    // IStore<ByteArray, byte[]>
+    // *************************
+
+    @Override
+    public List<Versioned<byte[]>> get(ByteArray key) throws SyncException {
+        StoreUtils.assertValidKey(key);
+        GetRequestMessage grm = new GetRequestMessage();
+
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        header.setTransactionId(syncManager.getTransactionId());
+        grm.setHeader(header);
+
+        grm.setKey(key.get());
+        grm.setStoreName(storeName);
+        
+        SyncMessage bsm = new SyncMessage(MessageType.GET_REQUEST);
+        bsm.setGetRequest(grm);
+
+        SyncReply reply = getReply(header.getTransactionId(), bsm);
+
+        return reply.getValues();
+    }
+
+    @Override
+    public IClosableIterator<Entry<ByteArray, List<Versioned<byte[]>>>>
+            entries() {
+        return new RemoteIterator();
+    }
+
+    @Override
+    public void put(ByteArray key, Versioned<byte[]> value) 
+            throws SyncException {
+        StoreUtils.assertValidKey(key);
+        PutRequestMessage prm = new PutRequestMessage();
+
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        header.setTransactionId(syncManager.getTransactionId());
+        prm.setHeader(header);
+        prm.setVersionedValue(TProtocolUtil.getTVersionedValue(value));
+        prm.setKey(key.get());
+        prm.setStoreName(storeName);
+        
+        SyncMessage bsm = new SyncMessage(MessageType.PUT_REQUEST);
+        bsm.setPutRequest(prm);
+        
+        getReply(header.getTransactionId(), bsm);
+    }
+
+    @Override
+    public List<IVersion> getVersions(ByteArray key) throws SyncException {
+        List<Versioned<byte[]>> values = get(key);
+        ArrayList<IVersion> versions = new ArrayList<IVersion>();
+        for (Versioned<byte[]> v : values) {
+            versions.add(v.getVersion());
+        }
+        return versions;
+    }
+
+    @Override
+    public String getName() {
+        return storeName;
+    }
+
+    @Override
+    public void close() throws SyncException {
+        
+    }
+
+    // *************
+    // Local methods
+    // *************
+    
+    private SyncReply getReply(int xid,
+                                  SyncMessage bsm) 
+            throws SyncException {
+        SyncReply reply = null;
+        try {
+            Future<SyncReply> future = 
+                    syncManager.sendRequest(xid, bsm);
+            reply = future.get(5, TimeUnit.SECONDS);
+            
+        } catch (Exception e) {
+            throw new RemoteStoreException("Error while waiting for reply", e);
+        }
+
+        if (reply.getError() != null)
+            throw reply.getError();
+
+        return reply;
+    }
+    
+    private class RemoteIterator 
+        implements IClosableIterator<Entry<ByteArray, 
+                                           List<Versioned<byte[]>>>> {
+
+        private final int cursorId;
+        Iterator<KeyedValues> currentChunk;
+        
+        public RemoteIterator() {
+            CursorRequestMessage crm = getCRM();
+            crm.setStoreName(storeName);
+            SyncMessage bsm = new SyncMessage(MessageType.CURSOR_REQUEST);
+            bsm.setCursorRequest(crm);
+            SyncReply reply;
+            try {
+                reply = getReply(crm.getHeader().getTransactionId(), 
+                                 bsm);
+            } catch (SyncException e) {
+                throw new SyncRuntimeException(e);
+            }
+            this.cursorId = reply.getIntValue();
+            if (reply.getKeyedValues() != null)
+                currentChunk = reply.getKeyedValues().iterator();
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (currentChunk != null) {
+                if (currentChunk.hasNext())
+                    return true;
+            }
+            Iterator<KeyedValues> nextChunk = getChunk();
+            if (nextChunk != null) {
+                currentChunk = nextChunk;
+                return nextChunk.hasNext();
+            }
+            return false;
+        }
+
+        @Override
+        public Entry<ByteArray, List<Versioned<byte[]>>> next() {
+            if (!hasNext()) throw new NoSuchElementException();
+            KeyedValues kv = currentChunk.next();
+            
+            ByteArray k = new ByteArray(kv.getKey());
+            List<Versioned<byte[]>> v = 
+                    TProtocolUtil.getVersionedList(kv.getValues());
+            return new Pair<ByteArray, List<Versioned<byte[]>>>(k, v);
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void close() {
+            CursorRequestMessage crm = getCRM();
+            crm.setCursorId(cursorId);
+            crm.setClose(true);
+            SyncMessage bsm = new SyncMessage(MessageType.CURSOR_REQUEST);
+            bsm.setCursorRequest(crm);
+            try {
+                getReply(crm.getHeader().getTransactionId(), 
+                         bsm);
+            } catch (SyncException e) {
+                throw new SyncRuntimeException(e);
+            }
+        }
+        
+        private Iterator<KeyedValues> getChunk() {
+            CursorRequestMessage crm = getCRM();
+            crm.setCursorId(cursorId);
+            SyncMessage bsm = new SyncMessage(MessageType.CURSOR_REQUEST);
+            bsm.setCursorRequest(crm);
+
+            SyncReply reply;
+            try {
+                reply = getReply(crm.getHeader().getTransactionId(), 
+                                              bsm);
+            } catch (SyncException e) {
+                throw new SyncRuntimeException(e);
+            }
+            if (reply.getKeyedValues() == null || 
+                reply.getKeyedValues().size() == 0) return null;
+
+            return reply.getKeyedValues().iterator();
+        }
+
+        private CursorRequestMessage getCRM() {
+            CursorRequestMessage crm = new CursorRequestMessage();
+            AsyncMessageHeader header = new AsyncMessageHeader();
+            header.setTransactionId(syncManager.getTransactionId());
+            crm.setHeader(header);
+            return crm;
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelHandler.java b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c215f93a89d75a8c6efc54683b76e9489a8ea12
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelHandler.java
@@ -0,0 +1,149 @@
+package org.sdnplatform.sync.internal.remote;
+
+import java.util.List;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.error.SyncException.ErrorType;
+import org.sdnplatform.sync.internal.rpc.AbstractRPCChannelHandler;
+import org.sdnplatform.sync.internal.rpc.TProtocolUtil;
+import org.sdnplatform.sync.thrift.CursorResponseMessage;
+import org.sdnplatform.sync.thrift.DeleteResponseMessage;
+import org.sdnplatform.sync.thrift.ErrorMessage;
+import org.sdnplatform.sync.thrift.GetResponseMessage;
+import org.sdnplatform.sync.thrift.HelloMessage;
+import org.sdnplatform.sync.thrift.PutResponseMessage;
+import org.sdnplatform.sync.thrift.RegisterResponseMessage;
+
+
+/**
+ * Implement the client side of the RPC service for the 
+ * {@link RemoteSyncManager}
+ * @see RemoteSyncManager
+ * @author readams
+ */
+public class RemoteSyncChannelHandler extends AbstractRPCChannelHandler {
+
+    RemoteSyncManager syncManager;
+
+    public RemoteSyncChannelHandler(RemoteSyncManager syncManager) {
+        super();
+        this.syncManager = syncManager;
+    }
+
+    // ****************************
+    // IdleStateAwareChannelHandler
+    // ****************************
+    
+    @Override
+    public void channelOpen(ChannelHandlerContext ctx, 
+                            ChannelStateEvent e) throws Exception {
+        syncManager.cg.add(ctx.getChannel());
+    }
+
+    @Override
+    public void channelDisconnected(ChannelHandlerContext ctx,
+                                    ChannelStateEvent e) throws Exception {
+        this.syncManager.channel = null;
+    }
+
+    // ******************************************
+    // AbstractRPCChannelHandler message handlers
+    // ******************************************
+
+    @Override
+    protected void handleHello(HelloMessage hello, Channel channel) {
+        syncManager.remoteNodeId = hello.getNodeId();
+    }
+
+    @Override
+    protected void handleGetResponse(GetResponseMessage response,
+                                     Channel channel) {
+        List<Versioned<byte[]>> values = 
+                TProtocolUtil.getVersionedList(response.getValues());
+        SyncReply reply = new SyncReply(values, null, true, null, 0);
+        syncManager.dispatchReply(response.getHeader().getTransactionId(), 
+                                  reply);
+    }
+
+    @Override
+    protected void handlePutResponse(PutResponseMessage response,
+                                     Channel channel) {
+        SyncReply reply = new SyncReply(null, null, true, null, 0);
+        syncManager.dispatchReply(response.getHeader().getTransactionId(), 
+                                  reply);
+
+    }
+
+    @Override
+    protected void handleDeleteResponse(DeleteResponseMessage response,
+                                        Channel channel) {
+        SyncReply reply = new SyncReply(null, null, 
+                                              response.isDeleted(), null, 0);
+        syncManager.dispatchReply(response.getHeader().getTransactionId(), 
+                                  reply);
+    }
+
+    @Override
+    protected void handleCursorResponse(CursorResponseMessage response,
+                                        Channel channel) {
+        SyncReply reply = new SyncReply(null, response.getValues(), true, 
+                                              null, response.getCursorId());
+        syncManager.dispatchReply(response.getHeader().getTransactionId(), 
+                                  reply);
+    }
+
+    @Override
+    protected void handleRegisterResponse(RegisterResponseMessage response,
+                                          Channel channel) {
+        SyncReply reply = new SyncReply(null, null, 
+                                              true, null, 0);
+        syncManager.dispatchReply(response.getHeader().getTransactionId(), 
+                                  reply);
+    }
+
+    @Override
+    protected void handleError(ErrorMessage error, Channel channel) {
+        ErrorType errType = ErrorType.GENERIC;
+        for (ErrorType e : ErrorType.values()) {
+            if (e.getValue() == error.getError().getErrorCode()) {
+                errType = e;
+                break;
+            }
+        }
+        SyncException ex = 
+                SyncException.newInstance(errType, 
+                                          error.getError().getMessage(), 
+                                          null);
+        SyncReply reply = new SyncReply(null, null, false, ex, 0);
+        syncManager.dispatchReply(error.getHeader().getTransactionId(), 
+                                  reply);
+    }
+
+    // *************************
+    // AbstractRPCChannelHandler
+    // *************************
+
+    @Override
+    protected Short getRemoteNodeId() {
+        return syncManager.remoteNodeId;
+    }
+
+    @Override
+    protected Short getLocalNodeId() {
+        return null;
+    }
+
+    @Override
+    protected String getLocalNodeIdString() {
+        return "client";
+    }
+
+    @Override
+    protected int getTransactionId() {
+        return syncManager.getTransactionId();
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncFuture.java b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncFuture.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e2088d093b4cb805b9990a4a142d7a7469655b1
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncFuture.java
@@ -0,0 +1,84 @@
+package org.sdnplatform.sync.internal.remote;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class RemoteSyncFuture implements Future<SyncReply> {
+
+    private final int xid;
+    private volatile SyncReply reply = null;
+    private Object notify = new Object();
+    
+    public RemoteSyncFuture(int xid) {
+        super();
+        this.xid = xid;
+    }
+
+    // **********************
+    // Future<SyncReply>
+    // **********************
+    
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning) {
+        return false;
+    }
+
+    @Override
+    public SyncReply get() throws InterruptedException,
+                               ExecutionException {
+        if (reply != null) return reply;
+        synchronized (notify) {
+            while (reply == null)
+                notify.wait();
+        }
+        return reply;
+    }
+
+    @Override
+    public SyncReply
+            get(long timeout, TimeUnit unit) throws InterruptedException,
+                                            ExecutionException,
+                                            TimeoutException {
+        if (reply != null) return reply;
+        synchronized (notify) {
+            notify.wait(TimeUnit.MILLISECONDS.convert(timeout, unit));
+        }
+        if (reply == null) throw new TimeoutException();
+        return reply;
+    }
+
+    @Override
+    public boolean isCancelled() {
+        return false;
+    }
+
+    @Override
+    public boolean isDone() {
+        return (reply != null);
+    }
+
+    // ****************
+    // RemoteSyncFuture
+    // ****************
+    
+    /**
+     * Get the xid for this message
+     * @return
+     */
+    public int getXid() {
+        return xid;
+    }
+    
+    /**
+     * Set the reply message
+     * @param reply
+     */
+    public void setReply(SyncReply reply) {
+        synchronized (notify) {
+            this.reply = reply;
+            notify.notifyAll();
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncManager.java b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..bae0019d7d0a13f1da91eab551fc9ed3cc971701
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncManager.java
@@ -0,0 +1,316 @@
+package org.sdnplatform.sync.internal.remote;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jboss.netty.bootstrap.ClientBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import org.sdnplatform.sync.error.RemoteStoreException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.error.SyncRuntimeException;
+import org.sdnplatform.sync.error.UnknownStoreException;
+import org.sdnplatform.sync.internal.AbstractSyncManager;
+import org.sdnplatform.sync.internal.rpc.RPCService;
+import org.sdnplatform.sync.internal.rpc.TProtocolUtil;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.store.MappingStoreListener;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.thrift.AsyncMessageHeader;
+import org.sdnplatform.sync.thrift.SyncMessage;
+import org.sdnplatform.sync.thrift.MessageType;
+import org.sdnplatform.sync.thrift.RegisterRequestMessage;
+import org.sdnplatform.sync.thrift.Store;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.IFloodlightService;
+
+/**
+ * Implementation of a sync service that passes its functionality off to a
+ * remote sync manager over a TCP connection
+ * @author readams
+ */
+@LogMessageCategory("State Synchronization")
+public class RemoteSyncManager extends AbstractSyncManager {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(RemoteSyncManager.class.getName());
+    
+    /**
+     * Channel group that will hold all our channels
+     */
+    final ChannelGroup cg = new DefaultChannelGroup("Internal RPC");
+
+    /**
+     * Active connection to server
+     */
+    protected volatile Channel channel;
+
+    /**
+     * The remote node ID of the node we're connected to
+     */
+    protected Short remoteNodeId;
+    
+    /**
+     * Client bootstrap
+     */
+    protected ClientBootstrap clientBootstrap;
+    
+    /**
+     * Transaction ID used in message headers in the RPC protocol
+     */
+    private AtomicInteger transactionId = new AtomicInteger();
+
+    /**
+     * The hostname of the server to connect to
+     */
+    protected String hostname = "localhost";
+    
+    /**
+     * Port to connect to
+     */
+    protected int port = 6642;
+    
+    private ConcurrentHashMap<Integer, RemoteSyncFuture> futureMap = 
+            new ConcurrentHashMap<Integer, RemoteSyncFuture>();
+    private Object futureNotify = new Object();
+    private static int MAX_PENDING_REQUESTS = 1000;
+    
+    // ************
+    // ISyncService
+    // ************
+
+    public RemoteSyncManager() {
+    }
+
+    @Override
+    public void registerStore(String storeName, Scope scope) 
+            throws SyncException {
+        doRegisterStore(storeName, scope, false);
+    }
+
+    @Override
+    public void registerPersistentStore(String storeName, Scope scope)
+            throws SyncException {
+        doRegisterStore(storeName, scope, true);
+    }
+
+    // *******************
+    // AbstractSyncManager
+    // *******************
+
+    @Override
+    public void addListener(String storeName, 
+                            MappingStoreListener listener)
+                                    throws UnknownStoreException {
+        ensureConnected();
+    }
+
+    @Override
+    public IStore<ByteArray, byte[]>
+            getStore(String storeName) throws UnknownStoreException {
+        ensureConnected();
+        return new RemoteStore(storeName, this);
+    }
+
+    @Override
+    public short getLocalNodeId() {
+        ensureConnected();
+        return remoteNodeId;
+    }
+    
+    @Override
+    public void shutdown() {
+        logger.debug("Shutting down Remote Sync Manager");
+        try {
+            if (!cg.close().await(5, TimeUnit.SECONDS)) {
+                logger.debug("Failed to cleanly shut down remote sync");
+            }
+            clientBootstrap.releaseExternalResources();
+        } catch (InterruptedException e) {
+            logger.debug("Interrupted while shutting down remote sync");
+        }
+    }
+
+    // *****************
+    // IFloodlightModule
+    // *****************
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        Map<String, String> config = context.getConfigParams(this);
+        if (null != config.get("hostname"))
+            hostname = config.get("hostname");
+        if (null != config.get("port"))
+            port = Integer.parseInt(config.get("port"));
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) 
+            throws FloodlightModuleException {
+        Executor bossExecutor = Executors.newCachedThreadPool();
+        Executor workerExecutor = Executors.newCachedThreadPool();
+        
+        final ClientBootstrap bootstrap =
+                new ClientBootstrap(
+                     new NioClientSocketChannelFactory(bossExecutor,
+                                                       workerExecutor));
+        bootstrap.setOption("child.reuseAddr", true);
+        bootstrap.setOption("child.keepAlive", true);
+        bootstrap.setOption("child.tcpNoDelay", true);
+        bootstrap.setOption("child.sendBufferSize", 
+                            RPCService.SEND_BUFFER_SIZE);
+        bootstrap.setOption("child.receiveBufferSize", 
+                            RPCService.SEND_BUFFER_SIZE);
+        bootstrap.setOption("child.connectTimeoutMillis", 
+                            RPCService.CONNECT_TIMEOUT);
+        bootstrap.setPipelineFactory(new RemoteSyncPipelineFactory(this));
+        clientBootstrap = bootstrap;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        return null;
+    }
+    
+    // *****************
+    // RemoteSyncManager
+    // *****************
+    
+    /**
+     * Get a suitable transaction ID for sending a message
+     * @return the unique transaction iD
+     */
+    public int getTransactionId() {
+        return transactionId.getAndIncrement();
+    }
+
+    /**
+     * Send a request to the server and generate a future for the 
+     * eventual reply.  Note that this call can block if there is no active
+     * connection while a new connection is re-established or if the maximum
+     * number of requests is already pending
+     * @param xid the transaction ID for the request
+     * @param request the actual request to send
+     * @return A {@link Future} for the reply message
+     * @throws InterruptedException 
+     */
+    public Future<SyncReply> sendRequest(int xid,
+                                            SyncMessage request) 
+                                         throws RemoteStoreException {
+        ensureConnected();
+        RemoteSyncFuture future = new RemoteSyncFuture(xid);
+        futureMap.put(Integer.valueOf(xid), future);
+
+        if (futureMap.size() > MAX_PENDING_REQUESTS) {
+            synchronized (futureNotify) {
+                while (futureMap.size() > MAX_PENDING_REQUESTS) {
+                    try {
+                        futureNotify.wait();
+                    } catch (InterruptedException e) {
+                        throw new RemoteStoreException("Could not send request",
+                                                       e);
+                    }
+                }
+            }
+        }
+        channel.write(request); 
+        return future;
+    }
+
+    @LogMessageDoc(level="WARN",
+                   message="Unexpected sync message reply type={type} id={id}",
+                   explanation="An error occurred in the sync protocol",
+                   recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+    public void dispatchReply(int xid,
+                              SyncReply reply) {
+        RemoteSyncFuture future = futureMap.get(Integer.valueOf(xid));
+        if (future == null) {
+            logger.warn("Unexpected sync message replyid={}", xid);
+            return;
+        }
+        futureMap.remove(Integer.valueOf(xid));
+        future.setReply(reply);
+        synchronized (futureNotify) {
+            futureNotify.notify();
+        }
+    }
+    
+    // ***************
+    // Local methods
+    // ***************
+
+    protected void ensureConnected() {
+        if (channel == null || !channel.isConnected()) {
+            for (int i = 0; i < 25; i++) {
+                synchronized (this) {
+                    if (connect(hostname, port))
+                        return;
+                }
+                try {
+                    Thread.sleep(1000);
+                } catch (Exception e) {}
+            }
+            if (channel == null) 
+                throw new SyncRuntimeException(new SyncException("Failed to establish connection"));
+        }
+    }
+    
+    protected boolean connect(String hostname, int port) {
+        SocketAddress sa =
+                new InetSocketAddress(hostname, port);
+        ChannelFuture future = clientBootstrap.connect(sa);
+        future.awaitUninterruptibly();
+        if (!future.isSuccess()) {
+            logger.error("Could not connect to " + hostname + 
+                         ":" + port, future.getCause());
+            return false;
+        }
+        channel = future.getChannel();
+        logger.debug("Connected to " + hostname + ":" + port);
+        return true;
+    }
+
+    private void doRegisterStore(String storeName, Scope scope, boolean b) 
+            throws SyncException{
+
+        ensureConnected();
+        RegisterRequestMessage rrm = new RegisterRequestMessage();
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        header.setTransactionId(getTransactionId());
+        rrm.setHeader(header);
+        
+        Store store = new Store(storeName);
+        store.setScope(TProtocolUtil.getTScope(scope));
+        store.setPersist(false);
+        rrm.setStore(store);
+        
+        SyncMessage bsm = new SyncMessage(MessageType.REGISTER_REQUEST);
+        bsm.setRegisterRequest(rrm);
+        Future<SyncReply> future =
+                sendRequest(header.getTransactionId(), bsm);
+        try {
+            future.get(2, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            throw new RemoteStoreException("Error while waiting for reply", e);
+        }        
+    }
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncPipelineFactory.java b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncPipelineFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..812781f52c85a51fbd75fc69e2fb04e7c851d412
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncPipelineFactory.java
@@ -0,0 +1,38 @@
+package org.sdnplatform.sync.internal.remote;
+
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.sdnplatform.sync.internal.rpc.ThriftFrameDecoder;
+import org.sdnplatform.sync.internal.rpc.ThriftFrameEncoder;
+
+/**
+ * Pipeline factory for the remote sync service
+ * @author readams
+ */
+public class RemoteSyncPipelineFactory implements ChannelPipelineFactory {
+
+    protected RemoteSyncManager syncManager;
+
+    private static final int maxFrameSize = 1024 * 1024 * 10;
+    
+    public RemoteSyncPipelineFactory(RemoteSyncManager syncManager) {
+        super();
+        this.syncManager = syncManager;
+    }
+
+    @Override
+    public ChannelPipeline getPipeline() throws Exception {
+        RemoteSyncChannelHandler channelHandler = 
+                new RemoteSyncChannelHandler(syncManager);
+        ChannelPipeline pipeline = Channels.pipeline();
+
+        pipeline.addLast("frameDecoder",
+                         new ThriftFrameDecoder(maxFrameSize));
+        pipeline.addLast("frameEncoder",
+                         new ThriftFrameEncoder());
+
+        pipeline.addLast("handler", channelHandler);
+        return pipeline;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/SyncReply.java b/src/main/java/org/sdnplatform/sync/internal/remote/SyncReply.java
new file mode 100644
index 0000000000000000000000000000000000000000..1c114a3e6ab48040c5905f32f2e279ec2647a51b
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/remote/SyncReply.java
@@ -0,0 +1,46 @@
+package org.sdnplatform.sync.internal.remote;
+
+import java.util.List;
+
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.thrift.KeyedValues;
+
+
+/*
+ * Represent a reply to a remote message
+ */
+public class SyncReply {
+    private List<KeyedValues> keyedValues;
+    private List<Versioned<byte[]>> values;
+    private boolean success;
+    private SyncException error;
+    private int intValue;
+
+    public SyncReply(List<Versioned<byte[]>> values,
+                        List<KeyedValues> keyedValues,
+                        boolean success, SyncException error, int intValue) {
+        super();
+        this.values = values;
+        this.keyedValues = keyedValues;
+        this.success = success;
+        this.error = error;
+        this.intValue = intValue;
+    }
+
+    public int getIntValue() {
+        return intValue;
+    }
+    public List<KeyedValues> getKeyedValues() {
+        return keyedValues;
+    }
+    public List<Versioned<byte[]>> getValues() {
+        return values;
+    }
+    public SyncException getError() {
+        return error;
+    }
+    public boolean isSuccess() {
+        return success;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/AbstractRPCChannelHandler.java b/src/main/java/org/sdnplatform/sync/internal/rpc/AbstractRPCChannelHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..23aeab647a76e6690c6cd261d71371010bd034fa
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/rpc/AbstractRPCChannelHandler.java
@@ -0,0 +1,439 @@
+package org.sdnplatform.sync.internal.rpc;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.util.List;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler;
+import org.jboss.netty.handler.timeout.IdleStateEvent;
+import org.jboss.netty.handler.timeout.ReadTimeoutException;
+import org.sdnplatform.sync.error.HandshakeTimeoutException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.thrift.AsyncMessageHeader;
+import org.sdnplatform.sync.thrift.SyncError;
+import org.sdnplatform.sync.thrift.SyncMessage;
+import org.sdnplatform.sync.thrift.CursorRequestMessage;
+import org.sdnplatform.sync.thrift.CursorResponseMessage;
+import org.sdnplatform.sync.thrift.DeleteRequestMessage;
+import org.sdnplatform.sync.thrift.DeleteResponseMessage;
+import org.sdnplatform.sync.thrift.EchoReplyMessage;
+import org.sdnplatform.sync.thrift.EchoRequestMessage;
+import org.sdnplatform.sync.thrift.ErrorMessage;
+import org.sdnplatform.sync.thrift.FullSyncRequestMessage;
+import org.sdnplatform.sync.thrift.GetRequestMessage;
+import org.sdnplatform.sync.thrift.GetResponseMessage;
+import org.sdnplatform.sync.thrift.HelloMessage;
+import org.sdnplatform.sync.thrift.MessageType;
+import org.sdnplatform.sync.thrift.PutRequestMessage;
+import org.sdnplatform.sync.thrift.PutResponseMessage;
+import org.sdnplatform.sync.thrift.RegisterRequestMessage;
+import org.sdnplatform.sync.thrift.RegisterResponseMessage;
+import org.sdnplatform.sync.thrift.SyncOfferMessage;
+import org.sdnplatform.sync.thrift.SyncRequestMessage;
+import org.sdnplatform.sync.thrift.SyncValueMessage;
+import org.sdnplatform.sync.thrift.SyncValueResponseMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Abstract base class for implementing the RPC protocol.  The protocol is 
+ * defined by a thrift specification; all protocol messages are delivered in
+ * a {@link SyncMessage} which will provide specific type information. 
+ * @author readams
+ */
+public abstract class AbstractRPCChannelHandler 
+    extends IdleStateAwareChannelHandler {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(AbstractRPCChannelHandler.class);
+
+    public AbstractRPCChannelHandler() {
+        super();
+    }
+
+    // ****************************
+    // IdleStateAwareChannelHandler
+    // ****************************
+
+    @Override
+    public void channelConnected(ChannelHandlerContext ctx,
+                                 ChannelStateEvent e) throws Exception {
+        HelloMessage m = new HelloMessage();
+        if (getLocalNodeId() != null)
+            m.setNodeId(getLocalNodeId());
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        header.setTransactionId(getTransactionId());
+        m.setHeader(header);
+        SyncMessage bsm = new SyncMessage(MessageType.HELLO);
+        bsm.setHello(m);
+        ctx.getChannel().write(bsm);
+    }
+
+    @Override
+    public void channelIdle(ChannelHandlerContext ctx,
+                            IdleStateEvent e) throws Exception {
+        // send an echo request
+        EchoRequestMessage m = new EchoRequestMessage();
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        header.setTransactionId(getTransactionId());
+        m.setHeader(header);
+        SyncMessage bsm = new SyncMessage(MessageType.ECHO_REQUEST);
+        bsm.setEchoRequest(m);
+        ctx.getChannel().write(bsm);
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx,
+                                ExceptionEvent e) throws Exception {
+        if (e.getCause() instanceof ReadTimeoutException) {
+            // read timeout
+            logger.error("[{}->{}] Disconnecting RPC node due to read timeout",
+                         getLocalNodeIdString(), getRemoteNodeIdString());
+            ctx.getChannel().close();
+        } else if (e.getCause() instanceof HandshakeTimeoutException) {
+            // read timeout
+            logger.error("[{}->{}] Disconnecting RPC node due to " +
+                    "handshake timeout",
+                    getLocalNodeIdString(), getRemoteNodeIdString());
+            ctx.getChannel().close();
+        } else if (e.getCause() instanceof ConnectException ||
+                   e.getCause() instanceof IOException) {
+            logger.debug("[{}->{}] {}: {}", 
+                         new Object[] {getLocalNodeIdString(),
+                                       getRemoteNodeIdString(), 
+                                       e.getCause().getClass().getName(),
+                                       e.getCause().getMessage()});
+        } else {
+            logger.error("[{}->{}] An error occurred on RPC channel",
+                         new Object[]{getLocalNodeIdString(), 
+                                      getRemoteNodeIdString(),
+                                      e.getCause()});
+            ctx.getChannel().close();
+        }
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx,
+                                MessageEvent e) throws Exception {
+        Object message = e.getMessage();
+        if (message instanceof SyncMessage) {
+            handleSyncMessage((SyncMessage)message, ctx.getChannel());
+        } else if (message instanceof List) {
+            for (Object i : (List<?>)message) {
+                if (i instanceof SyncMessage) {
+                    try {
+                        handleSyncMessage((SyncMessage)i,
+                                             ctx.getChannel());
+                    } catch (Exception ex) {
+                        logger.error("Error processing message", ex);
+                        Channels.fireExceptionCaught(ctx, ex);
+                    }
+                }
+            }
+        } else {
+            handleUnknownMessage(ctx, message);
+        }
+    }
+
+    // ****************
+    // Message Handlers
+    // ****************
+
+    /**
+     * A handler for messages on the channel that are not of type 
+     * {@link SyncMessage}
+     * @param ctx the context
+     * @param message the message object
+     */
+    protected void handleUnknownMessage(ChannelHandlerContext ctx, 
+                                        Object message) {
+        logger.warn("[{}->{}] Unhandled message: {}", 
+                    new Object[]{getLocalNodeIdString(), 
+                                 getRemoteNodeIdString(),
+                                 message.getClass().getCanonicalName()});
+    }
+    
+    /**
+     * Handle a generic {@link SyncMessage} and dispatch to an appropriate
+     * handler
+     * @param bsm the message
+     * @param channel the channel on which the message arrived
+     */
+    protected void handleSyncMessage(SyncMessage bsm, Channel channel) {
+        switch (bsm.getType()) {
+            case HELLO:
+                handleHello(bsm.getHello(), channel);
+                break;
+            case ECHO_REQUEST:
+                handleEchoRequest(bsm.getEchoRequest(), channel);
+                break;
+            case GET_REQUEST:
+                handleGetRequest(bsm.getGetRequest(), channel);
+                break;
+            case GET_RESPONSE:
+                handleGetResponse(bsm.getGetResponse(), channel);
+                break;
+            case PUT_REQUEST:
+                handlePutRequest(bsm.getPutRequest(), channel);
+                break;
+            case PUT_RESPONSE:
+                handlePutResponse(bsm.getPutResponse(), channel);
+                break;
+            case DELETE_REQUEST:
+                handleDeleteRequest(bsm.getDeleteRequest(), channel);
+                break;
+            case DELETE_RESPONSE:
+                handleDeleteResponse(bsm.getDeleteResponse(), channel);
+                break;
+            case SYNC_VALUE_RESPONSE:
+                handleSyncValueResponse(bsm.getSyncValueResponse(), channel);
+                break;
+            case SYNC_VALUE:
+                handleSyncValue(bsm.getSyncValue(), channel);
+                break;
+            case SYNC_OFFER:
+                handleSyncOffer(bsm.getSyncOffer(), channel);
+                break;
+            case FULL_SYNC_REQUEST:
+                handleFullSyncRequest(bsm.getFullSyncRequest(), channel);
+                break;
+            case SYNC_REQUEST:
+                handleSyncRequest(bsm.getSyncRequest(), channel);
+                break;
+            case CURSOR_REQUEST:
+                handleCursorRequest(bsm.getCursorRequest(), channel);
+                break;
+            case CURSOR_RESPONSE:
+                handleCursorResponse(bsm.getCursorResponse(), channel);
+                break;
+            case REGISTER_REQUEST:
+                handleRegisterRequest(bsm.getRegisterRequest(), channel);
+                break;
+            case REGISTER_RESPONSE:
+                handleRegisterResponse(bsm.getRegisterResponse(), channel);
+                break;
+            case ERROR:
+                handleError(bsm.getError(), channel);
+                break;
+            case ECHO_REPLY:
+                // do nothing; just the read will have reset our read timeout
+                // handler
+                break;
+            default:
+                logger.warn("[{}->{}] Unhandled message: {}", 
+                             new Object[]{getLocalNodeIdString(), 
+                                          getRemoteNodeIdString(), 
+                                          bsm.getType()});
+                break;
+        }
+        
+    }
+
+    protected void handleHello(HelloMessage request, Channel channel) {
+        unexpectedMessage(request.getHeader().getTransactionId(),
+                          MessageType.HELLO, channel);
+    }
+
+    protected void handleEchoRequest(EchoRequestMessage request,
+                                     Channel channel) {
+        EchoReplyMessage m = new EchoReplyMessage();
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        header.setTransactionId(request.getHeader().getTransactionId());
+        m.setHeader(header);
+        SyncMessage bsm = new SyncMessage(MessageType.ECHO_REPLY);
+        bsm.setEchoReply(m);
+        channel.write(bsm);
+    }
+
+    protected void handleGetRequest(GetRequestMessage request,
+                                  Channel channel) {
+        unexpectedMessage(request.getHeader().getTransactionId(),
+                          MessageType.GET_REQUEST, channel);
+    }
+
+    protected void handleGetResponse(GetResponseMessage response,
+                                     Channel channel) {
+        unexpectedMessage(response.getHeader().getTransactionId(),
+                          MessageType.GET_RESPONSE, channel);
+    }
+
+    protected void handlePutRequest(PutRequestMessage request,
+                                  Channel channel) {
+        unexpectedMessage(request.getHeader().getTransactionId(),
+                          MessageType.PUT_REQUEST, channel);
+    }
+    
+    protected void handlePutResponse(PutResponseMessage response,
+                                     Channel channel) {
+        unexpectedMessage(response.getHeader().getTransactionId(),
+                          MessageType.PUT_RESPONSE, channel);
+    }
+
+    protected void handleDeleteRequest(DeleteRequestMessage request,
+                                     Channel channel) {
+        unexpectedMessage(request.getHeader().getTransactionId(),
+                          MessageType.DELETE_REQUEST, channel);
+    }
+
+    protected void handleDeleteResponse(DeleteResponseMessage response,
+                                        Channel channel) {
+        unexpectedMessage(response.getHeader().getTransactionId(),
+                          MessageType.PUT_RESPONSE, channel);
+    }
+
+    protected void handleSyncValue(SyncValueMessage message, 
+                                   Channel channel) {
+        unexpectedMessage(message.getHeader().getTransactionId(),
+                          MessageType.SYNC_VALUE, channel);
+    }
+
+    protected void handleSyncValueResponse(SyncValueResponseMessage message, 
+                                           Channel channel) {
+        unexpectedMessage(message.getHeader().getTransactionId(),
+                          MessageType.SYNC_VALUE_RESPONSE, channel);
+    }
+
+    protected void handleSyncOffer(SyncOfferMessage message, 
+                                   Channel channel) {
+        unexpectedMessage(message.getHeader().getTransactionId(),
+                          MessageType.SYNC_OFFER, channel);
+    }
+
+    protected void handleSyncRequest(SyncRequestMessage request,
+                                   Channel channel) {
+        unexpectedMessage(request.getHeader().getTransactionId(),
+                          MessageType.SYNC_REQUEST, channel);
+    }
+
+    protected void handleFullSyncRequest(FullSyncRequestMessage request,
+                                         Channel channel) {
+        unexpectedMessage(request.getHeader().getTransactionId(),
+                          MessageType.FULL_SYNC_REQUEST, channel);        
+    }
+
+    protected void handleCursorRequest(CursorRequestMessage request,
+                                       Channel channel) {
+        unexpectedMessage(request.getHeader().getTransactionId(),
+                          MessageType.CURSOR_REQUEST, channel);
+    }
+
+    protected void handleCursorResponse(CursorResponseMessage response,
+                                        Channel channel) {
+        unexpectedMessage(response.getHeader().getTransactionId(),
+                          MessageType.CURSOR_RESPONSE, channel);
+    }
+
+    protected void handleRegisterRequest(RegisterRequestMessage request,
+                                       Channel channel) {
+        unexpectedMessage(request.getHeader().getTransactionId(),
+                          MessageType.REGISTER_REQUEST, channel);
+    }
+
+    protected void handleRegisterResponse(RegisterResponseMessage response,
+                                        Channel channel) {
+        unexpectedMessage(response.getHeader().getTransactionId(),
+                          MessageType.REGISTER_RESPONSE, channel);
+    }
+
+    protected void handleError(ErrorMessage error, Channel channel) {
+        logger.error("[{}->{}] Error for message {}: {}", 
+                     new Object[]{getLocalNodeIdString(), 
+                                  getRemoteNodeIdString(),
+                                  error.getHeader().getTransactionId(),
+                                  error.getError().getMessage()});
+    }
+
+    // *****************
+    // Utility functions
+    // *****************
+
+    /**
+     * Generate an error message from the provided transaction ID and
+     * exception
+     * @param transactionId the transaction Id
+     * @param error the exception
+     * @param type the type of the message that generated the error
+     * @return the {@link SyncError} message
+     */
+    protected SyncMessage getError(int transactionId, Exception error, 
+                                      MessageType type) {
+        int ec = SyncException.ErrorType.GENERIC.getValue();
+        if (error instanceof SyncException) {
+            ec = ((SyncException)error).getErrorCode().getValue();
+        }
+        SyncError m = new SyncError();
+        m.setErrorCode(ec);
+        m.setMessage(error.getMessage());
+        ErrorMessage em = new ErrorMessage();
+        em.setError(m);
+        em.setType(type);
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        header.setTransactionId(transactionId);
+        em.setHeader(header);
+        SyncMessage bsm = new SyncMessage(MessageType.ERROR);
+        bsm.setError(em);
+        return bsm;
+    }
+    
+    /**
+     * Send an error to the channel indicating that we got an unexpected
+     * message for this type of RPC client
+     * @param transactionId the transaction ID for the message that generated
+     * the error
+     * @param type The type of the message that generated the error
+     * @param channel the channel to write the error
+     */
+    protected void unexpectedMessage(int transactionId,
+                                     MessageType type,
+                                     Channel channel) {
+        String message = "Received unexpected message: " + type;
+        logger.warn("[{}->{}] {}",
+                    new Object[]{getLocalNodeIdString(), 
+                                 getRemoteNodeIdString(),
+                                 message});
+        channel.write(getError(transactionId, 
+                               new SyncException(message), type));
+    }
+    
+    /**
+     * Get a transaction ID suitable for sending an async message
+     * @return the unique transaction ID
+     */
+    protected abstract int getTransactionId();
+
+    /**
+     * Get the node ID for the remote node if its connected
+     * @return the node ID
+     */
+    protected abstract Short getRemoteNodeId();
+
+    /**
+     * Get the node ID for the remote node if its connected as a string
+     * for use output
+     * @return the node ID
+     */
+    protected String getRemoteNodeIdString() {
+        return ""+getRemoteNodeId();
+    }
+
+    /**
+     * Get the node ID for the local node if appropriate
+     * @return the node ID.  Null if this is a client
+     */
+    protected abstract Short getLocalNodeId();
+
+    /**
+     * Get the node ID for the local node as a string for use output
+     * @return the node ID
+     */
+    protected String getLocalNodeIdString() {
+        return ""+getLocalNodeId();
+    }
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/HandshakeTimeoutHandler.java b/src/main/java/org/sdnplatform/sync/internal/rpc/HandshakeTimeoutHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ba0e4bb9be944b6729ee23cf0bf31b70040e4d0
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/rpc/HandshakeTimeoutHandler.java
@@ -0,0 +1,105 @@
+/**
+*    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.sdnplatform.sync.internal.rpc;
+
+import java.util.concurrent.TimeUnit;
+
+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;
+import org.sdnplatform.sync.error.HandshakeTimeoutException;
+
+
+/**
+ * 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 RPCChannelHandler handler;
+    final Timer timer;
+    final long timeoutNanos;
+    volatile Timeout timeout;
+    
+    public HandshakeTimeoutHandler(RPCChannelHandler handler,
+                                   Timer timer,
+                                   long timeoutSeconds) {
+        super();
+        this.handler = handler;
+        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 (!handler.isClientConnection && 
+                ((handler.remoteNode == null ||
+                 !handler.rpcService.isConnected(handler.remoteNode.
+                                                 getNodeId()))))
+                Channels.fireExceptionCaught(ctx, EXCEPTION);
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..b46ddcc37ef630364ace3f468e7657c3b47619e8
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java
@@ -0,0 +1,559 @@
+package org.sdnplatform.sync.internal.rpc;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.ISyncService.Scope;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.Cursor;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.sdnplatform.sync.internal.config.Node;
+import org.sdnplatform.sync.internal.rpc.RPCService.NodeMessage;
+import org.sdnplatform.sync.internal.store.IStorageEngine;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.internal.version.VectorClock;
+import org.sdnplatform.sync.thrift.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Channel handler for the RPC service
+ * @author readams
+ */
+@LogMessageCategory("State Synchronization")
+public class RPCChannelHandler extends AbstractRPCChannelHandler {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(RPCChannelHandler.class);
+
+    protected SyncManager syncManager;
+    protected RPCService rpcService;
+    protected Node remoteNode;
+    protected boolean isClientConnection = false;
+
+    public RPCChannelHandler(SyncManager syncManager,
+                             RPCService rpcService) {
+        super();
+        this.syncManager = syncManager;
+        this.rpcService = rpcService;
+    }
+
+    // ****************************
+    // IdleStateAwareChannelHandler
+    // ****************************
+
+    @Override
+    public void channelOpen(ChannelHandlerContext ctx,
+                            ChannelStateEvent e) throws Exception {
+        rpcService.cg.add(ctx.getChannel());
+    }
+
+    @Override
+    public void channelDisconnected(ChannelHandlerContext ctx,
+                                    ChannelStateEvent e) throws Exception {
+        if (remoteNode != null) {
+            rpcService.disconnectNode(remoteNode.getNodeId());
+        }
+    }
+
+    // ******************************************
+    // AbstractRPCChannelHandler message handlers
+    // ******************************************
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx,
+                                MessageEvent e) throws Exception {
+        super.messageReceived(ctx, e);
+        rpcService.debugCounter.flushCounters();
+    }
+        
+    @Override
+    @LogMessageDoc(level="ERROR",
+              message="[{id}->{id}] Attempted connection from unrecognized " +
+                      "floodlight node {id}; disconnecting",
+              explanation="A unknown node connected.  This can happen " +
+                      "transiently if new nodes join the cluster.",
+              recommendation="If the problem persists, verify your cluster" +
+                "configuration and that you don't have unauthorized agents " +
+                "in your network.")
+    protected void handleHello(HelloMessage hello, Channel channel) {
+        if (!hello.isSetNodeId()) {
+            // this is a client connection.  Don't set this up as a node 
+            // connection
+            isClientConnection = true;
+            return;
+        }
+        remoteNode = syncManager.getClusterConfig().getNode(hello.getNodeId());
+        if (remoteNode == null) {
+            logger.error("[{}->{}] Attempted connection from unrecognized " +
+                         "floodlight node {}; disconnecting",
+                         new Object[]{getLocalNodeIdString(), 
+                                      getRemoteNodeIdString(),
+                                      hello.getNodeId()});
+            channel.close();
+            return;
+        }
+        rpcService.nodeConnected(remoteNode.getNodeId(), channel);
+
+        FullSyncRequestMessage srm = new FullSyncRequestMessage();
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        header.setTransactionId(getTransactionId());
+        srm.setHeader(header);
+        SyncMessage bsm = new SyncMessage(MessageType.FULL_SYNC_REQUEST);
+        channel.write(bsm);
+        
+        // XXX - TODO - if last connection was longer ago than the tombstone
+        // timeout, then we need to do a complete flush and reload of our
+        // state.  This is complex though since this applies across entire
+        // partitions and not just single nodes.  We'd need to identify the
+        // partition and nuke the smaller half (or lower priority in the case
+        // of an even split).  Downstream listeners would need to be able to
+        // handle a state nuke as well. A simple way to nuke would be to ensure
+        // floodlight is restarted in the smaller partition.
+    }
+
+    @Override
+    protected void handleGetRequest(GetRequestMessage request, 
+                                    Channel channel) {
+        String storeName = request.getStoreName();
+        try {
+            IStorageEngine<ByteArray, byte[]> store = 
+                    syncManager.getRawStore(storeName);
+    
+            GetResponseMessage m = new GetResponseMessage();
+            AsyncMessageHeader header = new AsyncMessageHeader();
+            header.setTransactionId(request.getHeader().getTransactionId());
+            m.setHeader(header);
+    
+            List<Versioned<byte[]>> values = 
+                    store.get(new ByteArray(request.getKey()));
+            for (Versioned<byte[]> value : values) {
+                m.addToValues(TProtocolUtil.getTVersionedValue(value));
+            }
+            
+            SyncMessage bsm = new SyncMessage(MessageType.GET_RESPONSE);
+            bsm.setGetResponse(m);
+            channel.write(bsm);
+        } catch (Exception e) {
+            channel.write(getError(request.getHeader().getTransactionId(), e, 
+                                   MessageType.GET_REQUEST));
+        }
+    }
+    
+    @Override
+    protected void handlePutRequest(PutRequestMessage request, 
+                                    Channel channel) {
+        String storeName = request.getStoreName();
+        try {
+            IStorageEngine<ByteArray, byte[]> store = 
+                    syncManager.getRawStore(storeName);
+    
+            ByteArray key = new ByteArray(request.getKey());
+            Versioned<byte[]> value = null;
+            if (request.isSetVersionedValue()) {
+                value = TProtocolUtil.
+                        getVersionedValued(request.getVersionedValue());
+                value.increment(syncManager.getLocalNodeId(), 
+                                System.currentTimeMillis());
+            } else if (request.isSetValue()) {
+                byte[] rvalue = request.getValue();
+                List<IVersion> versions = store.getVersions(key);
+                VectorClock newclock = new VectorClock();
+                for (IVersion v : versions) {
+                    newclock = newclock.merge((VectorClock)v);
+                }
+                newclock = newclock.incremented(syncManager.getLocalNodeId(), 
+                                                System.currentTimeMillis());
+                value = Versioned.value(rvalue, newclock);
+            } else {
+                throw new SyncException("No value specified for put");
+            }
+
+            store.put(key, value);
+
+            PutResponseMessage m = new PutResponseMessage();
+            AsyncMessageHeader header = new AsyncMessageHeader();
+            header.setTransactionId(request.getHeader().getTransactionId());
+            m.setHeader(header);
+    
+            SyncMessage bsm = new SyncMessage(MessageType.PUT_RESPONSE);
+            bsm.setPutResponse(m);
+            channel.write(bsm);
+        } catch (Exception e) {
+            channel.write(getError(request.getHeader().getTransactionId(), e, 
+                                   MessageType.PUT_REQUEST));
+        }
+    }
+    
+    @Override
+    protected void handleDeleteRequest(DeleteRequestMessage request, 
+                                       Channel channel) {
+        try {
+            String storeName = request.getStoreName();
+            IStorageEngine<ByteArray, byte[]> store = 
+                    syncManager.getRawStore(storeName);
+            ByteArray key = new ByteArray(request.getKey());
+            VectorClock newclock;
+            if (request.isSetVersion()) {
+                newclock = TProtocolUtil.getVersion(request.getVersion());
+            } else {
+                newclock = new VectorClock();
+                List<IVersion> versions = store.getVersions(key);
+                for (IVersion v : versions) {
+                    newclock = newclock.merge((VectorClock)v);
+                }
+            }
+            newclock = 
+                    newclock.incremented(rpcService.syncManager.getLocalNodeId(), 
+                                         System.currentTimeMillis());
+            Versioned<byte[]> value = Versioned.value(null, newclock);
+            store.put(key, value);
+            
+            DeleteResponseMessage m = new DeleteResponseMessage();
+            AsyncMessageHeader header = new AsyncMessageHeader();
+            header.setTransactionId(request.getHeader().getTransactionId());
+            m.setHeader(header);
+
+            SyncMessage bsm = 
+                    new SyncMessage(MessageType.DELETE_RESPONSE);
+            bsm.setDeleteResponse(m);
+            channel.write(bsm);
+        } catch (Exception e) {
+            channel.write(getError(request.getHeader().getTransactionId(), e, 
+                                   MessageType.DELETE_REQUEST));
+        }
+    }
+
+    @Override
+    protected void handleSyncValue(SyncValueMessage request,
+                                   Channel channel) {
+        if (request.isSetResponseTo())
+            rpcService.messageAcked(MessageType.SYNC_REQUEST, 
+                                    getRemoteNodeId());
+        try {
+            if (logger.isTraceEnabled()) {
+                logger.trace("[{}->{}] Got syncvalue {}", 
+                             new Object[]{getLocalNodeIdString(), 
+                                          getRemoteNodeIdString(), 
+                                          request});
+            }
+
+            Scope scope = TProtocolUtil.getScope(request.getStore().getScope());
+            for (KeyedValues kv : request.getValues()) {
+                Iterable<VersionedValue> tvvi = kv.getValues();
+                Iterable<Versioned<byte[]>> vs = new TVersionedValueIterable(tvvi);
+                syncManager.writeSyncValue(request.getStore().getStoreName(),
+                                           scope,
+                                           request.getStore().isPersist(), 
+                                           kv.getKey(), vs);
+            }
+
+            SyncValueResponseMessage m = new SyncValueResponseMessage();
+            m.setCount(request.getValuesSize());
+            AsyncMessageHeader header = new AsyncMessageHeader();
+            header.setTransactionId(request.getHeader().getTransactionId());
+            m.setHeader(header);
+            SyncMessage bsm = 
+                    new SyncMessage(MessageType.SYNC_VALUE_RESPONSE);
+            bsm.setSyncValueResponse(m);
+            
+            updateCounter(SyncManager.COUNTER_RECEIVED_VALUES, 
+                          request.getValuesSize());
+            channel.write(bsm);
+        } catch (Exception e) {
+
+            channel.write(getError(request.getHeader().getTransactionId(), e, 
+                                   MessageType.SYNC_VALUE));
+        }
+    }
+    
+    protected void handleSyncValueResponse(SyncValueResponseMessage message, 
+                                           Channel channel) {
+        rpcService.messageAcked(MessageType.SYNC_VALUE, getRemoteNodeId());
+    }
+
+    @Override
+    protected void handleSyncOffer(SyncOfferMessage request, 
+                                   Channel channel) {
+        try {
+            String storeName = request.getStore().getStoreName();
+            
+            SyncRequestMessage srm = new SyncRequestMessage();
+            AsyncMessageHeader header = new AsyncMessageHeader();
+            header.setTransactionId(request.getHeader().getTransactionId());
+            srm.setHeader(header);
+            srm.setStore(request.getStore());
+            
+            for (KeyedVersions kv : request.getVersions()) {
+                Iterable<org.sdnplatform.sync.thrift.VectorClock> tvci = 
+                        kv.getVersions();
+                Iterable<VectorClock> vci = new TVersionIterable(tvci);
+                
+                boolean wantKey = syncManager.handleSyncOffer(storeName, 
+                                                              kv.getKey(), vci);
+                if (wantKey)
+                    srm.addToKeys(kv.bufferForKey());
+            }
+            
+            SyncMessage bsm = 
+                    new SyncMessage(MessageType.SYNC_REQUEST);
+            bsm.setSyncRequest(srm);
+            if (logger.isTraceEnabled()) {
+                logger.trace("[{}->{}] Sending SyncRequest with {} elements", 
+                             new Object[]{getLocalNodeIdString(), 
+                                          getRemoteNodeIdString(), 
+                                          srm.getKeysSize()});
+            }
+            channel.write(bsm);
+            
+        } catch (Exception e) {
+            channel.write(getError(request.getHeader().getTransactionId(), 
+                                   e, MessageType.SYNC_OFFER));
+        }
+    }
+
+    @Override
+    protected void handleSyncRequest(SyncRequestMessage request, 
+                                     Channel channel) {
+        rpcService.messageAcked(MessageType.SYNC_OFFER, getRemoteNodeId());
+        if (!request.isSetKeys()) return;
+
+        String storeName = request.getStore().getStoreName();
+        try {
+            IStorageEngine<ByteArray, byte[]> store = 
+                    syncManager.getRawStore(storeName);
+
+            SyncMessage bsm = 
+                    TProtocolUtil.getTSyncValueMessage(request.getStore());
+            SyncValueMessage svm = bsm.getSyncValue();
+            svm.setResponseTo(request.getHeader().getTransactionId());
+            svm.getHeader().setTransactionId(rpcService.getTransactionId());
+
+            for (ByteBuffer key : request.getKeys()) {
+                ByteArray keyArray = new ByteArray(key.array());
+                List<Versioned<byte[]>> values = 
+                        store.get(keyArray);
+                if (values == null || values.size() == 0) continue;
+                KeyedValues kv = 
+                        TProtocolUtil.getTKeyedValues(keyArray, values);
+                svm.addToValues(kv);
+            }
+            
+            if (svm.isSetValues()) {
+                updateCounter(SyncManager.COUNTER_SENT_VALUES, 
+                              svm.getValuesSize());
+                rpcService.syncQueue.add(new NodeMessage(getRemoteNodeId(),
+                                                         bsm));
+            }
+        } catch (Exception e) {
+            channel.write(getError(request.getHeader().getTransactionId(), e, 
+                                   MessageType.SYNC_REQUEST));
+        }
+    }
+
+    @Override
+    protected void handleFullSyncRequest(FullSyncRequestMessage request,
+                                         Channel channel) {
+        startAntientropy();
+    }
+
+    protected void handleCursorRequest(CursorRequestMessage request,
+                                       Channel channel) {
+        try {
+            Cursor c = null;
+            if (request.isSetCursorId()) {
+                c = syncManager.getCursor(request.getCursorId());
+            } else {
+                c = syncManager.newCursor(request.getStoreName());
+            }
+            if (c == null) {
+                throw new SyncException("Unrecognized cursor");
+            }
+
+            CursorResponseMessage m = new CursorResponseMessage();
+            AsyncMessageHeader header = new AsyncMessageHeader();
+            header.setTransactionId(request.getHeader().getTransactionId());
+            m.setHeader(header);
+            m.setCursorId(c.getCursorId());
+
+            if (request.isClose()) {
+                syncManager.closeCursor(c);
+            } else {
+                int i = 0;
+                while (i < 50 && c.hasNext()) {
+                    Entry<ByteArray, List<Versioned<byte[]>>> e = c.next();
+                    
+                    m.addToValues(TProtocolUtil.getTKeyedValues(e.getKey(), 
+                                                                e.getValue()));
+                    i += 1;
+                }
+            }
+
+            SyncMessage bsm = 
+                    new SyncMessage(MessageType.CURSOR_RESPONSE);
+            bsm.setCursorResponse(m);
+            channel.write(bsm);
+        } catch (Exception e) {
+            channel.write(getError(request.getHeader().getTransactionId(),
+                                   e, MessageType.CURSOR_REQUEST));
+        }
+    }
+
+    @Override
+    protected void handleRegisterRequest(RegisterRequestMessage request,
+                                         Channel channel) {
+        try {
+            Scope scope = TProtocolUtil.getScope(request.store.getScope());
+            if (request.store.isPersist())
+                syncManager.registerPersistentStore(request.store.storeName, 
+                                                    scope);
+            else
+                syncManager.registerStore(request.store.storeName, scope);
+            RegisterResponseMessage m = new RegisterResponseMessage();
+            AsyncMessageHeader header = new AsyncMessageHeader();
+            header.setTransactionId(request.getHeader().getTransactionId());
+            m.setHeader(header);
+            SyncMessage bsm = 
+                    new SyncMessage(MessageType.REGISTER_RESPONSE);
+            bsm.setRegisterResponse(m);
+            channel.write(bsm);
+        } catch (Exception e) {
+            channel.write(getError(request.getHeader().getTransactionId(), e, 
+                                   MessageType.REGISTER_REQUEST));
+        }
+    }
+
+    @Override
+    protected void handleError(ErrorMessage error, Channel channel) {
+        rpcService.messageAcked(error.getType(), getRemoteNodeId());
+        super.handleError(error, channel);
+    }
+
+    // *************************
+    // AbstractRPCChannelHandler
+    // *************************
+
+    @Override
+    protected Short getLocalNodeId() {
+        return syncManager.getLocalNodeId();
+    }
+    
+    @Override
+    protected Short getRemoteNodeId() {
+        if (remoteNode != null) 
+            return remoteNode.getNodeId();
+        return null;
+    }
+
+    @Override
+    protected String getLocalNodeIdString() {
+        return ""+getLocalNodeId();
+    }
+    
+    @Override
+    protected String getRemoteNodeIdString() {
+        return ""+getRemoteNodeId();
+    }
+
+    @Override
+    protected int getTransactionId() {
+        return rpcService.getTransactionId();
+    }
+    
+    // *****************
+    // Utility functions
+    // *****************
+
+    protected void updateCounter(String counter, int incr) {
+        rpcService.debugCounter.updateCounter(counter, incr);
+    }
+    
+    protected void startAntientropy() {
+        // Run antientropy in a background task so we don't use up an I/O
+        // thread.  Note that this task will result in lots of traffic
+        // that will use I/O threads but each of those will be in manageable
+        // chunks
+        Runnable arTask = new Runnable() {
+            @Override
+            public void run() {
+                syncManager.antientropy(remoteNode);
+            }
+        };
+        syncManager.getThreadPool().getScheduledExecutor().execute(arTask);
+    }
+
+
+    protected static class TVersionedValueIterable
+        implements Iterable<Versioned<byte[]>> {
+        final Iterable<VersionedValue> tvvi;
+        
+        public TVersionedValueIterable(Iterable<VersionedValue> tvvi) {
+            this.tvvi = tvvi;
+        }
+
+        @Override
+        public Iterator<Versioned<byte[]>> iterator() {
+            final Iterator<VersionedValue> vs = tvvi.iterator();
+            return new Iterator<Versioned<byte[]>>() {
+
+                @Override
+                public boolean hasNext() {
+                    return vs.hasNext();
+                }
+
+                @Override
+                public Versioned<byte[]> next() {
+                    return TProtocolUtil.getVersionedValued(vs.next());
+                }
+
+                @Override
+                public void remove() {
+                    vs.remove();
+                }
+            };
+        }
+    }
+    
+    protected static class TVersionIterable
+        implements Iterable<VectorClock> {
+        final Iterable<org.sdnplatform.sync.thrift.VectorClock> tcvi;
+
+        public TVersionIterable(Iterable<org.sdnplatform.sync.thrift.VectorClock> tcvi) {
+            this.tcvi = tcvi;
+        }
+
+        @Override
+        public Iterator<VectorClock> iterator() {
+            final Iterator<org.sdnplatform.sync.thrift.VectorClock> tcs = 
+                    tcvi.iterator();
+            return new Iterator<VectorClock>() {
+
+                @Override
+                public boolean hasNext() {
+                    return tcs.hasNext();
+                }
+
+                @Override
+                public VectorClock next() {
+                    return TProtocolUtil.getVersion(tcs.next());
+                }
+
+                @Override
+                public void remove() {
+                    tcs.remove();
+                }
+            };
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCPipelineFactory.java b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCPipelineFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..888314ddf3ec845f1da2d1c14e4a87efecc3c78f
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCPipelineFactory.java
@@ -0,0 +1,60 @@
+package org.sdnplatform.sync.internal.rpc;
+
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+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;
+import org.sdnplatform.sync.internal.SyncManager;
+
+
+/**
+ * Pipeline factory for the sync service.
+ * @see SyncManager
+ * @author readams
+ */
+public class RPCPipelineFactory implements ChannelPipelineFactory {
+
+    protected SyncManager syncManager;
+    protected RPCService rpcService;
+    protected Timer timer;
+
+    private static final int maxFrameSize = 512 * 1024;
+    
+    public RPCPipelineFactory(SyncManager syncManager,
+                              RPCService rpcService) {
+        super();
+        this.syncManager = syncManager;
+        this.rpcService = rpcService;
+
+        this.timer = new HashedWheelTimer();
+    }
+
+    @Override
+    public ChannelPipeline getPipeline() throws Exception {
+        RPCChannelHandler channelHandler = 
+                new RPCChannelHandler(syncManager, rpcService);
+
+        IdleStateHandler idleHandler = 
+                new IdleStateHandler(timer, 5, 10, 0);
+        ReadTimeoutHandler readTimeoutHandler = 
+                new ReadTimeoutHandler(timer, 30);
+        
+        ChannelPipeline pipeline = Channels.pipeline();
+        pipeline.addLast("idle", idleHandler);
+        pipeline.addLast("timeout", readTimeoutHandler);
+        pipeline.addLast("handshaketimeout",
+                         new HandshakeTimeoutHandler(channelHandler, timer, 10));
+
+        pipeline.addLast("frameDecoder",
+                         new ThriftFrameDecoder(maxFrameSize));
+        pipeline.addLast("frameEncoder",
+                         new ThriftFrameEncoder());
+
+        pipeline.addLast("handler", channelHandler);
+        return pipeline;
+    }
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCService.java b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCService.java
new file mode 100644
index 0000000000000000000000000000000000000000..f68687062df31c1ae59d465155439ab637be614d
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCService.java
@@ -0,0 +1,664 @@
+package org.sdnplatform.sync.internal.rpc;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.util.SingletonTask;
+import net.floodlightcontroller.debugcounter.IDebugCounterService;
+
+import org.jboss.netty.bootstrap.ClientBootstrap;
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelFutureListener;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.jboss.netty.util.internal.LinkedTransferQueue;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.sdnplatform.sync.internal.config.Node;
+import org.sdnplatform.sync.internal.util.Pair;
+import org.sdnplatform.sync.thrift.SyncMessage;
+import org.sdnplatform.sync.thrift.MessageType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+
+/**
+ * A lightweight RPC mechanism built on netty.
+ * @author readams
+ */
+@LogMessageCategory("State Synchronization")
+public class RPCService {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(RPCService.class);
+
+    /**
+     * Sync manager associated with this RPC service
+     */
+    protected SyncManager syncManager;
+
+    /**
+     * Debug counter service
+     */
+    protected IDebugCounterService debugCounter;
+
+    /**
+     * Channel group that will hold all our channels
+     */
+    final ChannelGroup cg = new DefaultChannelGroup("Internal RPC");
+    
+    /**
+     * {@link Executor} used for netty boss threads
+     */
+    protected Executor bossExecutor;
+    
+    /**
+     * {@link Executor} used for netty worker threads
+     */
+    protected Executor workerExecutor;
+
+    /**
+     * Netty {@link ClientBootstrap} used for creating client connections 
+     */
+    protected ClientBootstrap clientBootstrap;
+    
+    /**
+     * Netty {@link ServerBootstrap} used for creating server connections 
+     */
+    protected ServerBootstrap serverBootstrap;
+
+    /**
+     * Node connections
+     */
+    protected HashMap<Short, NodeConnection> connections = 
+            new HashMap<Short, NodeConnection>();
+
+    /**
+     * Transaction ID used in message headers in the RPC protocol
+     */
+    protected AtomicInteger transactionId = new AtomicInteger();
+
+    /**
+     * Buffer size for sockets
+     */
+    public static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024;
+
+    /**
+     * Connect timeout for client connections
+     */
+    public static final int CONNECT_TIMEOUT = 500;
+
+    /**
+     * True after the {@link RPCService#run()} method is called
+     */
+    protected boolean started = false;
+    
+    /**
+     * true after the {@link RPCService#shutdown()} method
+     * is called. 
+     */
+    protected volatile boolean shutDown = false;
+
+    /**
+     * Task to periodically ensure that connections are active
+     */
+    protected SingletonTask reconnectTask;
+    
+    /**
+     * If we want to rate-limit certain types of messages, we can do
+     * so by limiting the overall number of outstanding messages.  
+     * The number of such messages will be stored in the
+     * {@link MessageWindow}
+     */
+    protected ConcurrentHashMap<Short, MessageWindow> messageWindows;
+    protected static final EnumSet<MessageType> windowedTypes = 
+            EnumSet.of(MessageType.SYNC_VALUE,
+                       MessageType.SYNC_OFFER);
+
+    /**
+     * A thread pool for handling sync messages.  These messages require
+     * a separate pool since writing to the node can be a blocking operation
+     * while waiting for window capacity, and blocking the I/O threads could
+     * lead to deadlock
+     * @see SyncMessageWorker
+     */
+    protected ExecutorService syncExecutor;
+    
+    /**
+     * A queue for holding sync messages that are awaiting being written
+     * to the channel.
+     * @see SyncMessageWorker
+     */
+    protected LinkedTransferQueue<NodeMessage> syncQueue = 
+            new LinkedTransferQueue<NodeMessage>();
+    
+    /**
+     * Number of workers in the sync message thread pool
+     */
+    protected static final int SYNC_MESSAGE_POOL = 2;
+
+    /**
+     * The maximum number of outstanding pending messages for messages
+     * that use message windows
+     */
+    protected static final int MAX_PENDING_MESSAGES = 500;
+
+    public RPCService(SyncManager syncManager, 
+                      IDebugCounterService debugCounter) {
+        super();
+        this.syncManager = syncManager;
+        this.debugCounter = debugCounter;
+
+        messageWindows = new ConcurrentHashMap<Short, MessageWindow>();
+    }
+
+    // *************
+    // public methods
+    // *************
+
+    /**
+     * Start the RPC service
+     */
+    public void run() {
+        started = true;
+
+        final ThreadGroup tg1 = new ThreadGroup("Sync Message Handlers");
+        tg1.setMaxPriority(Thread.NORM_PRIORITY - 3);
+        ThreadFactory f1 = new ThreadFactory() {
+            AtomicInteger id = new AtomicInteger();
+
+            @Override
+            public Thread newThread(Runnable runnable) {
+                return new Thread(tg1, runnable, 
+                                  "SyncMessage-" + id.getAndIncrement());
+            }
+        };
+        syncExecutor = Executors.newCachedThreadPool(f1);
+        for (int i = 0; i < SYNC_MESSAGE_POOL; i++) {
+            syncExecutor.execute(new SyncMessageWorker());
+        }
+        
+        final ThreadGroup tg2 = new ThreadGroup("Sync I/O Threads");
+        tg2.setMaxPriority(Thread.NORM_PRIORITY - 1);
+        ThreadFactory f2 = new ThreadFactory() {
+            @Override
+            public Thread newThread(Runnable runnable) {
+                return new Thread(tg2, runnable);
+            }
+        };
+        
+        bossExecutor = Executors.newCachedThreadPool(f2);
+        workerExecutor = Executors.newCachedThreadPool(f2);
+
+        ChannelPipelineFactory pipelineFactory = 
+                new RPCPipelineFactory(syncManager, this);
+
+        startServer(pipelineFactory);
+        startClients(pipelineFactory);
+    }
+
+    /**
+     * Stop the RPC service
+     */
+    @LogMessageDocs({
+        @LogMessageDoc(level="WARN",
+                message="Failed to cleanly shut down RPC server",
+                explanation="Could not close all open sockets cleanly"),
+        @LogMessageDoc(level="WARN",
+        message="Interrupted while shutting down RPC server",
+        explanation="Could not close all open sockets cleanly")
+    })
+    public void shutdown() {
+        shutDown = true;
+        try {
+            if (!cg.close().await(5, TimeUnit.SECONDS)) {
+                logger.warn("Failed to cleanly shut down RPC server");
+            }
+            clientBootstrap.releaseExternalResources();
+            serverBootstrap.releaseExternalResources();
+        } catch (InterruptedException e) {
+            logger.warn("Interrupted while shutting down RPC server");
+        }
+        logger.debug("Internal floodlight RPC shut down");
+    }
+
+    /**
+     * Get a suitable transaction ID for sending a message
+     * @return the unique transaction iD
+     */
+    public int getTransactionId() {
+        return transactionId.getAndIncrement();
+    }
+
+    /**
+     * Write a message to the node specified
+     * @param nodeId the node ID
+     * @param bsm the message to write
+     * @return <code>true</code> if the message was actually written to 
+     * the channel.  Note this is not the same as having been sent to the 
+     * other node.
+     * @throws InterruptedException 
+     */
+    public boolean writeToNode(Short nodeId, SyncMessage bsm) 
+            throws InterruptedException {
+        if (nodeId == null) return false;
+        NodeConnection nc = connections.get(nodeId);
+        if (nc != null && nc.state == NodeConnectionState.CONNECTED) {
+            waitForMessageWindow(bsm.getType(), nodeId, 0);
+            nc.nodeChannel.write(bsm);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Remove the connection from the connection registry and clean up
+     * any remaining shrapnel
+     * @param nodeId
+     */
+    public void disconnectNode(short nodeId) {
+        synchronized (connections) {
+            Short n = Short.valueOf(nodeId);
+            MessageWindow mw = messageWindows.get(n);
+            if (mw != null) {
+                mw.lock.lock();
+                mw.disconnected = true;
+                try {
+                    mw.full.signalAll();
+                    messageWindows.remove(n);
+                } finally {
+                    mw.lock.unlock();
+                }
+            }
+
+            NodeConnection nc = connections.get(nodeId);
+            if (nc != null) {
+                nc.nuke();
+            }
+            connections.remove(nodeId);
+        }
+    }
+    
+    /**
+     * Check whether all links are established
+     * @return
+     */
+    public boolean isFullyConnected() {
+        for (Node n : syncManager.getClusterConfig().getNodes()) {
+            if (n.getNodeId() != syncManager.getLocalNodeId() &&
+                !isConnected(n.getNodeId())) {
+                if (logger.isTraceEnabled()) {
+                    logger.trace("[{}->{}] missing connection",
+                                 syncManager.getLocalNodeId(),
+                                 n.getNodeId());
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Find out if a particular node is connected
+     * @param nodeId
+     * @return true if the node is connected
+     */
+    public boolean isConnected(short nodeId) {
+        NodeConnection nc = connections.get(nodeId);
+        return (nc != null && nc.state == NodeConnectionState.CONNECTED);
+    }
+
+    /**
+     * Called when a message is acknowledged by a remote node
+     * @param type the message type
+     * @param nodeId the remote node
+     */
+    public void messageAcked(MessageType type, Short nodeId) {
+        if (nodeId == null) return;
+        if (!windowedTypes.contains(type)) return;
+
+        MessageWindow mw = messageWindows.get(nodeId);
+        if (mw == null) return;
+
+        int pending = mw.pending.decrementAndGet();
+        if (pending < MAX_PENDING_MESSAGES) {
+            mw.lock.lock();
+            try {
+                mw.full.signalAll();
+            } finally {
+                mw.lock.unlock();
+            }
+        }
+    }
+
+    // *************
+    // Local methods
+    // *************
+    
+    /**
+     * Get the appropriate {@link MessageWindow} object for the given node. 
+     * @param nodeId the remote node
+     * @return a {@link MessageWindow} object 
+     */
+    private MessageWindow getMW(short nodeId) {
+
+        if (!isConnected(nodeId)) return null;
+
+        Short n = Short.valueOf(nodeId);
+        MessageWindow mw = messageWindows.get(n);
+        if (mw == null) {
+            mw = new MessageWindow();
+            MessageWindow old = messageWindows.putIfAbsent(n, mw);
+            if (old != null) mw = old;
+        }
+        
+        return mw;
+    }
+    
+    /**
+     * Wait for a message window slow to be available for the given node and 
+     * message type
+     * @param type the type of the message
+     * @param nodeId the node Id
+     * @param maxWait the maximum time to wait in milliseconds
+     * @throws InterruptedException 
+     * @return <code>true</code> if the message can be safely written
+     */
+    private boolean waitForMessageWindow(MessageType type, short nodeId,
+                                         long maxWait) 
+            throws InterruptedException {
+        if (!windowedTypes.contains(type)) return true;
+
+        long start = System.nanoTime();
+        
+        // note that this can allow slightly more than the maximum number
+        // of messages.  This is fine.
+        MessageWindow mw = getMW(nodeId);
+        if (!mw.disconnected && 
+            mw.pending.get() >= MAX_PENDING_MESSAGES) {
+            mw.lock.lock();
+            try {
+                while (!mw.disconnected && 
+                       mw.pending.get() >= MAX_PENDING_MESSAGES) {
+                    long now = System.nanoTime();
+                    if (maxWait > 0 && 
+                        (now - start) > maxWait * 1000) return false;
+                    mw.full.awaitNanos(now - start);
+                }
+            } finally {
+                mw.lock.unlock();
+            }
+        }
+        mw = getMW(nodeId);
+        if (mw != null)
+            mw.pending.getAndIncrement();
+        
+        return true;
+    }
+    
+    /**
+     * Start listening sockets
+     */
+    @LogMessageDoc(level="INFO",
+                   message="Listening for internal floodlight RPC on {port}",
+                   explanation="The internal RPC service is ready for connections")
+    protected void startServer(ChannelPipelineFactory pipelineFactory) {
+        final ServerBootstrap bootstrap =
+                new ServerBootstrap(
+                     new NioServerSocketChannelFactory(bossExecutor,
+                                                       workerExecutor));
+        bootstrap.setOption("reuseAddr", true);
+        bootstrap.setOption("child.keepAlive", true);
+        bootstrap.setOption("child.tcpNoDelay", true);
+        bootstrap.setOption("child.sendBufferSize", SEND_BUFFER_SIZE);
+        bootstrap.setOption("child.receiveBufferSize", SEND_BUFFER_SIZE);
+
+        bootstrap.setPipelineFactory(pipelineFactory);
+        serverBootstrap = bootstrap;
+
+        int port = syncManager.getClusterConfig().getNode().getPort();
+        InetSocketAddress sa = new InetSocketAddress(port);
+        cg.add(bootstrap.bind(sa));
+
+        logger.info("Listening for internal floodlight RPC on {}", sa);
+    }
+
+    /**
+     * Wait for the client connection
+     * @author readams
+     */
+    protected class ConnectCFListener implements ChannelFutureListener {
+        protected Node node;
+
+        public ConnectCFListener(Node node) {
+            super();
+            this.node = node;
+        }
+
+        @Override
+        public void operationComplete(ChannelFuture cf) throws Exception {
+            if (!cf.isSuccess()) {
+                synchronized (connections) {
+                    NodeConnection c = connections.remove(node.getNodeId());
+                    if (c != null) c.nuke();
+                    cf.getChannel().close();
+                }
+                
+                String message = "[unknown error]";
+                if (cf.isCancelled()) message = "Timed out on connect";
+                if (cf.getCause() != null) message = cf.getCause().getMessage();
+                logger.debug("[{}->{}] Could not connect to RPC " +
+                             "node: {}", 
+                             new Object[]{syncManager.getLocalNodeId(), 
+                                          node.getNodeId(), 
+                                          message});
+            } else {
+                logger.trace("[{}->{}] Channel future successful", 
+                             syncManager.getLocalNodeId(), 
+                             node.getNodeId());
+            }
+        }
+    }
+
+    /**
+     * Add the node connection to the node connection map
+     * @param nodeId the node ID for the channel
+     * @param channel the new channel
+     */
+    protected void nodeConnected(short nodeId, Channel channel) {
+        logger.debug("[{}->{}] Connection established",
+                     syncManager.getLocalNodeId(),
+                     nodeId);
+        synchronized (connections) {
+            NodeConnection c = connections.get(nodeId);
+            if (c == null) {
+                connections.put(nodeId, c = new NodeConnection());
+            }
+            c.nodeChannel = channel;
+            c.state = NodeConnectionState.CONNECTED;
+        }
+    }
+
+    /**
+     * Connect to remote servers.  We'll initiate the connection to
+     * any nodes with a lower ID so that there will be a single connection
+     * between each pair of nodes which we'll use symmetrically
+     */
+    protected void startClients(ChannelPipelineFactory pipelineFactory) {
+        final ClientBootstrap bootstrap =
+                new ClientBootstrap(
+                     new NioClientSocketChannelFactory(bossExecutor,
+                                                       workerExecutor));
+        bootstrap.setOption("child.reuseAddr", true);
+        bootstrap.setOption("child.keepAlive", true);
+        bootstrap.setOption("child.tcpNoDelay", true);
+        bootstrap.setOption("child.sendBufferSize", SEND_BUFFER_SIZE);
+        bootstrap.setOption("child.connectTimeoutMillis", CONNECT_TIMEOUT);
+        bootstrap.setPipelineFactory(pipelineFactory);
+        clientBootstrap = bootstrap;
+
+        ScheduledExecutorService ses = 
+                syncManager.getThreadPool().getScheduledExecutor();
+        reconnectTask = new SingletonTask(ses, new ConnectTask());
+        reconnectTask.reschedule(0, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Connect to a remote node if appropriate
+     * @param bootstrap the client bootstrap object
+     * @param n the node to connect to
+     */
+    protected void doNodeConnect(Node n) {
+        if (!shutDown && n.getNodeId() < syncManager.getLocalNodeId()) {
+            Short nodeId = n.getNodeId();
+
+            synchronized (connections) {
+                NodeConnection c = connections.get(n.getNodeId());
+                if (c == null) {
+                    connections.put(nodeId, c = new NodeConnection());
+                }
+
+                if (logger.isTraceEnabled()) {
+                    logger.trace("[{}->{}] Connection state: {}", 
+                                 new Object[]{syncManager.getLocalNodeId(),
+                                              nodeId, c.state});
+                }
+                if (c.state.equals(NodeConnectionState.NONE)) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("[{}->{}] Attempting connection {} {}", 
+                                     new Object[]{syncManager.getLocalNodeId(),
+                                                  nodeId, 
+                                                  n.getHostname(), 
+                                                  n.getPort()});
+                    }
+                    SocketAddress sa =
+                            new InetSocketAddress(n.getHostname(), n.getPort());
+                    c.pendingFuture = clientBootstrap.connect(sa);
+                    c.pendingFuture.addListener(new ConnectCFListener(n));
+                    c.state = NodeConnectionState.PENDING;
+                }
+            }
+        }
+    }
+    
+    /**
+     * Ensure that all client connections are active
+     */
+    protected void startClientConnections() {
+        for (Node n : syncManager.getClusterConfig().getNodes()) {
+            doNodeConnect(n);
+        }        
+    }
+
+    /**
+     * Periodically ensure that all the node connections are alive
+     * @author readams
+     */
+    protected class ConnectTask implements Runnable {
+        @Override
+        public void run() {
+            try {
+                if (!shutDown)
+                    startClientConnections();
+            } catch (Exception e) {
+                logger.error("Error in reconnect task", e);
+            }
+            if (!shutDown) {
+                reconnectTask.reschedule(500, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+    
+    /**
+     * Various states for connections
+     * @author readams
+     */
+    protected enum NodeConnectionState {
+        NONE,
+        PENDING,
+        CONNECTED
+    }
+
+    /**
+     * Connection state wrapper for node connections
+     * @author readams
+     */
+    protected static class NodeConnection {
+        volatile NodeConnectionState state = NodeConnectionState.NONE;        
+        protected ChannelFuture pendingFuture;
+        protected Channel nodeChannel;
+        
+        protected void nuke() {
+            state = NodeConnectionState.NONE;
+            if (pendingFuture != null) pendingFuture.cancel();
+            if (nodeChannel != null) nodeChannel.close();
+            pendingFuture = null;
+            nodeChannel = null;
+        }
+    }
+    
+    /**
+     * Maintain state for the pending message window for a given message type
+     * @author readams
+     */
+    protected static class MessageWindow {
+        AtomicInteger pending = new AtomicInteger();
+        volatile boolean disconnected = false;
+        Lock lock = new ReentrantLock();
+        Condition full = lock.newCondition();
+    }
+    
+    /**
+     * A pending message to be sent to a particular mode.
+     * @author readams
+     */
+    protected static class NodeMessage extends Pair<Short,SyncMessage> {
+        private static final long serialVersionUID = -3443080461324647922L;
+
+        public NodeMessage(Short first, SyncMessage second) {
+            super(first, second);
+        }
+    }
+    
+    /**
+     * A worker thread responsible for reading sync messages off the queue
+     * and writing them to the appropriate node's channel.  Because calls 
+     * {@link RPCService#writeToNode(Short, SyncMessage)} can block while
+     * waiting for available slots in the message window, we do this in a
+     * separate thread.
+     * @author readams
+     */
+    protected class SyncMessageWorker implements Runnable {
+        @Override
+        public void run() {
+            while (true) {
+                try {
+                    NodeMessage m = syncQueue.take();
+                    writeToNode(m.getFirst(), m.getSecond());
+                } catch (Exception e) {
+                    logger.error("Error while dispatching message", e);
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/TProtocolUtil.java b/src/main/java/org/sdnplatform/sync/internal/rpc/TProtocolUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..184482d794cc2b3aebb2d9ba7b1199c8de5b3506
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/rpc/TProtocolUtil.java
@@ -0,0 +1,293 @@
+package org.sdnplatform.sync.internal.rpc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.ISyncService.Scope;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.internal.version.ClockEntry;
+import org.sdnplatform.sync.internal.version.VectorClock;
+import org.sdnplatform.sync.thrift.AsyncMessageHeader;
+import org.sdnplatform.sync.thrift.SyncMessage;
+import org.sdnplatform.sync.thrift.KeyedValues;
+import org.sdnplatform.sync.thrift.KeyedVersions;
+import org.sdnplatform.sync.thrift.MessageType;
+import org.sdnplatform.sync.thrift.Store;
+import org.sdnplatform.sync.thrift.SyncOfferMessage;
+import org.sdnplatform.sync.thrift.SyncValueMessage;
+import org.sdnplatform.sync.thrift.VersionedValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Some utility methods for constructing Thrift messages
+ * @author readams
+ */
+public class TProtocolUtil {
+    protected static Logger logger =
+            LoggerFactory.getLogger(TProtocolUtil.class.getName());
+    
+    /**
+     * Convert a {@link VectorClock} into a 
+     * {@link org.sdnplatform.sync.thrift.VectorClock}
+     * @param vc the input clock
+     * @return the output thrift object
+     */
+    public static org.sdnplatform.sync.thrift.VectorClock
+        getTVectorClock(VectorClock vc) {
+        org.sdnplatform.sync.thrift.VectorClock tvc =
+                new org.sdnplatform.sync.thrift.VectorClock();
+        tvc.setTimestamp(vc.getTimestamp());
+        for (ClockEntry ce : vc.getEntries()) {
+            org.sdnplatform.sync.thrift.ClockEntry tce =
+                    new org.sdnplatform.sync.thrift.ClockEntry();
+            tce.setNodeId(ce.getNodeId());
+            tce.setVersion(ce.getVersion());
+            tvc.addToVersions(tce);
+        }
+        
+        return tvc;
+    }
+    
+    /**
+     * Allocate a thrift {@link org.sdnplatform.sync.thrift.VersionedValue}
+     * object wrapping a {@link Versioned} object
+     * @param value the value to wrap
+     * @return the thrift object
+     */
+    public static org.sdnplatform.sync.thrift.VersionedValue
+        getTVersionedValue(Versioned<byte[]> value) {
+
+        org.sdnplatform.sync.thrift.VersionedValue tvv =
+                new org.sdnplatform.sync.thrift.VersionedValue();
+        org.sdnplatform.sync.thrift.VectorClock tvc = 
+                getTVectorClock((VectorClock)value.getVersion());
+        
+        tvv.setVersion(tvc);
+        tvv.setValue(value.getValue());
+
+        return tvv;
+    }
+
+    /**
+     * Construct a thrift {@link org.sdnplatform.sync.thrift.KeyedValues}
+     * @param key the key
+     * @param value the versioned values
+     * @return the thrift object
+     */
+    public static KeyedValues getTKeyedValues(ByteArray key, 
+                                              Versioned<byte[]>... value) {
+        KeyedValues kv = new KeyedValues();
+        kv.setKey(key.get());
+        for (Versioned<byte[]> v : value) {
+            kv.addToValues(getTVersionedValue(v));
+        }
+        return kv;
+    }
+    
+    /**
+     * Construct a thrift {@link org.sdnplatform.sync.thrift.KeyedValues}
+     * @param key the key
+     * @param values the versioned values
+     * @return the thrift object
+     */
+    public static KeyedValues 
+            getTKeyedValues(ByteArray key, 
+                            Iterable<Versioned<byte[]>> values) {
+        KeyedValues kv = new KeyedValues();
+        kv.setKey(key.get());
+        for (Versioned<byte[]> v : values) {
+            kv.addToValues(getTVersionedValue(v));
+        }
+        return kv;
+    }
+    
+    /**
+     * Construct a thrift {@link org.sdnplatform.sync.thrift.KeyedValues}
+     * @param key the key
+     * @param value the versioned values
+     * @return the thrift object
+     */
+    public static KeyedVersions 
+            getTKeyedVersions(ByteArray key, List<Versioned<byte[]>> values) {
+        KeyedVersions kv = new KeyedVersions();
+        kv.setKey(key.get());
+        for (Versioned<byte[]> v : values) {
+            kv.addToVersions(getTVectorClock((VectorClock)v.getVersion()));
+        }
+        return kv;
+    }
+   
+    /**
+     * Allocate a thrift {@link org.sdnplatform.sync.thrift.Store} object
+     * for the current store
+     * @param storeName the name of the store
+     * @param scope the scope of the store
+     * @param persist whether the store is persistent
+     * @return the object
+     */
+    public static org.sdnplatform.sync.thrift.Store getTStore(String storeName,
+                                                               Scope scope, 
+                                                               boolean persist) {
+        return getTStore(storeName, getTScope(scope), persist);
+    }
+    
+    /**
+     * Allocate a thrift {@link org.sdnplatform.sync.thrift.Store} object
+     * for the current store
+     * @param storeName the name of the store
+     * @param scope the scope of the store
+     * @param persist whether the store is persistent
+     * @return the object
+     */
+    public static org.sdnplatform.sync.thrift.Store 
+            getTStore(String storeName,
+                      org.sdnplatform.sync.thrift.Scope scope,
+                      boolean persist) {
+        org.sdnplatform.sync.thrift.Store store =
+                new org.sdnplatform.sync.thrift.Store();
+        store.setScope(scope);
+        store.setStoreName(storeName);
+        store.setPersist(persist);
+        return store;
+    }
+
+    /**
+     * Convert a {@link org.sdnplatform.sync.thrift.Scope} into a 
+     * {@link Scope}
+     * @param tScope the {@link org.sdnplatform.sync.thrift.Scope} to convert
+     * @return the resulting {@link Scope}
+     */
+    public static Scope getScope(org.sdnplatform.sync.thrift.Scope tScope) {
+        switch (tScope) {
+            case LOCAL:
+                return Scope.LOCAL;
+            case GLOBAL:
+            default:
+                return Scope.GLOBAL;
+        }
+    }
+
+    /**
+     * Convert a {@link Scope} into a 
+     * {@link org.sdnplatform.sync.thrift.Scope}
+     * @param tScope the {@link Scope} to convert
+     * @return the resulting {@link org.sdnplatform.sync.thrift.Scope}
+     */
+    public static org.sdnplatform.sync.thrift.Scope getTScope(Scope Scope) {
+        switch (Scope) {
+            case LOCAL:
+                return org.sdnplatform.sync.thrift.Scope.LOCAL;
+            case GLOBAL:
+            default:
+                return org.sdnplatform.sync.thrift.Scope.GLOBAL;
+        }
+    }
+    
+    /**
+     * Get a partially-initialized {@link SyncValueMessage} wrapped with a 
+     * {@link SyncMessage}.  The values will not be set in the
+     * {@link SyncValueMessage}, and the transaction ID will not be set in 
+     * the {@link AsyncMessageHeader}.
+     * @param storeName the store name
+     * @param scope the scope
+     * @param persist whether the store is persistent
+     * @return the {@link SyncMessage}
+     */
+    public static SyncMessage getTSyncValueMessage(String storeName, 
+                                                      Scope scope,
+                                                      boolean persist) {
+        return getTSyncValueMessage(getTStore(storeName, scope, persist));
+    }
+
+    /**
+     * Get a partially-initialized {@link SyncValueMessage} wrapped with a 
+     * {@link SyncMessage}.  The values will not be set in the
+     * {@link SyncValueMessage}, and the transaction ID will not be set in 
+     * the {@link AsyncMessageHeader}.
+     * @param store the {@link Store} associated with the message
+     * @return the {@link SyncMessage}
+     */
+    public static SyncMessage getTSyncValueMessage(Store store) {
+        SyncMessage bsm = 
+                new SyncMessage(MessageType.SYNC_VALUE);
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        SyncValueMessage svm = new SyncValueMessage();
+        svm.setHeader(header);
+        svm.setStore(store);
+
+        bsm.setSyncValue(svm);
+        return bsm;
+    }
+    
+    /**
+     * Get a partially-initialized {@link SyncOfferMessage} wrapped with a 
+     * {@link SyncMessage}.
+     * @param storeName the name of the store associated with the message
+     * @param scope the {@link Scope} for the store
+     * @param persist the scope for the store 
+     * @return the {@link SyncMessage}
+     */
+    public static SyncMessage getTSyncOfferMessage(String storeName,
+                                                      Scope scope,
+                                                      boolean persist) {
+        SyncMessage bsm = new SyncMessage(MessageType.SYNC_OFFER);
+        AsyncMessageHeader header = new AsyncMessageHeader();
+        SyncOfferMessage som = new SyncOfferMessage();
+        som.setHeader(header);
+        som.setStore(getTStore(storeName, scope, persist));
+        
+        bsm.setSyncOffer(som);
+        return bsm;
+    }
+
+    /**
+     * Convert a thrift {@link org.sdnplatform.sync.thrift.VectorClock} into
+     * a {@link VectorClock}.
+     * @param tvc the {@link org.sdnplatform.sync.thrift.VectorClock}
+     * @param the {@link VectorClock}
+     */
+    public static VectorClock getVersion(org.sdnplatform.sync.thrift.VectorClock tvc) {
+        ArrayList<ClockEntry> entries =
+                new ArrayList<ClockEntry>();
+        if (tvc.getVersions() != null) {
+            for (org.sdnplatform.sync.thrift.ClockEntry ce :
+                tvc.getVersions()) {
+                entries.add(new ClockEntry(ce.getNodeId(), ce.getVersion()));
+            }
+        }
+        return new VectorClock(entries, tvc.getTimestamp());
+    }
+    
+    /**
+     * Convert a thrift {@link VersionedValue} into a {@link Versioned}.
+     * @param tvv the {@link VersionedValue}
+     * @return the {@link Versioned}
+     */
+    public static Versioned<byte[]> 
+            getVersionedValued(VersionedValue tvv) {
+                Versioned<byte[]> vv =
+                new Versioned<byte[]>(tvv.getValue(), 
+                                      getVersion(tvv.getVersion()));
+        return vv;
+    }
+
+    /**
+     * Convert from a list of {@link VersionedValue} to a list 
+     * of {@link Versioned<byte[]>}
+     * @param tvv the list of versioned values
+     * @return the list of versioned
+     */
+    public static List<Versioned<byte[]>> getVersionedList(List<VersionedValue> tvv) {
+        ArrayList<Versioned<byte[]>> values = 
+                new ArrayList<Versioned<byte[]>>();
+        if (tvv != null) {
+            for (VersionedValue v : tvv) {
+                values.add(TProtocolUtil.getVersionedValued(v));
+            }
+        }
+        return values;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameDecoder.java b/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameDecoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..50ec9d0f81f6613ba33555ed7366634baac2f759
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameDecoder.java
@@ -0,0 +1,49 @@
+package org.sdnplatform.sync.internal.rpc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.thrift.protocol.TCompactProtocol;
+import org.apache.thrift.transport.TIOStreamTransport;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBufferInputStream;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder;
+import org.sdnplatform.sync.thrift.SyncMessage;
+
+/**
+ * Decode a {@link SyncMessage} from the channel
+ * @author readams
+ */
+public class ThriftFrameDecoder extends LengthFieldBasedFrameDecoder {
+
+    public ThriftFrameDecoder(int maxSize) {
+        super(maxSize, 0, 4, 0, 4);
+    }
+
+    @Override
+    protected Object decode(ChannelHandlerContext ctx,
+                            Channel channel,
+                            ChannelBuffer buffer) throws Exception {
+        List<SyncMessage> ms = null;
+        ChannelBuffer frame = null;
+        while (null != (frame = (ChannelBuffer) super.decode(ctx, channel, 
+                                                             buffer))) {
+            if (ms == null) ms = new ArrayList<SyncMessage>();
+            ChannelBufferInputStream is = new ChannelBufferInputStream(frame);
+            TCompactProtocol thriftProtocol =
+                    new TCompactProtocol(new TIOStreamTransport(is));
+            SyncMessage bsm = new SyncMessage();
+            bsm.read(thriftProtocol);
+            ms.add(bsm);
+        }
+        return ms;
+    }
+
+    @Override
+    protected ChannelBuffer extractFrame(ChannelBuffer buffer,
+                                         int index, int length) {
+        return buffer.slice(index, length);
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameEncoder.java b/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..d71e8d2de8e51dd0285fa55b85c3d6e6c75bcc4e
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameEncoder.java
@@ -0,0 +1,39 @@
+package org.sdnplatform.sync.internal.rpc;
+
+import org.apache.thrift.protocol.TCompactProtocol;
+import org.apache.thrift.transport.TIOStreamTransport;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBufferOutputStream;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.buffer.DynamicChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
+import org.sdnplatform.sync.thrift.SyncMessage;
+
+
+/**
+ * Encode a {@link SyncMessage} into the channel
+ * @author readams
+ *
+ */
+public class ThriftFrameEncoder extends OneToOneEncoder {
+
+    @Override
+    protected Object encode(ChannelHandlerContext ctx, Channel channel,
+                            Object message) throws Exception {
+        if (message instanceof SyncMessage) {
+            ChannelBuffer buf = new DynamicChannelBuffer(512);
+            ChannelBufferOutputStream os = new ChannelBufferOutputStream(buf);
+            TCompactProtocol thriftProtocol =
+                    new TCompactProtocol(new TIOStreamTransport(os));
+            ((SyncMessage) message).write(thriftProtocol);
+
+            ChannelBuffer len = ChannelBuffers.buffer(4);
+            len.writeInt(buf.readableBytes());
+            return ChannelBuffers.wrappedBuffer(len, buf);
+        }
+        return message;
+    }
+
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/DerbySlf4jBridge.java b/src/main/java/org/sdnplatform/sync/internal/store/DerbySlf4jBridge.java
new file mode 100644
index 0000000000000000000000000000000000000000..43484289fe6eb664294d250c595bb64a18d105e8
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/DerbySlf4jBridge.java
@@ -0,0 +1,60 @@
+package org.sdnplatform.sync.internal.store;
+
+import java.io.Writer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Funnels Derby log outputs into an SLF4J logger. 
+ */
+public final class DerbySlf4jBridge
+{
+
+    private static final Logger logger = 
+            LoggerFactory.getLogger(DerbySlf4jBridge.class);
+
+    private DerbySlf4jBridge()
+    {
+    }
+    
+    /**
+     * A basic adapter that funnels Derby's logs through an SLF4J logger.
+     */
+    public static final class LoggingWriter extends Writer
+    {
+        @Override
+        public void write(final char[] cbuf, final int off, final int len)
+        {
+            if (!logger.isDebugEnabled()) return;
+            
+            // Don't bother with empty lines.
+            if (len > 1)
+            {
+                logger.debug(new String(cbuf, off, len));
+            }
+        }
+
+        @Override
+        public void flush()
+        {
+            // noop.
+        }
+
+        @Override
+        public void close()
+        {
+            // noop.
+        }
+    }
+
+    public static String getBridgeMethod() {
+        return DerbySlf4jBridge.class.getCanonicalName() + 
+                ".bridge";
+    }
+    
+    public static Writer bridge()
+    {
+        return new LoggingWriter();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/IStorageEngine.java b/src/main/java/org/sdnplatform/sync/internal/store/IStorageEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e1515f9164697449fe4b308519fe2bcc70afa10
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/IStorageEngine.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.store;
+
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.SyncException;
+
+
+/**
+ * A base storage class which is actually responsible for data persistence. This
+ * interface implies all the usual responsibilities of a Store implementation,
+ * and in addition
+ * <ol>
+ * <li>The implementation MUST throw an ObsoleteVersionException if the user
+ * attempts to put a version which is strictly before an existing version
+ * (concurrent is okay)</li>
+ * <li>The implementation MUST increment this version number when the value is
+ * stored.</li>
+ * <li>The implementation MUST contain an ID identifying it as part of the
+ * cluster</li>
+ * </ol>
+ *
+ * A hash value can be produced for known subtrees of a StorageEngine
+ *
+ *
+ * @param <K> The type of the key being stored
+ * @param <V> The type of the value being stored
+ * @param <T> The type of the transforms
+ *
+ */
+public interface IStorageEngine<K, V> extends IStore<K, V> {
+
+    /**
+     * Get an iterator over pairs of entries in the store. The key is the first
+     * element in the pair and the versioned value is the second element.
+     *
+     * Note that the iterator need not be threadsafe, and that it must be
+     * manually closed after use.
+     *
+     * @return An iterator over the entries in this StorageEngine.
+     */
+    public IClosableIterator<Entry<K,List<Versioned<V>>>> entries();
+
+    /**
+     * Get an iterator over keys in the store.
+     *
+     * Note that the iterator need not be threadsafe, and that it must be
+     * manually closed after use.
+     *
+     * @return An iterator over the keys in this StorageEngine.
+     */
+    public IClosableIterator<K> keys();
+
+    /**
+     * Truncate all entries in the store.  Note that this is a purely local
+     * operation and all the data will sync back over of it's connected
+     * @throws SyncException 
+     */
+    public void truncate() throws SyncException;
+
+    /**
+     * Write the given versioned values into the given key.
+     * @param key the key
+     * @param values the list of versions for that key
+     * @return true if any of the values were new and not obsolete
+     * @throws SyncException
+     */
+    public boolean writeSyncValue(K key, Iterable<Versioned<V>> values);
+    
+    /**
+     * Perform any periodic cleanup tasks that might need to be performed.
+     * This method will be called periodically by the sync manager
+     * @throws SyncException 
+     */
+    public void cleanupTask() throws SyncException;
+    
+    /**
+     * Returns true if the underlying data store is persistent
+     * @return whether the store is persistent
+     */
+    public boolean isPersistent();
+
+    /**
+     * Set the interval after which tombstones will be cleaned up.  This
+     * imposes an upper bound on the amount of time that two partitions can
+     * be separate before reaching consistency for any given key.
+     * @param interval the interval in milliseconds
+     */
+    void setTombstoneInterval(int interval);
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/IStore.java b/src/main/java/org/sdnplatform/sync/internal/store/IStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c97966252028736416f10f246c68683167dfc88
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/IStore.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2008-2009 LinkedIn, Inc
+ * Copyright 2013 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 org.sdnplatform.sync.internal.store;
+
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.SyncException;
+
+
+/**
+ * The basic interface used for storage and storage decorators. Allows the usual
+ * crud operations.
+ *
+ * Note that certain operations rely on the correct implementation of equals and
+ * hashCode for the key. As such, arrays as keys should be avoided.
+ *
+ *
+ */
+public interface IStore<K, V> {
+
+    /**
+     * Get the value associated with the given key
+     *
+     * @param key The key to check for
+     * @return The value associated with the key or an empty list if no values
+     *         are found.
+     * @throws SyncException
+     */
+    public List<Versioned<V>> get(K key) throws SyncException;
+    
+    /**
+     * Get an iterator over pairs of entries in the store. The key is the first
+     * element in the pair and the versioned value is the second element.
+     *
+     * Note that the iterator need not be threadsafe, and that it must be
+     * manually closed after use.
+     *
+     * @return An iterator over the entries in this StorageEngine.
+     */
+    public IClosableIterator<Entry<K,List<Versioned<V>>>> entries();
+    
+    /**
+     * Associate the value with the key and version in this store
+     *
+     * @param key The key to use
+     * @param value The value to store and its version.
+     */
+    public void put(K key, Versioned<V> value)
+            throws SyncException;
+
+    /**
+     * Get a list of the versions associated with the given key
+     * @param key the key
+     * @return the list of {@link IVersion} objects
+     * @throws SyncException
+     */
+    public List<IVersion> getVersions(K key) throws SyncException;
+    
+    /**
+     * @return The name of the store.
+     */
+    public String getName();
+
+    /**
+     * Close the store.
+     *
+     * @throws SyncException If closing fails.
+     */
+    public void close() throws SyncException;
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/InMemoryStorageEngine.java b/src/main/java/org/sdnplatform/sync/internal/store/InMemoryStorageEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc04ac5686c1fbe5c16e3241e64eff21c3c61ea0
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/InMemoryStorageEngine.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2008-2009 LinkedIn, Inc
+ * Copyright 2013 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 org.sdnplatform.sync.internal.store;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.IVersion.Occurred;
+import org.sdnplatform.sync.error.ObsoleteVersionException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.util.Pair;
+
+
+/**
+ * A simple non-persistent, in-memory store.
+ */
+public class InMemoryStorageEngine<K, V> implements IStorageEngine<K, V> {
+
+    private final ConcurrentMap<K, List<Versioned<V>>> map;
+    private final String name;
+    
+    /**
+     * Interval in milliseconds before tombstones will be cleared.
+     */
+    protected int tombstoneDeletion = 24 * 60 * 60 * 1000;
+
+    public InMemoryStorageEngine(String name) {
+        this.name = name;
+        this.map = new ConcurrentHashMap<K, List<Versioned<V>>>();
+    }
+
+    public InMemoryStorageEngine(String name, 
+                                 ConcurrentMap<K, List<Versioned<V>>> map) {
+        this.name = name;
+        this.map = map;
+    }
+
+    // ******************
+    // StorageEngine<K,V>
+    // ******************
+
+    @Override
+    public void close() {}
+
+    @Override
+    public List<IVersion> getVersions(K key) throws SyncException {
+        return StoreUtils.getVersions(get(key));
+    }
+
+    @Override
+    public List<Versioned<V>> get(K key) throws SyncException {
+        StoreUtils.assertValidKey(key);
+        List<Versioned<V>> results = map.get(key);
+        if(results == null) {
+            return new ArrayList<Versioned<V>>(0);
+        }
+        synchronized(results) {
+            return new ArrayList<Versioned<V>>(results);
+        }
+    }
+
+    @Override
+    public void put(K key, Versioned<V> value) throws SyncException {
+        if (!doput(key, value))
+            throw new ObsoleteVersionException();
+    }
+
+    public boolean doput(K key, Versioned<V> value) throws SyncException {
+        StoreUtils.assertValidKey(key);
+
+        IVersion version = value.getVersion();
+
+        while(true) {
+            List<Versioned<V>> items = map.get(key);
+            // If we have no value, optimistically try to add one
+            if(items == null) {
+                items = new ArrayList<Versioned<V>>();
+                items.add(new Versioned<V>(value.getValue(), version));
+                if (map.putIfAbsent(key, items) != null)
+                    continue;
+                return true;
+            } else {
+                synchronized(items) {
+                    // if this check fails, items has been removed from the map
+                    // by delete, so we try again.
+                    if(map.get(key) != items)
+                        continue;
+
+                    // Check for existing versions - remember which items to
+                    // remove in case of success
+                    List<Versioned<V>> itemsToRemove = new ArrayList<Versioned<V>>(items.size());
+                    for(Versioned<V> versioned: items) {
+                        Occurred occurred = value.getVersion().compare(versioned.getVersion());
+                        if(occurred == Occurred.BEFORE) {
+                            return false;
+                        } else if(occurred == Occurred.AFTER) {
+                            itemsToRemove.add(versioned);
+                        }
+                    }
+                    items.removeAll(itemsToRemove);
+                    items.add(value);
+                }
+                return true;
+            }
+        }
+    }
+
+    @Override
+    public IClosableIterator<Entry<K,List<Versioned<V>>>> entries() {
+        return new InMemoryIterator<K, V>(map);
+    }
+
+    @Override
+    public IClosableIterator<K> keys() {
+        // TODO Implement more efficient version.
+        return StoreUtils.keys(entries());
+    }
+
+    @Override
+    public void truncate() {
+        map.clear();
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean writeSyncValue(K key, Iterable<Versioned<V>> values) {
+        boolean success = false;
+        for (Versioned<V> value : values) {
+            try {
+                put (key, value);
+                success = true;
+            } catch (SyncException e) {
+                // ignore
+            }
+        }
+        return success;
+    }
+
+    @Override
+    public void cleanupTask() {
+        // Remove tombstones that are older than the tombstone deletion
+        // threshold.  If a value is deleted and the tombstone has been 
+        // cleaned up before the cluster is fully synchronized, then there
+        // is a chance that deleted values could be resurrected
+        Iterator<Entry<K, List<Versioned<V>>>> iter = map.entrySet().iterator();
+        while (iter.hasNext()) {
+            Entry<K, List<Versioned<V>>> e = iter.next();
+            List<Versioned<V>> items = e.getValue();
+
+            synchronized (items) {
+                if (StoreUtils.canDelete(items, tombstoneDeletion))
+                    iter.remove();
+            }
+        }
+    }
+
+    @Override
+    public boolean isPersistent() {
+        return false;
+    }
+
+    @Override
+    public void setTombstoneInterval(int interval) {
+        this.tombstoneDeletion = interval;
+    }
+
+    // *********************
+    // InMemoryStorageEngine
+    // *********************
+
+    /**
+     * Get the number of keys currently in the store
+     * @return
+     */
+    public int size() {
+        return map.size();
+    }
+    
+    /**
+     * Atomically remove the key and return the value that was mapped to it,
+     * if any
+     * @param key the key to remove
+     * @return the mapped values
+     */
+    public List<Versioned<V>> remove(K key) {
+        while (true) {
+            List<Versioned<V>> items = map.get(key);
+            synchronized (items) {
+                if (map.remove(key, items))
+                    return items;                
+            }
+        }
+    }
+
+    /**
+     * Check whether the given key is present in the store
+     * @param key the key
+     * @return <code>true</code> if the key is present
+     */
+    public boolean containsKey(K key) {
+        return map.containsKey(key);
+    }
+    
+    // ******
+    // Object
+    // ******
+
+    @Override
+    public String toString() {
+        return toString(15);
+    }
+
+    // *************
+    // Local methods
+    // *************
+
+    protected String toString(int size) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{");
+        int count = 0;
+        for(Entry<K, List<Versioned<V>>> entry: map.entrySet()) {
+            if(count > size) {
+                builder.append("...");
+                break;
+            }
+            builder.append(entry.getKey());
+            builder.append(':');
+            builder.append(entry.getValue());
+            builder.append(',');
+        }
+        builder.append('}');
+        return builder.toString();
+    }
+
+    private static class InMemoryIterator<K, V> implements 
+        IClosableIterator<Entry<K, List<Versioned<V>>>> {
+
+        private final Iterator<Entry<K, List<Versioned<V>>>> iterator;
+
+        public InMemoryIterator(ConcurrentMap<K, List<Versioned<V>>> map) {
+            this.iterator = map.entrySet().iterator();
+        }
+
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        public Pair<K, List<Versioned<V>>> next() {
+            Entry<K, List<Versioned<V>>> entry = iterator.next();
+            return new Pair<K, List<Versioned<V>>>(entry.getKey(), 
+                    entry.getValue());
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException("No removal y'all.");
+        }
+
+        @Override
+        public void close() {
+            // nothing to do
+        }
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/JacksonStore.java b/src/main/java/org/sdnplatform/sync/internal/store/JacksonStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4cd6e251d7d0b41bae9d93db28bd640e90ccea7
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/JacksonStore.java
@@ -0,0 +1,240 @@
+package org.sdnplatform.sync.internal.store;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.dataformat.smile.SmileFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.SerializationException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.error.SyncRuntimeException;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.internal.util.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * A store that will serialize and deserialize objects to JSON using Jackson
+ */
+public class JacksonStore<K, V> implements IStore<K, V> {
+    protected static Logger logger =
+            LoggerFactory.getLogger(JacksonStore.class);
+
+    protected static final ObjectMapper mapper = 
+            new ObjectMapper(new SmileFactory());
+    static {
+        mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,
+                         true);
+    }
+    
+    private final IStore<ByteArray, byte[]> delegate;
+    
+    private final ObjectWriter keyWriter;
+    private final ObjectWriter valueWriter;
+    private final ObjectReader keyReader;
+    private final ObjectReader valueReader;
+    
+    private final boolean keyAsTree;
+    private final boolean valueAsTree;
+
+    public JacksonStore(IStore<ByteArray, byte[]> delegate,
+                        Class<K> keyClass,
+                        Class<V> valueClass) {
+        super();
+        this.delegate = delegate;
+        if (keyClass.isAssignableFrom(JsonNode.class)) {
+            keyAsTree = true;
+            this.keyWriter = null;
+            this.keyReader = null;
+        } else {
+            keyAsTree = false;
+            this.keyWriter = mapper.writerWithType(keyClass);
+            this.keyReader = mapper.reader(keyClass);
+        }
+        if (valueClass.isAssignableFrom(JsonNode.class)) {
+            valueAsTree = true;
+            this.valueWriter = null;
+            this.valueReader = null;
+        } else {
+            valueAsTree = false;
+            this.valueWriter = mapper.writerWithType(valueClass);
+            this.valueReader = mapper.reader(valueClass);
+        }
+    }
+    
+    public JacksonStore(IStore<ByteArray, byte[]> delegate,
+                        TypeReference<K> keyType,
+                        TypeReference<V> valueType) {
+        super();
+        this.delegate = delegate;
+        keyAsTree = false;
+        valueAsTree = false;
+        this.keyWriter = mapper.writerWithType(keyType);
+        this.keyReader = mapper.reader(keyType);
+        this.valueWriter = mapper.writerWithType(valueType);
+        this.valueReader = mapper.reader(valueType);
+    }
+
+    // ************
+    // Store<K,V,T>
+    // ************
+    @Override
+    public List<Versioned<V>> get(K key) throws SyncException {
+        ByteArray keybytes = getKeyBytes(key);
+        List<Versioned<byte[]>> values = delegate.get(keybytes);
+        return convertValues(values);
+    }
+
+    @Override
+    public IClosableIterator<Entry<K, List<Versioned<V>>>> entries() {
+        return new JacksonIterator(delegate.entries());
+    }
+
+    @Override
+    public void put(K key, Versioned<V> value)
+            throws SyncException {
+        ByteArray keybytes = getKeyBytes(key);
+        byte[] valuebytes = value.getValue() != null 
+                ? getValueBytes(value.getValue()) 
+                : null;
+        delegate.put(keybytes,
+                     new Versioned<byte[]>(valuebytes, value.getVersion()));
+    }
+
+    @Override
+    public String getName() {
+        return delegate.getName();
+    }
+
+    @Override
+    public void close() throws SyncException {
+        delegate.close();
+    }
+
+    @Override
+    public List<IVersion> getVersions(K key) throws SyncException {
+        ByteArray keybytes = getKeyBytes(key);
+        return delegate.getVersions(keybytes);
+    }
+
+    // *************
+    // Local methods
+    // *************
+
+    private ByteArray getKeyBytes(K key)
+            throws SyncException {
+        if (key == null)
+            throw new IllegalArgumentException("Cannot get null key");
+
+        try {
+            if (keyAsTree)
+                return new ByteArray(mapper.writeValueAsBytes(key));
+            else
+                return new ByteArray(keyWriter.writeValueAsBytes(key));
+        } catch (Exception e) {
+            throw new SerializationException(e);
+        }
+    }
+
+    private byte[] getValueBytes(V value) throws SyncException {
+        try {
+            if (valueAsTree)
+                return mapper.writeValueAsBytes(value);
+            else
+                return valueWriter.writeValueAsBytes(value);
+        } catch (Exception e) {
+            throw new SerializationException(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private V getValueObject(byte[] value) throws SyncException {
+        try {
+            if (value == null) return null;
+            if (valueAsTree)
+                return (V)mapper.readTree(value);
+            else
+                return valueReader.readValue(value);
+        } catch (Exception e) {
+            throw new SerializationException(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private K getKeyObject(ByteArray key) throws SyncException {
+        try {
+            if (keyAsTree)
+                return (K)mapper.readTree(key.get());
+            else
+                return keyReader.readValue(key.get());
+        } catch (Exception e) {
+            throw new SerializationException(e);
+        }
+    }
+    
+    private List<Versioned<V>> convertValues(List<Versioned<byte[]>> values) 
+            throws SyncException {
+        if (values != null) {
+            List<Versioned<V>> objectvalues =
+                new ArrayList<Versioned<V>>(values.size());
+            for (Versioned<byte[]> vb : values) {
+                objectvalues.add(new Versioned<V>(getValueObject(vb.getValue()),
+                        vb.getVersion()));
+            }
+            return objectvalues;        
+        }
+        return null;
+    }
+    
+    private class JacksonIterator implements 
+        IClosableIterator<Entry<K, List<Versioned<V>>>> {
+
+        IClosableIterator<Entry<ByteArray, List<Versioned<byte[]>>>> delegate;
+        
+        public JacksonIterator(IClosableIterator<Entry<ByteArray, 
+                               List<Versioned<byte[]>>>> delegate) {
+            super();
+            this.delegate = delegate;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return delegate.hasNext();
+        }
+
+        @Override
+        public Entry<K, List<Versioned<V>>> next() {
+            Entry<ByteArray, List<Versioned<byte[]>>> n = delegate.next();
+            try {
+                return new Pair<K, List<Versioned<V>>>(getKeyObject(n.getKey()), 
+                                        convertValues(n.getValue()));
+            } catch (SyncException e) {
+                throw new SyncRuntimeException("Failed to construct next value",
+                                               e);
+            }
+        }
+
+        @Override
+        public void remove() {
+            delegate.remove();
+        }
+
+        @Override
+        public void close() {
+            delegate.close();
+        }
+        
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/JavaDBStorageEngine.java b/src/main/java/org/sdnplatform/sync/internal/store/JavaDBStorageEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..d05393406aebcb106854c863200a99966011397e
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/JavaDBStorageEngine.java
@@ -0,0 +1,505 @@
+package org.sdnplatform.sync.internal.store;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+
+import javax.sql.ConnectionPoolDataSource;
+import org.apache.derby.jdbc.EmbeddedConnectionPoolDataSource40;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.IVersion.Occurred;
+import org.sdnplatform.sync.error.ObsoleteVersionException;
+import org.sdnplatform.sync.error.PersistException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.error.SyncRuntimeException;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.internal.util.EmptyClosableIterator;
+import org.sdnplatform.sync.internal.util.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.smile.SmileFactory;
+
+/**
+ * Persistent storage engine that keeps its data in a JDB database
+ * @author readams
+ */
+public class JavaDBStorageEngine implements IStorageEngine<ByteArray, byte[]> {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(JavaDBStorageEngine.class.getName());
+    
+    private static String CREATE_DATA_TABLE = 
+            " (datakey varchar(4096) primary key," +
+            "datavalue blob)";
+    private static String SELECT_ALL =
+            "select * from <tbl>";
+    private static String SELECT_KEY =
+            "select * from <tbl> where datakey = ?";
+    private static String INSERT_KEY =
+            "insert into <tbl> values (?, ?)";
+    private static String UPDATE_KEY =
+            "update <tbl> set datavalue = ? where datakey = ?";
+    private static String DELETE_KEY =
+            "delete from <tbl> where datakey = ?";
+    private static String TRUNCATE =
+            "delete from <tbl>";
+    
+    private String name;
+    
+    private ConnectionPoolDataSource dataSource;
+
+    /**
+     * Interval in milliseconds before tombstones will be cleared.
+     */
+    private int tombstoneDeletion = 24 * 60 * 60 * 1000;
+
+    private static final ObjectMapper mapper = 
+            new ObjectMapper(new SmileFactory());
+    {
+        System.setProperty("derby.stream.error.method",
+                           DerbySlf4jBridge.getBridgeMethod());
+    }
+
+    /**
+     * Construct a new storage engine that will use the provided engine
+     * as a delegate and provide persistence for its data.  Note that
+     * the delegate engine must be empty when this object is constructed
+     * @param delegate the delegate engine to persist
+     * @throws SyncException 
+     */
+    public JavaDBStorageEngine(String name, 
+                               ConnectionPoolDataSource dataSource)
+            throws PersistException {
+        super();
+        
+        this.name = name;
+        this.dataSource = dataSource;
+
+        try {
+            initTable();
+        } catch (SQLException sqle) {
+            throw new PersistException("Could not initialize persistent storage",
+                                       sqle);
+        }
+    }
+    
+    // *******************************
+    // StorageEngine<ByteArray,byte[]>
+    // *******************************
+
+    @Override
+    public List<Versioned<byte[]>> get(ByteArray key) throws SyncException {
+        StoreUtils.assertValidKey(key);
+        Connection dbConnection = null;
+        PreparedStatement stmt = null;
+        try {
+            dbConnection = getConnection();
+            stmt = dbConnection.prepareStatement(getSql(SELECT_KEY));
+            return doSelect(stmt, getKeyAsString(key));
+
+        } catch (Exception e) {
+            throw new PersistException("Could not retrieve key" +
+                    " from database",
+                    e);
+        } finally {
+            cleanupSQL(dbConnection, stmt);
+        }
+    }
+
+    @Override
+    public IClosableIterator<Entry<ByteArray, List<Versioned<byte[]>>>>
+            entries() {
+        PreparedStatement stmt = null;
+        Connection dbConnection = null;
+        try {
+            // we never close this connection unless there's an error; 
+            // it must be closed by the DbIterator
+            dbConnection = getConnection();
+            stmt = dbConnection.prepareStatement(getSql(SELECT_ALL));
+            ResultSet rs = stmt.executeQuery();
+            return new DbIterator(dbConnection, stmt, rs);                
+        } catch (Exception e) {
+            logger.error("Could not create iterator on data", e);
+            try {
+                cleanupSQL(dbConnection, stmt);
+            } catch (Exception e2) {
+                logger.error("Failed to clean up after error", e2);
+            }
+            return new EmptyClosableIterator<Entry<ByteArray,List<Versioned<byte[]>>>>();
+        }
+    }
+
+    @Override
+    public void put(ByteArray key, Versioned<byte[]> value) 
+            throws SyncException {
+        StoreUtils.assertValidKey(key);
+        Connection dbConnection = null;
+        try {
+            PreparedStatement stmt = null;
+            PreparedStatement update = null;
+            try {
+                String keyStr = getKeyAsString(key);
+                dbConnection = getConnection();
+                dbConnection.setAutoCommit(false);
+                stmt = dbConnection.prepareStatement(getSql(SELECT_KEY));
+                List<Versioned<byte[]>> values = doSelect(stmt, keyStr);
+
+                int vindex;
+                if (values.size() > 0) {
+                    update = dbConnection.prepareStatement(getSql(UPDATE_KEY));
+                    update.setString(2, keyStr);
+                    vindex = 1;
+                } else {
+                    update = dbConnection.prepareStatement(getSql(INSERT_KEY));
+                    update.setString(1, keyStr);
+                    vindex = 2;
+                }
+
+                List<Versioned<byte[]>> itemsToRemove = 
+                        new ArrayList<Versioned<byte[]>>(values.size());
+                for(Versioned<byte[]> versioned: values) {
+                    Occurred occurred = value.getVersion().compare(versioned.getVersion());
+                    if(occurred == Occurred.BEFORE) {
+                        throw new ObsoleteVersionException("Obsolete version for key '" + key
+                                                           + "': " + value.getVersion());
+                    } else if(occurred == Occurred.AFTER) {
+                        itemsToRemove.add(versioned);
+                    }
+                }
+                values.removeAll(itemsToRemove);
+                values.add(value);
+
+                ByteArrayInputStream is = 
+                        new ByteArrayInputStream(mapper.writeValueAsBytes(values));                
+                update.setBinaryStream(vindex, is);
+                update.execute();
+                dbConnection.commit();
+            } catch (SyncException e) {
+                dbConnection.rollback();
+                throw e;
+            } catch (Exception e) {
+                dbConnection.rollback();
+                throw new PersistException("Could not retrieve key from database",
+                                           e);
+            } finally {
+                cleanupSQL(dbConnection, stmt, update);
+            }
+        } catch (SQLException e) {
+            cleanupSQL(dbConnection);
+            throw new PersistException("Could not clean up", e);
+        }
+    }
+
+    @Override
+    public IClosableIterator<ByteArray> keys() {
+        return StoreUtils.keys(entries());
+    }
+
+    @Override
+    public void truncate() throws SyncException {
+        Connection dbConnection = null;
+        PreparedStatement update = null;
+        try {
+            dbConnection = getConnection();
+            update = dbConnection.prepareStatement(getSql(TRUNCATE));
+            update.execute();
+        } catch (Exception e) {
+            logger.error("Failed to truncate store " + getName(), e);
+        } finally {
+            cleanupSQL(dbConnection, update);
+        }
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void close() throws SyncException {
+        
+    }
+
+    @Override
+    public boolean writeSyncValue(ByteArray key,
+                                  Iterable<Versioned<byte[]>> values) {
+        boolean success = false;
+        for (Versioned<byte[]> value : values) {
+            try {
+                put (key, value);
+                success = true;
+            } catch (PersistException e) {
+                logger.error("Failed to sync value because of " +
+                             "persistence exception", e);
+            } catch (SyncException e) {
+                // ignore obsolete version exception
+            }
+        }
+        return success;
+    }
+
+    @Override
+    public List<IVersion> getVersions(ByteArray key) throws SyncException {
+        return StoreUtils.getVersions(get(key));
+    }
+
+    @Override
+    public void cleanupTask() throws SyncException {
+        Connection dbConnection = null;
+        PreparedStatement stmt = null;
+        try {
+            dbConnection = getConnection();
+            dbConnection.setAutoCommit(true);
+            stmt = dbConnection.prepareStatement(getSql(SELECT_ALL));
+            ResultSet rs = stmt.executeQuery();
+            while (rs.next()) {
+                List<Versioned<byte[]>> items = getVersionedList(rs);
+                if (StoreUtils.canDelete(items, tombstoneDeletion)) {
+                    doClearTombstone(rs.getString("datakey"));
+                }
+            }                
+        } catch (Exception e) {
+            logger.error("Failed to delete key", e);
+        } finally {
+            cleanupSQL(dbConnection, stmt);
+        }
+    }
+
+    @Override
+    public boolean isPersistent() {
+        return true;
+    }
+
+    @Override
+    public void setTombstoneInterval(int interval) {
+        this.tombstoneDeletion = interval;
+    }
+
+    // *******************
+    // JavaDBStorageEngine
+    // *******************
+
+    /**
+     * Get a connection pool data source for use by Java DB storage engines
+     * @param memory whether to actually use a memory database
+     * @return the {@link ConnectionPoolDataSource}
+     */
+    public static ConnectionPoolDataSource getDataSource(boolean memory) {
+
+        EmbeddedConnectionPoolDataSource40 ds = 
+                new EmbeddedConnectionPoolDataSource40();
+        if (memory) {
+            ds.setDatabaseName("memory:SyncDB");                
+        } else {
+            ds.setDatabaseName("SyncDB");
+        }
+        ds.setCreateDatabase("create");
+        ds.setUser("floodlight");
+        ds.setPassword("floodlight");
+        return ds;
+    }
+    
+    // *************
+    // Local methods
+    // *************
+    
+    private static void cleanupSQL(Connection dbConnection) 
+            throws SyncException {
+        cleanupSQL(dbConnection, (PreparedStatement[])null);
+    }
+    
+    private static void cleanupSQL(Connection dbConnection, 
+                                   PreparedStatement... stmts) 
+                                    throws SyncException {
+        try {
+            if (stmts != null) {
+                for (PreparedStatement stmt : stmts) {
+                    if (stmt != null) 
+                        stmt.close();
+                }
+            }
+        } catch (SQLException e) {
+            throw new PersistException("Could not close statement", e);
+        } finally {
+            try {
+                if (dbConnection != null && !dbConnection.isClosed())
+                    dbConnection.close();
+            } catch (SQLException e) {
+                throw new PersistException("Could not close connection", e);
+            }
+        }
+    }
+    
+    private Connection getConnection() throws SQLException {
+        Connection conn = dataSource.getPooledConnection().getConnection();
+        conn.setTransactionIsolation(Connection.
+                                     TRANSACTION_READ_COMMITTED);
+        return conn;
+    }
+    
+    private void initTable() throws SQLException {
+        Connection dbConnection = getConnection();
+        Statement statement = null;
+        statement = dbConnection.createStatement();
+        try {
+            statement.execute("CREATE TABLE " + getName() +
+                              CREATE_DATA_TABLE);
+        } catch (SQLException e) {
+            // eat table already exists exception
+            if (!"X0Y32".equals(e.getSQLState()))
+                throw e;
+        } finally {
+            if (statement != null) statement.close();
+            dbConnection.close();
+        }
+    }
+    
+    private String getKeyAsString(ByteArray key) 
+            throws UnsupportedEncodingException {
+        return new String(key.get(), "UTF8");
+    }
+
+    private static ByteArray getStringAsKey(String keyStr) 
+            throws UnsupportedEncodingException {
+        return new ByteArray(keyStr.getBytes("UTF8"));
+    }
+    
+    private String getSql(String sql) {
+        return sql.replace("<tbl>", getName());
+    }
+    
+    private static List<Versioned<byte[]>> getVersionedList(ResultSet rs) 
+                throws SQLException, JsonParseException, 
+                    JsonMappingException, IOException {
+        InputStream is = rs.getBinaryStream("datavalue");
+        return mapper.readValue(is,
+                                new TypeReference<List<VCVersioned<byte[]>>>() {});
+    }
+    
+    private List<Versioned<byte[]>> doSelect(PreparedStatement stmt,
+                                             String key) 
+                throws SQLException, JsonParseException, 
+                    JsonMappingException, IOException {
+        stmt.setString(1, key);
+        ResultSet rs = stmt.executeQuery();
+        
+        if (rs.next()) {
+            return getVersionedList(rs);
+        } else {
+            return new ArrayList<Versioned<byte[]>>(0);
+        }
+    }
+
+    private void doClearTombstone(String keyStr) throws SyncException {
+        Connection dbConnection = null;
+        try {
+            PreparedStatement stmt = null;
+            PreparedStatement update = null;
+            try {
+                dbConnection = getConnection();
+                dbConnection.setAutoCommit(false);    
+                stmt = dbConnection.prepareStatement(getSql(SELECT_KEY));
+                List<Versioned<byte[]>> items = doSelect(stmt, keyStr);
+                if (StoreUtils.canDelete(items, tombstoneDeletion)) {
+                    update = dbConnection.prepareStatement(getSql(DELETE_KEY));
+                    update.setString(1, keyStr);
+                    update.execute();
+                }
+                dbConnection.commit();
+
+            } catch (Exception e) {
+                if (dbConnection != null)
+                    dbConnection.rollback();
+                logger.error("Failed to delete key", e);
+            } finally {
+                cleanupSQL(dbConnection, stmt, update);
+            }
+        } catch (SQLException e) {
+            logger.error("Failed to clean up after error", e);
+            cleanupSQL(dbConnection);
+        }
+    }
+    
+    private static class DbIterator implements 
+        IClosableIterator<Entry<ByteArray,List<Versioned<byte[]>>>> {
+
+        private final Connection dbConnection;
+        private final PreparedStatement stmt;
+        private final ResultSet rs;
+        private boolean hasNext = false;
+        private boolean hasNextSet = false;
+        
+        public DbIterator(Connection dbConnection,
+                          PreparedStatement stmt, 
+                          ResultSet rs) {
+            super();
+            this.dbConnection = dbConnection;
+            this.stmt = stmt;
+            this.rs = rs;
+        }
+
+        @Override
+        public boolean hasNext() {
+            try {
+                if (hasNextSet) return hasNext;
+                hasNextSet = true;
+                hasNext = rs.next();
+            } catch (Exception e) {
+                logger.error("Error in DB Iterator", e);
+                hasNextSet = true;
+                hasNext = false;
+            }
+            return hasNext;
+        }
+
+        @Override
+        public Pair<ByteArray, List<Versioned<byte[]>>> next() {
+            if (hasNext()) {
+                try {
+                    ByteArray key = getStringAsKey(rs.getString("datakey"));
+                    List<Versioned<byte[]>> vlist = getVersionedList(rs);
+                    hasNextSet = false;
+                    return new Pair<ByteArray, 
+                                    List<Versioned<byte[]>>>(key, vlist);
+                } catch (Exception e) {
+                    throw new SyncRuntimeException("Error in DB Iterator", 
+                                                   new PersistException(e));
+                }
+            } else {
+                throw new NoSuchElementException();
+            }
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void close() {
+            try {
+                cleanupSQL(dbConnection, stmt);
+            } catch (SyncException e) {
+                logger.error("Could not close DB iterator", e);
+            }
+        }
+        
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/ListenerStorageEngine.java b/src/main/java/org/sdnplatform/sync/internal/store/ListenerStorageEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..56ced90b80300c668e2bc40114ac64722f5b2062
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/ListenerStorageEngine.java
@@ -0,0 +1,151 @@
+package org.sdnplatform.sync.internal.store;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+
+import net.floodlightcontroller.debugcounter.IDebugCounterService;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.IStoreListener.UpdateType;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.sdnplatform.sync.internal.util.ByteArray;
+
+
+/**
+ * A storage engine that proxies to another storage engine and notifies
+ * registered listeners of changes
+ * @author readams
+ */
+public class ListenerStorageEngine 
+    implements IStorageEngine<ByteArray, byte[]> {
+
+    /**
+     * Listeners for this store
+     */
+    protected List<MappingStoreListener> listeners = 
+            new ArrayList<MappingStoreListener>();
+
+    /**
+     * The local storage for this storage engine
+     */
+    protected IStorageEngine<ByteArray, byte[]> localStorage;
+
+    /**
+     * Debug counter service
+     */
+    protected IDebugCounterService debugCounter;
+    
+    /**
+     * Allocate new {@link ListenerStorageEngine}
+     * @param localStorage the delegate store
+     * @param debugCounter debug counter service
+     */
+    public ListenerStorageEngine(IStorageEngine<ByteArray,
+                                                byte[]> localStorage,
+                                                IDebugCounterService debugCounter) {
+        this.localStorage = localStorage;
+        this.debugCounter = debugCounter;
+    }
+
+    // *************************
+    // StorageEngine<Key,byte[]>
+    // *************************
+
+    @Override
+    public List<Versioned<byte[]>> get(ByteArray key) throws SyncException {
+        updateCounter(SyncManager.COUNTER_GETS);
+        return localStorage.get(key);
+    }
+
+    @Override
+    public IClosableIterator<Entry<ByteArray,List<Versioned<byte[]>>>> entries() {
+        updateCounter(SyncManager.COUNTER_ITERATORS);
+        return localStorage.entries();
+    }
+
+    @Override
+    public void put(ByteArray key, Versioned<byte[]> value)
+            throws SyncException {
+        updateCounter(SyncManager.COUNTER_PUTS);
+        localStorage.put(key, value);
+        notifyListeners(key, UpdateType.LOCAL);
+    }
+
+    @Override
+    public IClosableIterator<ByteArray> keys() {
+        return localStorage.keys();
+    }
+
+    @Override
+    public void truncate() throws SyncException {
+        localStorage.truncate();
+    }
+
+    @Override
+    public String getName() {
+        return localStorage.getName();
+    }
+
+    @Override
+    public void close() throws SyncException {
+        localStorage.close();
+    }
+
+    @Override
+    public List<IVersion> getVersions(ByteArray key) throws SyncException {
+        return localStorage.getVersions(key);
+    }
+
+    @Override
+    public boolean writeSyncValue(ByteArray key,
+                                  Iterable<Versioned<byte[]>> values) {
+        boolean r = localStorage.writeSyncValue(key, values);
+        if (r) notifyListeners(key, UpdateType.REMOTE);
+        return r;
+    }
+
+    @Override
+    public void cleanupTask() throws SyncException {
+        localStorage.cleanupTask();
+    }
+
+    @Override
+    public boolean isPersistent() {
+        return localStorage.isPersistent();
+    }
+
+    @Override
+    public void setTombstoneInterval(int interval) {
+        localStorage.setTombstoneInterval(interval);
+    }
+    
+    // *********************
+    // ListenerStorageEngine
+    // *********************
+
+    public void addListener(MappingStoreListener listener) {
+        listeners.add(listener);
+    }
+
+    protected void notifyListeners(ByteArray key, UpdateType type) {
+        notifyListeners(Collections.singleton(key).iterator(), type);
+    }
+
+    protected void notifyListeners(Iterator<ByteArray> keys, UpdateType type) {
+        for (MappingStoreListener msl : listeners) {
+            msl.notify(keys, type);
+        }
+    }
+
+    protected void updateCounter(String counterName) {
+        if (debugCounter != null) {
+            debugCounter.updateCounter(counterName);
+        }
+    }    
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/MappingStoreListener.java b/src/main/java/org/sdnplatform/sync/internal/store/MappingStoreListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..920c60f421e783689d6424e699bbef8d4f68ff3b
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/MappingStoreListener.java
@@ -0,0 +1,90 @@
+package org.sdnplatform.sync.internal.store;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.sdnplatform.sync.IStoreListener;
+import org.sdnplatform.sync.IStoreListener.UpdateType;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+/**
+ * A class that will map from the raw serialized keys to the appropriate key
+ * type for a store listener
+ * @author readams
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class MappingStoreListener {
+    protected static Logger logger =
+            LoggerFactory.getLogger(MappingStoreListener.class);
+
+    TypeReference typeRef;
+    Class keyClass;    
+    IStoreListener listener;
+
+    public MappingStoreListener(TypeReference typeRef, Class keyClass,
+                                IStoreListener listener) {
+        super();
+        this.typeRef = typeRef;
+        this.keyClass = keyClass;
+        this.listener = listener;
+    }
+
+    public void notify(Iterator<ByteArray> keys, UpdateType type) {
+        listener.keysModified(new MappingIterator(keys), type);
+    }
+    
+    class MappingIterator implements Iterator {
+        Iterator<ByteArray> keys;
+        protected Object next;
+
+        public MappingIterator(Iterator<ByteArray> keys) {
+            super();
+            this.keys = keys;
+        }
+
+        private Object map() {
+            try {
+                ByteArray ka = keys.next();
+                Object key = null;
+                if (typeRef != null)
+                    key = JacksonStore.mapper.readValue(ka.get(), typeRef);
+                else if (keyClass != null)
+                    key = JacksonStore.mapper.readValue(ka.get(), keyClass);
+
+                return key;
+            } catch (Exception e) {
+                return null;
+            } 
+        }
+        
+        @Override
+        public boolean hasNext() {
+            if (next != null) return true;
+            while (keys.hasNext()) {
+                next = map();
+                if (next != null) return true;
+            }
+            return false;
+        }
+
+        @Override
+        public Object next() {
+            if (hasNext()) {
+                Object cur = next;
+                next = null;
+                return cur;
+            }
+            throw new NoSuchElementException();            
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+        
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/StoreUtils.java b/src/main/java/org/sdnplatform/sync/internal/store/StoreUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b92dade96666ff6ae0b145eaf41d5b36d34c043
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/StoreUtils.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.store;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.IVersion.Occurred;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.version.VectorClock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * Group of store utilities
+ *
+ */
+public class StoreUtils {
+    protected static final Logger logger =
+            LoggerFactory.getLogger(StoreUtils.class);
+
+    public static void assertValidKeys(Iterable<?> keys) {
+        if(keys == null)
+            throw new IllegalArgumentException("Keys cannot be null.");
+        for(Object key: keys)
+            assertValidKey(key);
+    }
+
+    public static <K> void assertValidKey(K key) {
+        if(key == null)
+            throw new IllegalArgumentException("Key cannot be null.");
+    }
+
+    /**
+     * Implements getAll by delegating to get.
+     * @throws SyncException
+     */
+    public static <K, V> Map<K, List<Versioned<V>>>
+        getAll(IStore<K, V> storageEngine,
+               Iterable<K> keys) throws SyncException {
+        Map<K, List<Versioned<V>>> result = newEmptyHashMap(keys);
+        for(K key: keys) {
+            List<Versioned<V>> value =
+                    storageEngine.get(key);
+            if(!value.isEmpty())
+                result.put(key, value);
+        }
+        return result;
+    }
+
+    /**
+     * Returns an empty map with expected size matching the iterable size if
+     * it's of type Collection. Otherwise, an empty map with the default size is
+     * returned.
+     */
+    public static <K, V> HashMap<K, V> newEmptyHashMap(Iterable<?> iterable) {
+        if(iterable instanceof Collection<?>)
+            return Maps.newHashMapWithExpectedSize(((Collection<?>) iterable).size());
+        return Maps.newHashMap();
+    }
+
+    /**
+     * Closes a Closeable and logs a potential error instead of re-throwing the
+     * exception. If {@code null} is passed, this method is a no-op.
+     *
+     * This is typically used in finally blocks to prevent an exception thrown
+     * during close from hiding an exception thrown inside the try.
+     *
+     * @param c The Closeable to close, may be null.
+     */
+    public static void close(Closeable c) {
+        if(c != null) {
+            try {
+                c.close();
+            } catch(IOException e) {
+                logger.error("Error closing stream", e);
+            }
+        }
+    }
+
+
+    public static <V> List<IVersion> getVersions(List<Versioned<V>> versioneds) {
+        List<IVersion> versions = Lists.newArrayListWithCapacity(versioneds.size());
+        for(Versioned<?> versioned: versioneds)
+            versions.add(versioned.getVersion());
+        return versions;
+    }
+
+    public static <K, V> IClosableIterator<K>
+        keys(final IClosableIterator<Entry<K, V>> values) {
+        return new IClosableIterator<K>() {
+
+            public void close() {
+                values.close();
+            }
+
+            public boolean hasNext() {
+                return values.hasNext();
+            }
+
+            public K next() {
+                Entry<K, V> value = values.next();
+                if(value == null)
+                    return null;
+                return value.getKey();
+            }
+
+            public void remove() {
+                values.remove();
+            }
+
+        };
+    }
+
+    public static <V> boolean canDelete(List<Versioned<V>> items,
+                                         long tombstoneDeletion) {
+        List<VectorClock> tombstones = new ArrayList<VectorClock>();
+        long now = System.currentTimeMillis();
+        // make two passes; first we find tombstones that are old enough.
+        for (Versioned<V> v : items) {
+            if (v.getValue() == null) {
+                VectorClock vc = (VectorClock)v.getVersion();
+                if ((vc.getTimestamp() + tombstoneDeletion) < now)
+                    tombstones.add(vc);
+            }
+        }
+
+        // second, if we find a tombstone which is later than every
+        // non-tombstone value, then we can delete the key.
+        for (VectorClock vc : tombstones) {
+            boolean later = true;
+            for (Versioned<V> v : items) {
+                if (v.getValue() != null) {
+                    VectorClock curvc = (VectorClock)v.getVersion();
+                    if (!Occurred.AFTER.equals(vc.compare(curvc))) {
+                        later = false;
+                        break;
+                    }
+                }
+            }
+            if (later) {
+                // we found a tombstone that's old enough and 
+                // logically later than all non-tombstones.  We can 
+                // remove the value from the map.
+                return true;
+            }
+        }
+        
+        return false;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/SynchronizingStorageEngine.java b/src/main/java/org/sdnplatform/sync/internal/store/SynchronizingStorageEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..689911e547b05df925bfef804ddb15c506532333
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/SynchronizingStorageEngine.java
@@ -0,0 +1,75 @@
+package org.sdnplatform.sync.internal.store;
+
+import net.floodlightcontroller.debugcounter.IDebugCounterService;
+
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.ISyncService.Scope;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * This storage engine will asynchronously replicate its data to the other
+ * nodes in the cluster based on the scope of the s
+ */
+public class SynchronizingStorageEngine extends ListenerStorageEngine {
+
+    protected static Logger logger =
+                LoggerFactory.getLogger(SynchronizingStorageEngine.class);
+
+    /**
+     * The synchronization manager
+     */
+    protected SyncManager syncManager;
+
+    /**
+     * The scope of distribution for data in this store
+     */
+    protected Scope scope;
+
+    /**
+     * Allocate a synchronizing storage engine
+     * @param localStorage the local storage
+     * @param syncManager the sync manager
+     * @param debugCounter the debug counter service
+     * @param scope the scope for this store
+     * @param rpcService the RPC service
+     * @param storeName the name of the store
+     */
+    public SynchronizingStorageEngine(IStorageEngine<ByteArray,
+                                                    byte[]> localStorage,
+                                      SyncManager syncManager,
+                                      IDebugCounterService debugCounter, 
+                                      Scope scope) {
+        super(localStorage, debugCounter);
+        this.localStorage = localStorage;
+        this.syncManager = syncManager;
+        this.scope = scope;
+    }
+
+    // *************************
+    // StorageEngine<Key,byte[]>
+    // *************************
+
+    @Override
+    public void put(ByteArray key, Versioned<byte[]> value)
+            throws SyncException {
+        super.put(key, value);
+        syncManager.queueSyncTask(this, key, value);
+    }
+    
+    // **************
+    // Public methods
+    // **************
+
+    /**
+     * Get the scope for this store
+     * @return
+     */
+    public Scope getScope() {
+        return scope;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/store/VCVersioned.java b/src/main/java/org/sdnplatform/sync/internal/store/VCVersioned.java
new file mode 100644
index 0000000000000000000000000000000000000000..4861892bc7004416a5384eca71f74fc8dbc05e43
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/store/VCVersioned.java
@@ -0,0 +1,23 @@
+package org.sdnplatform.sync.internal.store;
+
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.internal.version.VectorClock;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+
+public final class VCVersioned<T> extends Versioned<T> {
+
+    private static final long serialVersionUID = 8038484251323965062L;
+
+    public VCVersioned(T object) {
+        super(object);
+    }
+
+    @JsonCreator
+    public VCVersioned(@JsonProperty("object") T object,
+                       @JsonProperty("version") VectorClock version) {
+        super(object, version);
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/util/ByteArray.java b/src/main/java/org/sdnplatform/sync/internal/util/ByteArray.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a9473562e4d226fb7e8f5f22e805074bcd90129
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/util/ByteArray.java
@@ -0,0 +1,65 @@
+package org.sdnplatform.sync.internal.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.openflow.util.HexString;
+
+/**
+ * A byte array container that provides an equals and hashCode pair based on the
+ * contents of the byte array. This is useful as a key for Maps.
+ */
+public final class ByteArray implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final ByteArray EMPTY = new ByteArray();
+
+    private final byte[] underlying;
+
+    public ByteArray(byte... underlying) {
+        this.underlying = underlying;
+    }
+
+    public byte[] get() {
+        return underlying;
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(underlying);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if(this == obj)
+            return true;
+        if(!(obj instanceof ByteArray))
+            return false;
+        ByteArray other = (ByteArray) obj;
+        return Arrays.equals(underlying, other.underlying);
+    }
+
+    @Override
+    public String toString() {
+        return Arrays.toString(underlying);
+    }
+
+    /**
+     * Translate the each ByteArray in an iterable into a hexidecimal string
+     *
+     * @param arrays The array of bytes to translate
+     * @return An iterable of converted strings
+     */
+    public static Iterable<String> toHexStrings(Iterable<ByteArray> arrays) {
+        ArrayList<String> ret = new ArrayList<String>();
+        for(ByteArray array: arrays)
+            ret.add(HexString.toHexString(array.get()));
+        return ret;
+    }
+
+    public int length() {
+        return underlying.length;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/util/EmptyClosableIterator.java b/src/main/java/org/sdnplatform/sync/internal/util/EmptyClosableIterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..55554db9297d60bb3aec00bedd9b86218cce6bcd
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/util/EmptyClosableIterator.java
@@ -0,0 +1,26 @@
+package org.sdnplatform.sync.internal.util;
+
+import java.util.NoSuchElementException;
+
+import org.sdnplatform.sync.IClosableIterator;
+
+
+public class EmptyClosableIterator<T> implements IClosableIterator<T> {
+    
+    public boolean hasNext() {
+        return false;
+    }
+
+    public T next() {
+        throw new NoSuchElementException();
+    }
+
+    public void remove() {
+        throw new NoSuchElementException();
+    }
+
+    @Override
+    public void close() {
+        // no-op
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/util/Pair.java b/src/main/java/org/sdnplatform/sync/internal/util/Pair.java
new file mode 100644
index 0000000000000000000000000000000000000000..db78f6dc4812b98715a864102d14760c54b63dde
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/util/Pair.java
@@ -0,0 +1,113 @@
+package org.sdnplatform.sync.internal.util;
+
+import java.io.Serializable;
+import java.util.Map.Entry;
+
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+
+/**
+ * Represents a pair of items.
+ */
+public class Pair<F, S> implements Serializable, Function<F, S>, 
+            Entry<F, S> {
+
+    private static final long serialVersionUID = 1L;
+
+    private final F first;
+
+    private final S second;
+
+    /**
+     * Static factory method that, unlike the constructor, performs generic
+     * inference saving some typing. Use in the following way (for a pair of
+     * Strings):
+     *
+     * <p>
+     * <code>
+     * Pair<String, String> pair = Pair.create("first", "second");
+     * </code>
+     * </p>
+     *
+     * @param <F> The type of the first thing.
+     * @param <S> The type of the second thing
+     * @param first The first thing
+     * @param second The second thing
+     * @return The pair (first,second)
+     */
+    public static final <F, S> Pair<F, S> create(F first, S second) {
+        return new Pair<F, S>(first, second);
+    }
+
+    /**
+     * Use the static factory method {@link #create(Object, Object)} instead of
+     * this where possible.
+     *
+     * @param first
+     * @param second
+     */
+    public Pair(F first, S second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    public S apply(F from) {
+        if(from == null ? first == null : from.equals(first))
+            return second;
+        return null;
+    }
+
+    public final F getFirst() {
+        return first;
+    }
+
+    public final S getSecond() {
+        return second;
+    }
+
+    @Override
+    public final int hashCode() {
+        final int PRIME = 31;
+        int result = 1;
+        result = PRIME * result + ((first == null) ? 0 : first.hashCode());
+        result = PRIME * result + ((second == null) ? 0 : second.hashCode());
+        return result;
+    }
+
+    @Override
+    public final boolean equals(Object obj) {
+        if(this == obj)
+            return true;
+        if(!(obj instanceof Pair<?, ?>))
+            return false;
+
+        final Pair<?, ?> other = (Pair<?, ?>) (obj);
+        return Objects.equal(first, other.first) && Objects.equal(second, other.second);
+    }
+
+    @Override
+    public final String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("[ " + first + ", " + second + " ]");
+        return builder.toString();
+    }
+
+    // *****
+    // Entry
+    // *****
+
+    @Override
+    public F getKey() {
+        return getFirst();
+    }
+
+    @Override
+    public S getValue() {
+        return getSecond();
+    }
+
+    @Override
+    public S setValue(S value) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/version/ChainedResolver.java b/src/main/java/org/sdnplatform/sync/internal/version/ChainedResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3b98d93fe197f842a6988ccd09497eac9d9df2f
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/version/ChainedResolver.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.version;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.sdnplatform.sync.IInconsistencyResolver;
+
+
+/**
+ * Apply the given inconsistency resolvers in order until there are 1 or fewer
+ * items left.
+ *
+ *
+ */
+public class ChainedResolver<T> implements IInconsistencyResolver<T> {
+
+    private List<IInconsistencyResolver<T>> resolvers;
+
+    public ChainedResolver(IInconsistencyResolver<T>... resolvers) {
+        this.resolvers = new ArrayList<IInconsistencyResolver<T>>(resolvers.length);
+        for(IInconsistencyResolver<T> resolver: resolvers)
+            this.resolvers.add(resolver);
+    }
+
+    public List<T> resolveConflicts(List<T> items) {
+        for(IInconsistencyResolver<T> resolver: resolvers) {
+            if(items.size() <= 1)
+                return items;
+            else
+                items = resolver.resolveConflicts(items);
+        }
+
+        return items;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if(this == o)
+            return true;
+        if(o == null || getClass() != o.getClass())
+            return false;
+
+        ChainedResolver<?> that = (ChainedResolver<?>) o;
+
+        if(resolvers != null
+                ? !resolvers.equals(that.resolvers)
+                : that.resolvers != null)
+            return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return resolvers != null ? resolvers.hashCode() : 0;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/version/ClockEntry.java b/src/main/java/org/sdnplatform/sync/internal/version/ClockEntry.java
new file mode 100644
index 0000000000000000000000000000000000000000..058a1cacd7658aa26f0f9ee486188790e1205c64
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/version/ClockEntry.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2008-2009 LinkedIn, Inc
+ * Copyright 2013 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 org.sdnplatform.sync.internal.version;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * An entry element for a vector clock versioning scheme This assigns the
+ * version from a specific machine, the VectorClock keeps track of the complete
+ * system version, which will consist of many individual Version objects.
+ *
+ *
+ */
+public final class ClockEntry
+    implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = -759862327985468981L;
+
+    private final short nodeId;
+    private final long version;
+
+    /**
+     * Create a new Version from constituate parts
+     *
+     * @param nodeId The node id
+     * @param version The current version
+     */
+    @JsonCreator
+    public ClockEntry(@JsonProperty("nodeId") short nodeId, 
+                      @JsonProperty("version") long version) {
+        if(nodeId < 0)
+            throw new IllegalArgumentException("Node id " + nodeId + " is not in the range (0, "
+                                               + Short.MAX_VALUE + ").");
+        if(version < 1)
+            throw new IllegalArgumentException("Version " + version + " is not in the range (1, "
+                                               + Short.MAX_VALUE + ").");
+        this.nodeId = nodeId;
+        this.version = version;
+    }
+
+    @Override
+    public ClockEntry clone() {
+        try {
+            return (ClockEntry) super.clone();
+        } catch(CloneNotSupportedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public short getNodeId() {
+        return nodeId;
+    }
+
+    public long getVersion() {
+        return version;
+    }
+
+    public ClockEntry incremented() {
+        return new ClockEntry(nodeId, version + 1);
+    }
+
+    @Override
+    public int hashCode() {
+        return nodeId + (((int) version) << 16);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if(this == o)
+            return true;
+
+        if(o == null)
+            return false;
+
+        if(o.getClass().equals(ClockEntry.class)) {
+            ClockEntry v = (ClockEntry) o;
+            return v.getNodeId() == getNodeId() && v.getVersion() == getVersion();
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return nodeId + ":" + version;
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/version/TimeBasedInconsistencyResolver.java b/src/main/java/org/sdnplatform/sync/internal/version/TimeBasedInconsistencyResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..75b0c80530dc8d242eb3c76dba7fab9bf39391f3
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/version/TimeBasedInconsistencyResolver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2008-2009 LinkedIn, Inc
+ * Copyright 2013 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 org.sdnplatform.sync.internal.version;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.sdnplatform.sync.IInconsistencyResolver;
+import org.sdnplatform.sync.Versioned;
+
+
+/**
+ * Resolve inconsistencies based on timestamp in the vector clock
+ * @param <T> The type f the versioned object
+ */
+public class TimeBasedInconsistencyResolver<T>
+    implements IInconsistencyResolver<Versioned<T>> {
+
+    public List<Versioned<T>> resolveConflicts(List<Versioned<T>> items) {
+        if(items.size() <= 1) {
+            return items;
+        } else {
+            Versioned<T> max = items.get(0);
+            long maxTime =
+                    ((VectorClock) items.get(0).getVersion()).getTimestamp();
+            VectorClock maxClock = ((VectorClock) items.get(0).getVersion());
+            for(Versioned<T> versioned: items) {
+                VectorClock clock = (VectorClock) versioned.getVersion();
+                if(clock.getTimestamp() > maxTime) {
+                    max = versioned;
+                    maxTime = ((VectorClock) versioned.getVersion()).
+                            getTimestamp();
+                }
+                maxClock = maxClock.merge(clock);
+            }
+            Versioned<T> maxTimeClockVersioned =
+                    new Versioned<T>(max.getValue(), maxClock);
+            return Collections.singletonList(maxTimeClockVersioned);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if(this == o)
+            return true;
+        return (o != null && getClass() == o.getClass());
+    }
+
+    @Override
+    public int hashCode() {
+        return getClass().hashCode();
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/version/VectorClock.java b/src/main/java/org/sdnplatform/sync/internal/version/VectorClock.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ad1b5bf3dff01fb03d8f75d9fe6a2373185fcf2
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/version/VectorClock.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2008-2009 LinkedIn, Inc
+ * Copyright 2013 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 org.sdnplatform.sync.internal.version;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.sdnplatform.sync.IVersion;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import com.google.common.collect.Lists;
+
+/**
+ * A vector of the number of writes mastered by each node. The vector is stored
+ * sparely, since, in general, writes will be mastered by only one node. This
+ * means implicitly all the versions are at zero, but we only actually store
+ * those greater than zero.
+ */
+public class VectorClock implements IVersion, Serializable, Cloneable {
+
+    private static final long serialVersionUID = 7663945747147638702L;
+
+    private static final int MAX_NUMBER_OF_VERSIONS = Short.MAX_VALUE;
+
+    /* A sorted list of live versions ordered from least to greatest */
+    private final List<ClockEntry> versions;
+
+    /*
+     * The time of the last update on the server on which the update was
+     * performed
+     */
+    private final long timestamp;
+
+    /**
+     * Construct an empty VectorClock
+     */
+    public VectorClock() {
+        this(new ArrayList<ClockEntry>(0), System.currentTimeMillis());
+    }
+
+    public VectorClock(long timestamp) {
+        this(new ArrayList<ClockEntry>(0), timestamp);
+    }
+
+    /**
+     * Create a VectorClock with the given version and timestamp
+     *
+     * @param versions The version to prepopulate
+     * @param timestamp The timestamp to prepopulate
+     */
+    @JsonCreator
+    public VectorClock(@JsonProperty("entries") List<ClockEntry> versions, 
+                       @JsonProperty("timestamp") long timestamp) {
+        this.versions = versions;
+        this.timestamp = timestamp;
+    }
+
+    /**
+     * Get new vector clock based on this clock but incremented on index nodeId
+     *
+     * @param nodeId The id of the node to increment
+     * @return A vector clock equal on each element execept that indexed by
+     *         nodeId
+     */
+    public VectorClock incremented(int nodeId, long time) {
+        if(nodeId < 0 || nodeId > Short.MAX_VALUE)
+            throw new IllegalArgumentException(nodeId
+                                               + " is outside the acceptable range of node ids.");
+
+        // stop on the index greater or equal to the node
+        List<ClockEntry> newversions = Lists.newArrayList(versions);
+        boolean found = false;
+        int index = 0;
+        for(; index < newversions.size(); index++) {
+            if(newversions.get(index).getNodeId() == nodeId) {
+                found = true;
+                break;
+            } else if(newversions.get(index).getNodeId() > nodeId) {
+                found = false;
+                break;
+            }
+        }
+
+        if(found) {
+            newversions.set(index, newversions.get(index).incremented());
+        } else if(index < newversions.size() - 1) {
+            newversions.add(index, new ClockEntry((short) nodeId, 1));
+        } else {
+            // we don't already have a version for this, so add it
+            if(newversions.size() > MAX_NUMBER_OF_VERSIONS)
+                throw new IllegalStateException("Vector clock is full!");
+            newversions.add(index, new ClockEntry((short) nodeId, 1));
+        }
+
+        return new VectorClock(newversions, time);
+    }
+
+    @Override
+    public VectorClock clone() {
+        return new VectorClock(Lists.newArrayList(versions), this.timestamp);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (int) (timestamp ^ (timestamp >>> 32));
+        result = prime * result
+                 + ((versions == null) ? 0 : versions.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;
+        VectorClock other = (VectorClock) obj;
+        if (timestamp != other.timestamp) return false;
+        if (versions == null) {
+            if (other.versions != null) return false;
+        } else if (!versions.equals(other.versions)) return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("version(");
+        if(this.versions.size() > 0) {
+            for(int i = 0; i < this.versions.size() - 1; i++) {
+                builder.append(this.versions.get(i));
+                builder.append(", ");
+            }
+            builder.append(this.versions.get(this.versions.size() - 1));
+        }
+        builder.append(")");
+        builder.append(" ts:" + timestamp);
+        return builder.toString();
+    }
+
+    @JsonIgnore
+    public long getMaxVersion() {
+        long max = -1;
+        for(ClockEntry entry: versions)
+            max = Math.max(entry.getVersion(), max);
+        return max;
+    }
+
+    public VectorClock merge(VectorClock clock) {
+        VectorClock newClock = new VectorClock();
+        int i = 0;
+        int j = 0;
+        while(i < this.versions.size() && j < clock.versions.size()) {
+            ClockEntry v1 = this.versions.get(i);
+            ClockEntry v2 = clock.versions.get(j);
+            if(v1.getNodeId() == v2.getNodeId()) {
+                newClock.versions.add(new ClockEntry(v1.getNodeId(), Math.max(v1.getVersion(),
+                                                                              v2.getVersion())));
+                i++;
+                j++;
+            } else if(v1.getNodeId() < v2.getNodeId()) {
+                newClock.versions.add(v1.clone());
+                i++;
+            } else {
+                newClock.versions.add(v2.clone());
+                j++;
+            }
+        }
+
+        // Okay now there may be leftovers on one or the other list remaining
+        for(int k = i; k < this.versions.size(); k++)
+            newClock.versions.add(this.versions.get(k).clone());
+        for(int k = j; k < clock.versions.size(); k++)
+            newClock.versions.add(clock.versions.get(k).clone());
+
+        return newClock;
+    }
+
+    @Override
+    public Occurred compare(IVersion v) {
+        if(!(v instanceof VectorClock))
+            throw new IllegalArgumentException("Cannot compare Versions of different types.");
+
+        return compare(this, (VectorClock) v);
+    }
+
+    /**
+     * Is this Reflexive, AntiSymetic, and Transitive? Compare two VectorClocks,
+     * the outcomes will be one of the following: -- Clock 1 is BEFORE clock 2
+     * if there exists an i such that c1(i) <= c(2) and there does not exist a j
+     * such that c1(j) > c2(j). -- Clock 1 is CONCURRENT to clock 2 if there
+     * exists an i, j such that c1(i) < c2(i) and c1(j) > c2(j) -- Clock 1 is
+     * AFTER clock 2 otherwise
+     *
+     * @param v1 The first VectorClock
+     * @param v2 The second VectorClock
+     */
+    public static Occurred compare(VectorClock v1, VectorClock v2) {
+        if(v1 == null || v2 == null)
+            throw new IllegalArgumentException("Can't compare null vector clocks!");
+        // We do two checks: v1 <= v2 and v2 <= v1 if both are true then
+        boolean v1Bigger = false;
+        boolean v2Bigger = false;
+        int p1 = 0;
+        int p2 = 0;
+
+        while(p1 < v1.versions.size() && p2 < v2.versions.size()) {
+            ClockEntry ver1 = v1.versions.get(p1);
+            ClockEntry ver2 = v2.versions.get(p2);
+            if(ver1.getNodeId() == ver2.getNodeId()) {
+                if(ver1.getVersion() > ver2.getVersion())
+                    v1Bigger = true;
+                else if(ver2.getVersion() > ver1.getVersion())
+                    v2Bigger = true;
+                p1++;
+                p2++;
+            } else if(ver1.getNodeId() > ver2.getNodeId()) {
+                // since ver1 is bigger that means it is missing a version that
+                // ver2 has
+                v2Bigger = true;
+                p2++;
+            } else {
+                // this means ver2 is bigger which means it is missing a version
+                // ver1 has
+                v1Bigger = true;
+                p1++;
+            }
+        }
+
+        /* Okay, now check for left overs */
+        if(p1 < v1.versions.size())
+            v1Bigger = true;
+        else if(p2 < v2.versions.size())
+            v2Bigger = true;
+
+        /* This is the case where they are equal, return BEFORE arbitrarily */
+        if(!v1Bigger && !v2Bigger)
+            return Occurred.BEFORE;
+        /* This is the case where v1 is a successor clock to v2 */
+        else if(v1Bigger && !v2Bigger)
+            return Occurred.AFTER;
+        /* This is the case where v2 is a successor clock to v1 */
+        else if(!v1Bigger && v2Bigger)
+            return Occurred.BEFORE;
+        /* This is the case where both clocks are parallel to one another */
+        else
+            return Occurred.CONCURRENTLY;
+    }
+
+    public long getTimestamp() {
+        return this.timestamp;
+    }
+
+    public List<ClockEntry> getEntries() {
+        return Collections.unmodifiableList(this.versions);
+    }
+}
diff --git a/src/main/java/org/sdnplatform/sync/internal/version/VectorClockInconsistencyResolver.java b/src/main/java/org/sdnplatform/sync/internal/version/VectorClockInconsistencyResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..a50779bb27e78e2346a8bdb916412acfc139b979
--- /dev/null
+++ b/src/main/java/org/sdnplatform/sync/internal/version/VectorClockInconsistencyResolver.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.version;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import org.sdnplatform.sync.IInconsistencyResolver;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.IVersion.Occurred;
+
+import com.google.common.collect.Lists;
+
+/**
+ * An inconsistency resolver that uses the object VectorClocks leaving only a
+ * set of concurrent versions remaining.
+ *
+ *
+ */
+public class VectorClockInconsistencyResolver<T>
+    implements IInconsistencyResolver<Versioned<T>> {
+
+    public List<Versioned<T>> resolveConflicts(List<Versioned<T>> items) {
+        int size = items.size();
+        if(size <= 1)
+            return items;
+
+        List<Versioned<T>> newItems = Lists.newArrayList();
+        for(Versioned<T> v1: items) {
+            boolean found = false;
+            for(ListIterator<Versioned<T>> it2 =
+                    newItems.listIterator(); it2.hasNext();) {
+                Versioned<T> v2 = it2.next();
+                Occurred compare = v1.getVersion().compare(v2.getVersion());
+                if(compare == Occurred.AFTER) {
+                    if(found)
+                        it2.remove();
+                    else
+                        it2.set(v1);
+                }
+                if(compare != Occurred.CONCURRENTLY)
+                    found = true;
+            }
+            if(!found)
+                newItems.add(v1);
+        }
+        return newItems;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        return (o != null && getClass() == o.getClass());
+    }
+
+    @Override
+    public int hashCode() {
+        return getClass().hashCode();
+    }
+}
diff --git a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
index dc38c05b97eed015c76e1f6ea56ae3b1099130be..4f2f68440b700de96a3c5690f857a13dc560b6a4 100644
--- a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
+++ b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
@@ -26,4 +26,6 @@ net.floodlightcontroller.devicemanager.test.MockDeviceManager
 net.floodlightcontroller.core.test.MockFloodlightProvider
 net.floodlightcontroller.core.test.MockThreadPoolService
 net.floodlightcontroller.firewall.Firewall
-net.floodlightcontroller.loadbalancer.LoadBalancer
\ No newline at end of file
+net.floodlightcontroller.loadbalancer.LoadBalancer
+org.sdnplatform.sync.internal.SyncManager
+org.sdnplatform.sync.internal.SyncTorture
diff --git a/src/main/resources/floodlightdefault.properties b/src/main/resources/floodlightdefault.properties
index 2d384d3a1b6e39accfcd56839588cbfc1fb9bdbe..620471ab9fb0491700e9980e7f98675942dcd00d 100644
--- a/src/main/resources/floodlightdefault.properties
+++ b/src/main/resources/floodlightdefault.properties
@@ -15,7 +15,8 @@ net.floodlightcontroller.counter.CounterStore,\
 net.floodlightcontroller.debugcounter.DebugCounter,\
 net.floodlightcontroller.perfmon.PktInProcessingTime,\
 net.floodlightcontroller.ui.web.StaticWebRoutable,\
-net.floodlightcontroller.loadbalancer.LoadBalancer
+net.floodlightcontroller.loadbalancer.LoadBalancer,\
+org.sdnplatform.sync.internal.SyncManager
 net.floodlightcontroller.restserver.RestApiServer.port = 8080
 net.floodlightcontroller.core.FloodlightProvider.openflowport = 6633
 net.floodlightcontroller.jython.JythonDebugInterface.port = 6655
diff --git a/src/main/thrift/sync.thrift b/src/main/thrift/sync.thrift
new file mode 100644
index 0000000000000000000000000000000000000000..f7f766520e772232a7f3debde07f2b1afc4b6153
--- /dev/null
+++ b/src/main/thrift/sync.thrift
@@ -0,0 +1,218 @@
+#
+# Interface definition for sync
+#
+
+namespace java org.sdnplatform.sync.thrift
+namespace cpp org.sdnplatform.sync.thrift
+namespace py sync
+namespace php sync
+namespace perl sync
+
+const string VERSION = "1.0.0"
+
+#
+# data structures
+#
+
+struct ClockEntry {
+  1: required i16 nodeId,
+  2: required i64 version
+}
+
+struct VectorClock {
+  1: optional list<ClockEntry> versions,
+  2: optional i64 timestamp
+}
+
+struct VersionedValue {
+  1: optional binary value,
+  2: required VectorClock version
+}
+
+struct SyncError {
+  1: i32 errorCode,
+  2: string message
+}
+
+struct KeyedValues {
+  1: required binary key,
+  2: required list<VersionedValue> values
+}
+
+struct KeyedVersions {
+  1: required binary key,
+  2: required list<VectorClock> versions
+}
+
+struct AsyncMessageHeader {
+  1: optional i32 transactionId,
+}
+
+enum Scope {
+  GLOBAL = 0
+  LOCAL = 1,
+}
+
+struct Store {
+  1: required string storeName,
+  2: optional Scope scope,
+  3: optional bool persist
+}
+
+#
+# Protocol messages
+#
+
+enum MessageType {
+  HELLO = 1,
+  ERROR = 2,
+  ECHO_REQUEST = 3,
+  ECHO_REPLY = 4,
+  GET_REQUEST = 5,
+  GET_RESPONSE = 6,
+  PUT_REQUEST = 7,
+  PUT_RESPONSE = 8,
+  DELETE_REQUEST = 9,
+  DELETE_RESPONSE = 10,
+  SYNC_VALUE = 11,
+  SYNC_VALUE_RESPONSE = 12,
+  SYNC_OFFER = 13,
+  SYNC_REQUEST = 14,
+  FULL_SYNC_REQUEST = 15,
+  CURSOR_REQUEST = 16,
+  CURSOR_RESPONSE = 17,
+  REGISTER_REQUEST = 18,
+  REGISTER_RESPONSE = 19,
+}
+
+struct HelloMessage {
+  1: required AsyncMessageHeader header,
+  2: optional i16 nodeId,
+}
+
+struct ErrorMessage {
+  1: required AsyncMessageHeader header,
+  2: optional SyncError error,
+  3: optional MessageType type
+}
+
+struct EchoRequestMessage {
+  1: required AsyncMessageHeader header
+}
+
+struct EchoReplyMessage {
+  1: required AsyncMessageHeader header
+}
+
+struct GetRequestMessage {
+  1: required AsyncMessageHeader header
+  2: required string storeName,
+  3: required binary key
+}
+
+struct GetResponseMessage {
+  1: required AsyncMessageHeader header
+  2: list<VersionedValue> values,
+  3: optional SyncError error
+}
+
+struct PutRequestMessage {
+  1: required AsyncMessageHeader header
+  2: required string storeName,
+  3: required binary key,
+  4: optional VersionedValue versionedValue,
+  5: optional binary value,
+}
+
+struct PutResponseMessage {
+  1: required AsyncMessageHeader header
+}
+
+struct DeleteRequestMessage {
+  1: required AsyncMessageHeader header
+  2: required string storeName,
+  3: required binary key,
+  4: optional VectorClock version
+}
+
+struct DeleteResponseMessage {
+  1: optional AsyncMessageHeader header,
+  2: optional bool deleted
+}
+
+struct SyncValueMessage {
+  1: required AsyncMessageHeader header,
+  2: required Store store,
+  3: list<KeyedValues> values,
+  4: optional i32 responseTo
+}
+
+struct SyncValueResponseMessage {
+  1: required AsyncMessageHeader header,
+  2: optional i32 count
+}
+
+struct SyncOfferMessage {
+  1: required AsyncMessageHeader header,
+  2: required Store store,
+  3: list<KeyedVersions> versions
+}
+
+struct SyncRequestMessage {
+  1: required AsyncMessageHeader header,
+  2: required Store store,
+  3: optional list<binary> keys
+}
+
+struct FullSyncRequestMessage {
+  1: required AsyncMessageHeader header,
+}
+
+struct CursorRequestMessage {
+  1: required AsyncMessageHeader header,
+  2: optional string storeName,
+  3: optional i32 cursorId,
+  4: optional bool close
+}
+
+struct CursorResponseMessage {
+  1: required AsyncMessageHeader header,
+  2: required i32 cursorId,
+  3: list<KeyedValues> values
+}
+
+struct RegisterRequestMessage {
+  1: required AsyncMessageHeader header,
+  2: required Store store
+}
+
+struct RegisterResponseMessage {
+  1: required AsyncMessageHeader header,
+}
+
+#
+# Message wrapper
+#
+
+struct SyncMessage {
+  1: required MessageType type,
+  2: optional HelloMessage hello,
+  3: optional ErrorMessage error,
+  4: optional EchoRequestMessage echoRequest,
+  5: optional EchoReplyMessage echoReply,
+  6: optional GetRequestMessage getRequest,
+  7: optional GetResponseMessage getResponse,
+  8: optional PutRequestMessage putRequest,
+  9: optional PutResponseMessage putResponse,
+  10: optional DeleteRequestMessage deleteRequest,
+  11: optional DeleteResponseMessage deleteResponse,
+  12: optional SyncValueMessage syncValue,
+  13: optional SyncValueResponseMessage syncValueResponse,
+  14: optional SyncOfferMessage syncOffer,
+  15: optional SyncRequestMessage syncRequest,
+  16: optional FullSyncRequestMessage fullSyncRequest,
+  17: optional CursorRequestMessage cursorRequest,
+  18: optional CursorResponseMessage cursorResponse,
+  19: optional RegisterRequestMessage registerRequest,
+  20: optional RegisterResponseMessage registerResponse
+}
\ No newline at end of file
diff --git a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java
index e21239ab92dfe01a36bcf349a620ae10a97cec56..e25ba585f9b4281452f3a956d50fbabeb1ef53c7 100644
--- a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java
+++ b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java
@@ -217,7 +217,7 @@ public class ControllerTest extends FloodlightTestCase
         byte[] testPacketSerialized = testPacket.serialize();
 
         // Build the PacketIn
-        OFPacketIn pi = ((OFPacketIn) new BasicFactory().getMessage(OFType.PACKET_IN))
+        OFPacketIn pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN))
                 .setBufferId(-1)
                 .setInPort((short) 1)
                 .setPacketData(testPacketSerialized)
@@ -425,7 +425,7 @@ public class ControllerTest extends FloodlightTestCase
         byte[] testPacketSerialized = testPacket.serialize();
 
         // Build the PacketIn
-        OFPacketIn pi = ((OFPacketIn) new BasicFactory().getMessage(OFType.PACKET_IN))
+        OFPacketIn pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN))
                 .setBufferId(-1)
                 .setInPort((short) 1)
                 .setPacketData(testPacketSerialized)
@@ -1467,7 +1467,7 @@ public class ControllerTest extends FloodlightTestCase
         byte[] testPacketSerialized = testPacket.serialize();
 
         // Build the PacketIn
-        OFPacketIn pi = ((OFPacketIn) new BasicFactory().getMessage(OFType.PACKET_IN))
+        OFPacketIn pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN))
                 .setBufferId(-1)
                 .setInPort((short) 1)
                 .setPacketData(testPacketSerialized)
@@ -1662,7 +1662,7 @@ public class ControllerTest extends FloodlightTestCase
                 byte[] testPacketSerialized = testPacket.serialize();
 
                 // Build the PacketIn
-                pi = ((OFPacketIn) new BasicFactory().getMessage(OFType.PACKET_IN))
+                pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN))
                         .setBufferId(-1)
                         .setInPort((short) 1)
                         .setPacketData(testPacketSerialized)
diff --git a/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java b/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java
index 40d1f7b59671e132d390d36a67979b6142100c6e..7148b472fa58c81cb03f26c22883ed6407dbe31b 100644
--- a/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java
+++ b/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java
@@ -62,7 +62,7 @@ public class RoleChangerTest {
     public void setUp() throws Exception {
         controller = createMock(Controller.class);
         roleChanger = new RoleChanger(controller);
-        BasicFactory factory = new BasicFactory();
+        BasicFactory factory = BasicFactory.getInstance();
         expect(controller.getOFMessageFactory()).andReturn(factory).anyTimes();
     }
     
diff --git a/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java b/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java
index 7b5cd6293c1888250299c6352918c610df70733c..78139acfba14f6d2fb61ec8092332be3dfe0ed0c 100644
--- a/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java
+++ b/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java
@@ -77,7 +77,7 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro
         switches = new ConcurrentHashMap<Long, IOFSwitch>();
         switchListeners = new CopyOnWriteArrayList<IOFSwitchListener>();
         haListeners = new CopyOnWriteArrayList<IHAListener>();
-        factory = new BasicFactory();
+        factory = BasicFactory.getInstance();
     }
 
     @Override
diff --git a/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java b/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java
index 99b1ecf2537ccdc80ab2f6caf622d68222dfbb56..07f8c3dd5295233dde8e5405816df12cc17bc031 100644
--- a/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java
+++ b/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java
@@ -41,7 +41,7 @@ import org.openflow.protocol.factory.BasicFactory;
 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();
+    protected static BasicFactory OFMessageFactory = BasicFactory.getInstance();
     
     /**
      * Generates a DHCP request OFPacketIn.
diff --git a/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java b/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java
index b9397b603b2585af1adcb9bcf51ef9db38958f14..566493a7075a5c921c664a819ca776b1fc0f6614 100644
--- a/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java
+++ b/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java
@@ -111,6 +111,7 @@ public class LinkDiscoveryManagerTest extends FloodlightTestCase {
         return mockSwitch;
     }
 
+    @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
@@ -525,7 +526,7 @@ public class LinkDiscoveryManagerTest extends FloodlightTestCase {
         byte[] testPacketSerialized = testPacket.serialize();
         OFPacketIn pi;
         // build out input packet
-        pi = ((OFPacketIn) new BasicFactory().getMessage(OFType.PACKET_IN))
+        pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN))
                 .setBufferId(-1)
                 .setInPort((short) 1)
                 .setPacketData(testPacketSerialized)
diff --git a/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java b/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java
index 27c6c4444645a60698ef2aa76b548848f70a61d3..84db817f747542c1d5b5ef3f04e2d0061657dd5f 100644
--- a/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java
+++ b/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java
@@ -50,7 +50,7 @@ public class OFMessageDamperTest {
     
     @Before
     public void setUp() throws IOException {
-        factory = new BasicFactory();
+        factory = BasicFactory.getInstance();
         cntx = new FloodlightContext();
         
         sw1 = new OFMessageDamperMockSwitch();
diff --git a/src/test/java/org/openflow/protocol/BasicFactoryTest.java b/src/test/java/org/openflow/protocol/BasicFactoryTest.java
index 312fcd3f57d616c6716e7f702e56a086a44609d0..350d4be39c3f1148c7a6c4191085bbe8bb9132a5 100644
--- a/src/test/java/org/openflow/protocol/BasicFactoryTest.java
+++ b/src/test/java/org/openflow/protocol/BasicFactoryTest.java
@@ -37,7 +37,7 @@ import org.openflow.util.U16;
 public class BasicFactoryTest extends TestCase {
 
     public void testCreateAndParse() throws MessageParseException {
-        BasicFactory factory = new BasicFactory();
+        BasicFactory factory = BasicFactory.getInstance();
         OFMessage m = factory.getMessage(OFType.HELLO);
         m.setVersion((byte) 1);
         m.setType(OFType.ECHO_REQUEST);
@@ -56,7 +56,7 @@ public class BasicFactoryTest extends TestCase {
     }
 
     public void testInvalidMsgParse() throws MessageParseException {
-        BasicFactory factory = new BasicFactory();
+        BasicFactory factory = BasicFactory.getInstance();
         OFMessage m = factory.getMessage(OFType.HELLO);
         m.setVersion((byte) 1);
         m.setType(OFType.ECHO_REQUEST);
@@ -69,7 +69,7 @@ public class BasicFactoryTest extends TestCase {
     }
 
     public void testCurrouptedMsgParse() throws MessageParseException {
-        BasicFactory factory = new BasicFactory();
+        BasicFactory factory = BasicFactory.getInstance();
         OFMessage m = factory.getMessage(OFType.HELLO);
         m.setVersion((byte) 1);
         m.setType(OFType.ERROR);
@@ -86,7 +86,7 @@ public class BasicFactoryTest extends TestCase {
     }
 
     public void testCustomVendorAction() throws MessageParseException {
-        BasicFactory factory = new BasicFactory();
+        BasicFactory factory = BasicFactory.getInstance();
         OFVendorActionRegistry.getInstance().register(
                 MockVendorAction.VENDOR_ID, new MockVendorActionFactory());
 
@@ -119,7 +119,7 @@ public class BasicFactoryTest extends TestCase {
                 0x05, 0x06, 0x07, 0x08               // pad
             };
 
-        BasicFactory factory = new BasicFactory();
+        BasicFactory factory = BasicFactory.getInstance();
         OFVendorActionRegistry.getInstance().register(
                 MockVendorAction.VENDOR_ID, new MockVendorActionFactory());
 
diff --git a/src/test/java/org/openflow/protocol/OFErrorTest.java b/src/test/java/org/openflow/protocol/OFErrorTest.java
index 45d52576e75c2760d1499497964258f42457564c..f4a57269826d81cf17b47b547e4b7588afeb873f 100644
--- a/src/test/java/org/openflow/protocol/OFErrorTest.java
+++ b/src/test/java/org/openflow/protocol/OFErrorTest.java
@@ -34,14 +34,14 @@ 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.setErrorType(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(),
+        TestCase.assertEquals(OFErrorType.OFPET_HELLO_FAILED.getValue(),
                 msg.getErrorType());
         TestCase.assertEquals((short) OFHelloFailedCode.OFPHFC_INCOMPATIBLE
                 .ordinal(), msg.getErrorType());
@@ -51,7 +51,7 @@ public class OFErrorTest extends OFTestCase {
         bb.clear();
         msg.writeTo(bb);
         msg.readFrom(bb);
-        TestCase.assertEquals((short) OFErrorType.OFPET_HELLO_FAILED.getValue(),
+        TestCase.assertEquals(OFErrorType.OFPET_HELLO_FAILED.getValue(),
                 msg.getErrorType());
         TestCase.assertEquals((short) OFHelloFailedCode.OFPHFC_INCOMPATIBLE
                 .ordinal(), msg.getErrorType());
@@ -74,7 +74,7 @@ public class OFErrorTest extends OFTestCase {
                 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();
+        OFMessageFactory factory = BasicFactory.getInstance();
         ChannelBuffer oferrorBuf = 
                 ChannelBuffers.wrappedBuffer(oferrorRaw);
         List<OFMessage> msg = factory.parseMessage(oferrorBuf);
diff --git a/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java b/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java
index 50bac8f711131923bd4f0ff27488d23f71b1b2e3..1885cae7b3d4731ccae5bfbe4fdb295951b1a760 100644
--- a/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java
+++ b/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java
@@ -64,7 +64,7 @@ public class OFStatisticsReplyTest extends OFTestCase {
                 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xc4, 0x00, 0x00, 0x00,
                 0x08, 0x00, 0x02, 0x00, 0x00 };
 
-        OFMessageFactory factory = new BasicFactory();
+        OFMessageFactory factory = BasicFactory.getInstance();
         ChannelBuffer packetBuf = ChannelBuffers.wrappedBuffer(packet);
         List<OFMessage> msg = factory.parseMessage(packetBuf);
         TestCase.assertNotNull(msg);
diff --git a/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java b/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java
index 74af6f41d213593a746639132a320b1b9e81f1c6..fceb8955eb755a12bb03c68bd1102513874b06ab 100644
--- a/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java
+++ b/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java
@@ -40,7 +40,7 @@ public class OFStatisticsRequestTest extends OFTestCase {
                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 (byte) 0xff, 0x00, (byte) 0xff, (byte) 0xff };
 
-        OFMessageFactory factory = new BasicFactory();
+        OFMessageFactory factory = BasicFactory.getInstance();
         ChannelBuffer packetBuf = ChannelBuffers.wrappedBuffer(packet);
         List<OFMessage> msg = factory.parseMessage(packetBuf);
         TestCase.assertNotNull(msg);
@@ -64,7 +64,7 @@ public class OFStatisticsRequestTest extends OFTestCase {
                 0x00, 0x00, 0x00, 0x00, (byte) 0xff, 0x00, 0x00, 0x00,
                 (byte) 0xff, (byte) 0xff, 0x4e, 0x20 };
 
-        OFMessageFactory factory = new BasicFactory();
+        OFMessageFactory factory = BasicFactory.getInstance();
         ChannelBuffer packetBuf = ChannelBuffers.wrappedBuffer(packet);
         List<OFMessage> msg = factory.parseMessage(packetBuf);
         TestCase.assertNotNull(msg);
diff --git a/src/test/java/org/openflow/protocol/OFVendorTest.java b/src/test/java/org/openflow/protocol/OFVendorTest.java
index b85a915a9f76898d4bfcdd3f06e0607aa926ed1d..c0385f4415824d1c79408e93ac31de4be40f3e52 100644
--- a/src/test/java/org/openflow/protocol/OFVendorTest.java
+++ b/src/test/java/org/openflow/protocol/OFVendorTest.java
@@ -38,14 +38,17 @@ public class OFVendorTest extends OFTestCase {
     static class AcmeVendorData implements OFVendorData {
         protected int dataType;
         
+        @Override
         public int getLength() {
             return 4;
         }
         
+        @Override
         public void readFrom(ChannelBuffer data, int length) {
             dataType = data.readInt();
         }
         
+        @Override
         public void writeTo(ChannelBuffer data) {
             data.writeInt(dataType);
         }
@@ -74,16 +77,19 @@ public class OFVendorTest extends OFTestCase {
             return value;
         }
         
+        @Override
         public int getLength() {
             return 8;
         }
         
+        @Override
         public void readFrom(ChannelBuffer data, int length) {
             super.readFrom(data, length);
             flags = data.readShort();
             value = data.readShort();
 
         }
+        @Override
         public void writeTo(ChannelBuffer data) {
             super.writeTo(data);
             data.writeShort(flags);
@@ -92,6 +98,7 @@ public class OFVendorTest extends OFTestCase {
         
         public static Instantiable<OFVendorData> getInstantiable() {
             return new Instantiable<OFVendorData>() {
+                @Override
                 public OFVendorData instantiate() {
                     return new AcmeVendorData1();
                 }
@@ -122,16 +129,19 @@ public class OFVendorTest extends OFTestCase {
             return subtype;
         }
         
+        @Override
         public int getLength() {
             return 12;
         }
         
+        @Override
         public void readFrom(ChannelBuffer data, int length) {
             super.readFrom(data, length);
             type = data.readShort();
             subtype = data.readShort();
 
         }
+        @Override
         public void writeTo(ChannelBuffer data) {
             super.writeTo(data);
             data.writeShort(type);
@@ -140,6 +150,7 @@ public class OFVendorTest extends OFTestCase {
         
         public static Instantiable<OFVendorData> getInstantiable() {
             return new Instantiable<OFVendorData>() {
+                @Override
                 public OFVendorData instantiate() {
                     return new AcmeVendorData2();
                 }
@@ -160,7 +171,7 @@ public class OFVendorTest extends OFTestCase {
     
     private OFVendor makeVendorMessage(int vendor) {
         OFVendor msg = (OFVendor) messageFactory.getMessage(OFType.VENDOR);
-        msg.setVendorDataFactory(new BasicFactory());
+        msg.setVendorDataFactory(BasicFactory.getInstance());
         msg.setVendor(vendor);
         return msg;
     }
diff --git a/src/test/java/org/openflow/util/OFTestCase.java b/src/test/java/org/openflow/util/OFTestCase.java
index 5132c2c7007e1dbc6c86e0adf77afedcc28c5a12..07bee2d589a1901ad1f77afc3a2ee262a48be429 100644
--- a/src/test/java/org/openflow/util/OFTestCase.java
+++ b/src/test/java/org/openflow/util/OFTestCase.java
@@ -28,7 +28,7 @@ public class OFTestCase extends TestCase {
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        messageFactory = new BasicFactory();
+        messageFactory = BasicFactory.getInstance();
     }
 
     public void test() throws Exception {
diff --git a/src/test/java/org/sdnplatform/sync/VersionedTest.java b/src/test/java/org/sdnplatform/sync/VersionedTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c2c4433db1330a03af7363ec52beef714bddeb4
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/VersionedTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.internal.TUtils;
+
+
+public class VersionedTest {
+
+    private Versioned<Integer> getVersioned(Integer value, int... versionIncrements) {
+        return new Versioned<Integer>(value, TUtils.getClock(versionIncrements));
+    }
+
+    public void mustHaveVersion() {
+        try {
+            new Versioned<Integer>(1, null);
+            fail("Successfully created Versioned with null version.");
+        } catch(NullPointerException e) {
+            // this is good
+        }
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals("Null versioneds not equal.", getVersioned(null), getVersioned(null));
+        assertEquals("equal versioneds not equal.", getVersioned(1), getVersioned(1));
+        assertEquals("equal versioneds not equal.", getVersioned(1, 1, 2), getVersioned(1, 1, 2));
+
+        assertTrue("Equals values with different version are equal!",
+                   !getVersioned(1, 1, 2).equals(getVersioned(1, 1, 2, 2)));
+        assertTrue("Different values with same version are equal!",
+                   !getVersioned(1, 1, 2).equals(getVersioned(2, 1, 2)));
+        assertTrue("Different values with different version are equal!",
+                   !getVersioned(1, 1, 2).equals(getVersioned(2, 1, 1, 2)));
+
+        // Should work for array types too!
+        assertEquals("Equal arrays are not equal!",
+                     new Versioned<byte[]>(new byte[] { 1 }),
+                     new Versioned<byte[]>(new byte[] { 1 }));
+    }
+
+    @Test
+    public void testClone() {
+        Versioned<Integer> v1 = getVersioned(2, 1, 2, 3);
+        Versioned<Integer> v2 = v1.cloneVersioned();
+        assertEquals(v1, v2);
+        assertTrue(v1 != v2);
+        assertTrue(v1.getVersion() != v2.getVersion());
+        v2.increment(1, System.currentTimeMillis());
+        assertTrue(!v1.equals(v2));
+    }
+
+}
diff --git a/src/test/java/org/sdnplatform/sync/client/ClientTest.java b/src/test/java/org/sdnplatform/sync/client/ClientTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec677fc7e498ef1422d9ed97c8464fa5941fe3db
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/client/ClientTest.java
@@ -0,0 +1,152 @@
+package org.sdnplatform.sync.client;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.debugcounter.IDebugCounterService;
+import net.floodlightcontroller.debugcounter.NullDebugCounter;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.threadpool.ThreadPool;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sdnplatform.sync.ISyncService.Scope;
+import org.sdnplatform.sync.client.SyncClient;
+import org.sdnplatform.sync.client.SyncClient.SyncClientSettings;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.sdnplatform.sync.internal.config.Node;
+
+
+public class ClientTest {
+    protected SyncManager syncManager;
+    protected final static ObjectMapper mapper = new ObjectMapper();
+    protected String nodeString;
+    ArrayList<Node> nodes;
+    ThreadPool tp;
+
+    @Before
+    public void setUp() throws Exception {
+        nodes = new ArrayList<Node>();
+        nodes.add(new Node("localhost", 40101, (short)1, (short)1));
+        nodeString = mapper.writeValueAsString(nodes);
+        
+        tp = new ThreadPool();
+        syncManager = new SyncManager();
+        
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        fmc.addService(IThreadPoolService.class, tp);
+        fmc.addService(IDebugCounterService.class, new NullDebugCounter());
+        
+        fmc.addConfigParam(syncManager, "nodes", nodeString);
+        fmc.addConfigParam(syncManager, "thisNode", ""+1);
+        syncManager.registerStore("global", Scope.GLOBAL);
+        tp.init(fmc);
+        syncManager.init(fmc);
+        tp.startUp(fmc);
+        syncManager.startUp(fmc);
+    }
+
+    @After
+    public void tearDown() {
+        if (null != tp)
+            tp.getScheduledExecutor().shutdownNow();
+        tp = null;
+
+        if (null != syncManager)
+            syncManager.shutdown();
+        syncManager = null;
+    }
+
+    @Test
+    public void testClientBasic() throws Exception {
+        SyncClientSettings scs = new SyncClientSettings();
+        scs.hostname = "localhost";
+        scs.port = 40101;
+        scs.storeName = "global";
+        scs.debug = true;
+        SyncClient client = new SyncClient(scs);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        client.out = new PrintStream(out);
+        ByteArrayOutputStream err = new ByteArrayOutputStream();
+        client.err = new PrintStream(err);
+        client.connect();
+        client.executeCommandLine("get \"key\"");
+        assertEquals("", err.toString());
+        assertEquals("Connected to localhost:40101\n" +
+                "Getting Key:\n" +
+                "\"key\"\n\n" +
+                "Not found\n",
+                out.toString());
+        
+        out = new ByteArrayOutputStream();
+        err = new ByteArrayOutputStream();
+        client.out = new PrintStream(out);
+        client.err = new PrintStream(err);
+        client.executeCommandLine("put \"key\" {\"field1\": \"value1\", \"field2\": \"value2\"}");
+        assertEquals("", err.toString());
+        assertEquals("Putting Key:\n" +
+                "\"key\"\n\n" +
+                "Value:\n" +
+                "{\n" +
+                "  \"field1\" : \"value1\",\n" +
+                "  \"field2\" : \"value2\"\n" +
+                "}\n" +
+                "Success\n",
+                out.toString());
+        
+        out = new ByteArrayOutputStream();
+        err = new ByteArrayOutputStream();
+        client.out = new PrintStream(out);
+        client.err = new PrintStream(err);
+        client.executeCommandLine("get \"key\"");
+        assertEquals("", err.toString());
+        assertEquals("Getting Key:\n" +
+                "\"key\"\n\n" +
+                "Value:\n" +
+                "{\n" +
+                "  \"field1\" : \"value1\",\n" +
+                "  \"field2\" : \"value2\"\n" +
+                "}\n",
+                out.toString());
+        
+        out = new ByteArrayOutputStream();
+        err = new ByteArrayOutputStream();
+        client.out = new PrintStream(out);
+        client.err = new PrintStream(err);
+        client.executeCommandLine("delete \"key\"");
+        assertEquals("", err.toString());
+        assertEquals("Deleting Key:\n" +
+                "\"key\"\n\n" +
+                "Success\n",
+                out.toString());
+        
+        out = new ByteArrayOutputStream();
+        err = new ByteArrayOutputStream();
+        client.out = new PrintStream(out);
+        client.err = new PrintStream(err);
+        client.executeCommandLine("get \"key\"");
+        assertEquals("", err.toString());
+        assertEquals("Getting Key:\n" +
+                "\"key\"\n\n" +
+                "Not found\n",
+                out.toString());
+        
+        out = new ByteArrayOutputStream();
+        err = new ByteArrayOutputStream();
+        client.out = new PrintStream(out);
+        client.err = new PrintStream(err);
+        client.executeCommandLine("quit");
+        assertEquals("", err.toString());
+        assertEquals("",
+                out.toString());
+        
+        client.executeCommandLine("help");
+        assert(!"".equals(out.toString()));
+    }
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/SyncManagerTest.java b/src/test/java/org/sdnplatform/sync/internal/SyncManagerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f0a56bdf100f398c8ab44526006a7f83f1b028b
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/SyncManagerTest.java
@@ -0,0 +1,702 @@
+package org.sdnplatform.sync.internal;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.debugcounter.IDebugCounterService;
+import net.floodlightcontroller.debugcounter.NullDebugCounter;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.threadpool.ThreadPool;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IInconsistencyResolver;
+import org.sdnplatform.sync.IStoreClient;
+import org.sdnplatform.sync.IStoreListener;
+import org.sdnplatform.sync.IStoreListener.UpdateType;
+import org.sdnplatform.sync.ISyncService;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.ISyncService.Scope;
+import org.sdnplatform.sync.error.ObsoleteVersionException;
+import org.sdnplatform.sync.internal.AbstractSyncManager;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.sdnplatform.sync.internal.SyncTorture;
+import org.sdnplatform.sync.internal.config.Node;
+import org.sdnplatform.sync.internal.config.PropertyCCProvider;
+import org.sdnplatform.sync.internal.store.Key;
+import org.sdnplatform.sync.internal.store.TBean;
+import org.sdnplatform.sync.internal.version.VectorClock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class SyncManagerTest {
+    protected static Logger logger =
+            LoggerFactory.getLogger(SyncManagerTest.class);
+    
+    protected FloodlightModuleContext[] moduleContexts;
+    protected SyncManager[] syncManagers;
+    protected final static ObjectMapper mapper = new ObjectMapper();
+    protected String nodeString;
+    ArrayList<Node> nodes;
+
+    ThreadPool tp;
+
+    protected void setupSyncManager(FloodlightModuleContext fmc,
+                                    SyncManager syncManager, Node thisNode)
+            throws FloodlightModuleException {
+        fmc.addService(IThreadPoolService.class, tp);
+        fmc.addService(IDebugCounterService.class, new NullDebugCounter());
+        fmc.addConfigParam(syncManager, "configProviders", 
+                           PropertyCCProvider.class.getName());
+        fmc.addConfigParam(syncManager, "nodes", nodeString);
+        fmc.addConfigParam(syncManager, "thisNode", ""+thisNode.getNodeId());
+        syncManager.registerStore("global", Scope.GLOBAL);
+        syncManager.registerStore("local", Scope.LOCAL);
+        tp.init(fmc);
+        syncManager.init(fmc);
+        tp.startUp(fmc);
+        syncManager.startUp(fmc);
+    }
+    
+    @Before
+    public void setUp() throws Exception {
+        tp = new ThreadPool();
+        
+        syncManagers = new SyncManager[4];
+        moduleContexts = new FloodlightModuleContext[4];
+
+        nodes = new ArrayList<Node>();
+        nodes.add(new Node("localhost", 40101, (short)1, (short)1));
+        nodes.add(new Node("localhost", 40102, (short)2, (short)2));
+        nodes.add(new Node("localhost", 40103, (short)3, (short)1));
+        nodes.add(new Node("localhost", 40104, (short)4, (short)2));
+        nodeString = mapper.writeValueAsString(nodes);
+
+        for(int i = 0; i < 4; i++) {
+            moduleContexts[i] = new FloodlightModuleContext();
+            syncManagers[i] = new SyncManager();
+            setupSyncManager(moduleContexts[i], syncManagers[i], nodes.get(i));
+        }
+    }
+
+    @After
+    public void tearDown() {
+        tp.getScheduledExecutor().shutdownNow();
+        tp = null;
+
+        if (syncManagers != null) {
+            for(int i = 0; i < syncManagers.length; i++) {
+                if (null != syncManagers[i])
+                    syncManagers[i].shutdown();
+            }
+        }
+        syncManagers = null;
+    }
+
+    @Test
+    public void testBasicOneNode() throws Exception {
+        AbstractSyncManager sync = syncManagers[0];
+        IStoreClient<Key, TBean> testClient =
+                sync.getStoreClient("global", Key.class, TBean.class);
+        Key k = new Key("com.bigswitch.bigsync.internal", "test");
+        TBean tb = new TBean("hello", 42);
+        TBean tb2 = new TBean("hello", 84);
+        TBean tb3 = new TBean("hello", 126);
+        
+        assertNotNull(testClient.get(k));
+        assertNull(testClient.get(k).getValue());
+        
+        testClient.put(k, tb);
+        Versioned<TBean> result = testClient.get(k);
+        assertEquals(result.getValue(), tb);
+
+        result.setValue(tb2);
+        testClient.put(k, result);
+
+        try {
+            result.setValue(tb3);
+            testClient.put(k, result);
+            fail("Should get ObsoleteVersionException");
+        } catch (ObsoleteVersionException e) {
+            // happy town
+        }
+
+        result = testClient.get(k);
+        assertEquals(tb2, result.getValue());
+        
+    }
+
+    @Test
+    public void testIterator() throws Exception {
+        AbstractSyncManager sync = syncManagers[0];
+        IStoreClient<Key, TBean> testClient =
+                sync.getStoreClient("local", Key.class, TBean.class);
+        
+        HashMap<Key, TBean> testMap = new HashMap<Key, TBean>();
+        for (int i = 0; i < 100; i++) {
+            Key k = new Key("com.bigswitch.bigsync.internal", "test" + i);
+            TBean tb = new TBean("value", i);
+            testMap.put(k, tb);
+            testClient.put(k, tb);
+        }
+        
+        IClosableIterator<Entry<Key, Versioned<TBean>>> iter = 
+                testClient.entries();
+        int size = 0;
+        try {
+            while (iter.hasNext()) {
+                Entry<Key, Versioned<TBean>> e = iter.next();
+                assertEquals(testMap.get(e.getKey()), e.getValue().getValue());
+                size += 1;
+            }
+        } finally {
+            iter.close();
+        }
+        assertEquals(testMap.size(), size);
+    }
+
+    private <K, V> Versioned<V> waitForValue(IStoreClient<K, V> client,
+                                             K key, V value,
+                                             int maxTime,
+                                             String clientName) 
+                                                     throws Exception {
+        Versioned<V> v = null;
+        long then = System.currentTimeMillis();
+        while (true) {
+            v = client.get(key);
+            if (value != null) {
+                if (v.getValue() != null && v.getValue().equals(value)) break;
+            } else {
+                if (v.getValue() != null) break;
+            }
+            if (v.getValue() != null)
+                logger.info("{}: Value for key {} not yet right: " +
+                            "expected: {}; actual: {}",
+                            new Object[]{clientName, key, value, v.getValue()});
+            else 
+                logger.info("{}: Value for key {} is null: expected {}", 
+                            new Object[]{clientName, key, value});
+                
+
+            Thread.sleep(100);
+            assertTrue(then + maxTime > System.currentTimeMillis());
+        }
+        return v;
+    }
+
+    private void waitForFullMesh(int maxTime) throws Exception {
+        long then = System.currentTimeMillis();
+
+        while (true) {
+            boolean full = true;
+            for(int i = 0; i < syncManagers.length; i++) {
+                if (!syncManagers[i].rpcService.isFullyConnected())
+                    full = false;
+            }
+            if (full) break;
+            Thread.sleep(100);
+            assertTrue(then + maxTime > System.currentTimeMillis());
+        }
+    }
+
+    private void waitForConnection(SyncManager sm,
+                                   short nodeId,
+                                   boolean connected,
+                                   int maxTime) throws Exception {
+        long then = System.currentTimeMillis();
+
+        while (true) {
+            if (connected == sm.rpcService.isConnected(nodeId)) break;
+            Thread.sleep(100);
+            assertTrue(then + maxTime > System.currentTimeMillis());
+        }
+    }
+
+    @Test
+    public void testBasicGlobalSync() throws Exception {
+        waitForFullMesh(2000);
+
+        ArrayList<IStoreClient<String, String>> clients =
+                new ArrayList<IStoreClient<String, String>>(syncManagers.length);
+        // write one value to each node's local interface
+        for (int i = 0; i < syncManagers.length; i++) {
+            IStoreClient<String, String> client =
+                    syncManagers[i].getStoreClient("global", 
+                                                   String.class, String.class);
+            clients.add(client);
+            client.put("key" + i, ""+i);
+        }
+
+        // verify that we see all the values everywhere
+        for (int j = 0; j < clients.size(); j++) {
+            for (int i = 0; i < syncManagers.length; i++) {
+                waitForValue(clients.get(j), "key" + i, ""+i, 2000, "client"+j);
+            }
+        }
+    }
+
+    @Test
+    public void testBasicLocalSync() throws Exception {
+        waitForFullMesh(2000);
+
+        ArrayList<IStoreClient<String, String>> clients =
+                new ArrayList<IStoreClient<String, String>>(syncManagers.length);
+        // write one value to each node's local interface
+        for (int i = 0; i < syncManagers.length; i++) {
+            IStoreClient<String, String> client =
+                    syncManagers[i].getStoreClient("local", 
+                                                   String.class, String.class);
+            clients.add(client);
+            client.put("key" + i, ""+i);
+        }
+
+        // verify that we see all the values from each local group at all the
+        // nodes of that local group
+        for (int j = 0; j < clients.size(); j++) {
+            IStoreClient<String, String> client = clients.get(j);
+            for (int i = 0; i < syncManagers.length; i++) {
+                if (i % 2 == j % 2)
+                    waitForValue(client, "key" + i, ""+i, 2000, "client"+j);
+                else {
+                    Versioned<String> v = client.get("key" + i);
+                    if (v.getValue() != null) {
+                        fail("Node " + j + " reading key" + i + 
+                             ": " + v.getValue());
+                    }
+                }
+            }
+        }
+    }
+    
+    @Test
+    public void testConcurrentWrite() throws Exception {
+        waitForFullMesh(2000);
+        
+        // Here we generate concurrent writes and then resolve them using
+        // a custom inconsistency resolver
+        IInconsistencyResolver<Versioned<List<String>>> ir = 
+                new IInconsistencyResolver<Versioned<List<String>>>() {
+            @Override
+            public List<Versioned<List<String>>>
+                    resolveConflicts(List<Versioned<List<String>>> items) {
+                VectorClock vc = null;
+                List<String> strings = new ArrayList<String>();
+                for (Versioned<List<String>> item : items) {
+                    if (vc == null) 
+                        vc = (VectorClock)item.getVersion();
+                    else
+                        vc = vc.merge((VectorClock)item.getVersion());
+                    
+                    strings.addAll(item.getValue());
+                }
+                Versioned<List<String>> v = 
+                        new Versioned<List<String>>(strings, vc);
+                return Collections.singletonList(v);
+            }
+        };
+        
+        TypeReference<List<String>> tr = new TypeReference<List<String>>() {};
+        TypeReference<String> ktr = new TypeReference<String>() {};
+        IStoreClient<String, List<String>> client0 =
+                syncManagers[0].getStoreClient("local", ktr, tr, ir);
+        IStoreClient<String, List<String>> client2 =
+                syncManagers[2].getStoreClient("local", ktr, tr, ir);
+        
+        client0.put("key", Collections.singletonList("value"));
+        Versioned<List<String>> v = client0.get("key");
+        assertNotNull(v);
+        
+        // now we generate two writes that are concurrent to each other
+        // but are both locally after the first write.  The result should be
+        // two non-obsolete lists each containing a single element.
+        // The inconsistency resolver above will resolve these by merging
+        // the lists
+        List<String> comp = new ArrayList<String>();
+        v.setValue(Collections.singletonList("newvalue0"));
+        comp.add("newvalue0");
+        client0.put("key", v);
+        v.setValue(Collections.singletonList("newvalue1"));
+        comp.add("newvalue1");
+        client2.put("key", v);
+        
+        v = waitForValue(client0, "key", comp, 1000, "client0");
+
+        // add one more value to the array.  Now there will be exactly one
+        // non-obsolete value
+        List<String> newlist = new ArrayList<String>(v.getValue());
+        assertEquals(2, newlist.size());
+        newlist.add("finalvalue");
+        v.setValue(newlist);
+        client0.put("key", v);
+        
+        v = waitForValue(client2, "key", newlist, 2000, "client2");
+        assertEquals(3, newlist.size());
+      
+    }
+
+    @Test
+    public void testReconnect() throws Exception {
+        IStoreClient<String, String> client0 =
+                syncManagers[0].getStoreClient("global", 
+                                               String.class, 
+                                               String.class);
+        IStoreClient<String, String> client1 =
+                syncManagers[1].getStoreClient("global", 
+                                               String.class, String.class);
+        IStoreClient<String, String> client2 =
+                syncManagers[2].getStoreClient("global", 
+                                               String.class, String.class);
+
+        client0.put("key0", "value0");
+        waitForValue(client2, "key0", "value0", 1000, "client0");
+
+        logger.info("Shutting down server ID 1");
+        syncManagers[0].shutdown();
+        
+        client1.put("newkey1", "newvalue1");
+        client2.put("newkey2", "newvalue2");
+        client1.put("key0", "newvalue0");
+        client2.put("key2", "newvalue2");
+        
+        for (int i = 0; i < 500; i++) {
+            client2.put("largetest" + i, "largetestvalue");
+        }
+        
+        logger.info("Initializing server ID 1");
+        syncManagers[0] = new SyncManager();
+        setupSyncManager(moduleContexts[0], syncManagers[0], nodes.get(0));
+
+        waitForFullMesh(2000);
+
+        client0 = syncManagers[0].getStoreClient("global", 
+                                                 String.class, String.class);
+        waitForValue(client0, "newkey1", "newvalue1", 1000, "client0");
+        waitForValue(client0, "newkey2", "newvalue2", 1000, "client0");
+        waitForValue(client0, "key0", "newvalue0", 1000, "client0");
+        waitForValue(client0, "key2", "newvalue2", 1000, "client0");
+
+        for (int i = 0; i < 500; i++) {
+            waitForValue(client0, "largetest" + i, 
+                         "largetestvalue", 1000, "client0");
+        }
+    }
+    
+    protected class Update {
+        String key;
+        UpdateType type;
+
+        public Update(String key, UpdateType type) {
+            super();
+            this.key = key;
+            this.type = type;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + getOuterType().hashCode();
+            result = prime * result + ((key == null) ? 0 : key.hashCode());
+            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 (getClass() != obj.getClass()) return false;
+            Update other = (Update) obj;
+            if (!getOuterType().equals(other.getOuterType())) return false;
+            if (key == null) {
+                if (other.key != null) return false;
+            } else if (!key.equals(other.key)) return false;
+            if (type != other.type) return false;
+            return true;
+        }
+
+        private SyncManagerTest getOuterType() {
+            return SyncManagerTest.this;
+        }
+    }
+    
+    protected class TestListener implements IStoreListener<String> {
+        HashSet<Update> notified = new HashSet<Update>();
+        
+        @Override
+        public void keysModified(Iterator<String> keys, 
+                                 UpdateType type) {
+            while (keys.hasNext())
+                notified.add(new Update(keys.next(), type));
+        }
+        
+    }
+    
+    @SuppressWarnings("rawtypes")
+    private void waitForNotify(TestListener tl, 
+                               HashSet comp,
+                               int maxTime) throws Exception {
+        long then = System.currentTimeMillis();
+
+        while (true) {
+            if (tl.notified.containsAll(comp)) break;
+            Thread.sleep(100);
+            assertTrue(then + maxTime > System.currentTimeMillis());
+        }
+    }
+
+    @Test
+    public void testNotify() throws Exception {
+        IStoreClient<String, String> client0 =
+                syncManagers[0].getStoreClient("local", 
+                                               String.class, String.class);
+        IStoreClient<String, String> client2 =
+                syncManagers[2].getStoreClient("local", 
+                                               new TypeReference<String>() {}, 
+                                               new TypeReference<String>() {});
+        
+        TestListener t0 = new TestListener();
+        TestListener t2 = new TestListener();
+        client0.addStoreListener(t0);
+        client2.addStoreListener(t2);
+        
+        client0.put("test0", "value");
+        client2.put("test2", "value");
+
+        HashSet<Update> c0 = new HashSet<Update>();
+        c0.add(new Update("test0", UpdateType.LOCAL));
+        c0.add(new Update("test2", UpdateType.REMOTE));
+        HashSet<Update> c2 = new HashSet<Update>();
+        c2.add(new Update("test0", UpdateType.REMOTE));
+        c2.add(new Update("test2", UpdateType.LOCAL));
+        
+        waitForNotify(t0, c0, 2000);
+        waitForNotify(t2, c2, 2000);
+        assertEquals(2, t0.notified.size());
+        assertEquals(2, t2.notified.size());
+        
+        t0.notified.clear();
+        t2.notified.clear();
+        
+        Versioned<String> v0 = client0.get("test0");
+        v0.setValue("newvalue");
+        client0.put("test0", v0);
+
+        Versioned<String> v2 = client0.get("test2");
+        v2.setValue("newvalue");
+        client2.put("test2", v2);
+
+        waitForNotify(t0, c0, 2000);
+        waitForNotify(t2, c2, 2000);
+        assertEquals(2, t0.notified.size());
+        assertEquals(2, t2.notified.size());
+
+        t0.notified.clear();
+        t2.notified.clear();
+        
+        client0.delete("test0");
+        client2.delete("test2");
+
+        waitForNotify(t0, c0, 2000);
+        waitForNotify(t2, c2, 2000);
+        assertEquals(2, t0.notified.size());
+        assertEquals(2, t2.notified.size());
+    }
+    
+    @Test
+    public void testAddNode() throws Exception {
+        waitForFullMesh(2000);
+        IStoreClient<String, String> client0 =
+                syncManagers[0].getStoreClient("global", 
+                                               String.class, String.class);
+        IStoreClient<String, String> client1 =
+                syncManagers[1].getStoreClient("global", 
+                                               String.class, String.class);
+        client0.put("key", "value");
+        waitForValue(client1, "key", "value", 2000, "client1");
+        
+        nodes.add(new Node("localhost", 40105, (short)5, (short)5));
+        SyncManager[] sms = Arrays.copyOf(syncManagers, 
+                                          syncManagers.length + 1);
+        FloodlightModuleContext[] fmcs =
+                Arrays.copyOf(moduleContexts,
+                              moduleContexts.length + 1);
+        sms[syncManagers.length] = new SyncManager();
+        fmcs[moduleContexts.length] = new FloodlightModuleContext();
+        nodeString = mapper.writeValueAsString(nodes);
+
+        setupSyncManager(fmcs[moduleContexts.length],
+                         sms[syncManagers.length],
+                         nodes.get(syncManagers.length));
+        syncManagers = sms;
+        moduleContexts = fmcs;
+
+        for(int i = 0; i < 4; i++) {
+            moduleContexts[i].addConfigParam(syncManagers[i],
+                                             "nodes", nodeString);
+            syncManagers[i].updateConfiguration();
+        }
+        waitForFullMesh(2000);
+
+        IStoreClient<String, String> client4 =
+                syncManagers[4].getStoreClient("global", 
+                                               String.class, String.class);
+        client4.put("newkey", "newvalue");
+        waitForValue(client4, "key", "value", 2000, "client4");
+        waitForValue(client0, "newkey", "newvalue", 2000, "client0");
+    }
+    
+    @Test
+    public void testRemoveNode() throws Exception {
+        waitForFullMesh(2000);
+        IStoreClient<String, String> client0 =
+                syncManagers[0].getStoreClient("global", 
+                                               String.class, String.class);
+        IStoreClient<String, String> client1 =
+                syncManagers[1].getStoreClient("global", 
+                                               String.class, String.class);
+        IStoreClient<String, String> client2 =
+                syncManagers[2].getStoreClient("global", 
+                                               String.class, String.class);
+
+        client0.put("key", "value");
+        waitForValue(client1, "key", "value", 2000, "client1");
+        
+        nodes.remove(0);
+        nodeString = mapper.writeValueAsString(nodes);
+
+        SyncManager oldNode = syncManagers[0];
+        syncManagers = Arrays.copyOfRange(syncManagers, 1, 4);
+        moduleContexts = Arrays.copyOfRange(moduleContexts, 1, 4);
+
+        try {
+            for(int i = 0; i < syncManagers.length; i++) {
+                moduleContexts[i].addConfigParam(syncManagers[i],
+                                                 "nodes", nodeString);
+                syncManagers[i].updateConfiguration();
+                waitForConnection(syncManagers[i], (short)1, false, 2000);
+            }
+        } finally {
+            oldNode.shutdown();
+        }
+        waitForFullMesh(2000);
+
+        client1.put("newkey", "newvalue");
+        waitForValue(client2, "key", "value", 2000, "client4");
+        waitForValue(client2, "newkey", "newvalue", 2000, "client0");
+    }
+
+    @Test
+    public void testChangeNode() throws Exception {
+        waitForFullMesh(2000);
+        IStoreClient<String, String> client0 =
+                syncManagers[0].getStoreClient("global", 
+                                               String.class, String.class);
+        IStoreClient<String, String> client2 =
+                syncManagers[2].getStoreClient("global", 
+                                               String.class, String.class);
+        client0.put("key", "value");
+        waitForValue(client2, "key", "value", 2000, "client2");
+
+        nodes.set(2, new Node("localhost", 50103, (short)3, (short)1));
+        nodeString = mapper.writeValueAsString(nodes);
+
+        for(int i = 0; i < syncManagers.length; i++) {
+            moduleContexts[i].addConfigParam(syncManagers[i],
+                                             "nodes", nodeString);
+            syncManagers[i].updateConfiguration();
+        }
+        waitForFullMesh(2000);
+        
+        waitForValue(client2, "key", "value", 2000, "client2");
+        client2 = syncManagers[2].getStoreClient("global", 
+                                                 String.class, String.class);
+        client0.put("key", "newvalue");
+        waitForValue(client2, "key", "newvalue", 2000, "client2");
+    }
+
+    /**
+     * Do a brain-dead performance test with one thread writing and waiting
+     * for the values on the other node.  The result get printed to the log
+     */
+    public void testSimpleWritePerformance(String store) throws Exception {
+        waitForFullMesh(5000);
+        
+        final int count = 1000000;
+
+        IStoreClient<String, String> client0 =
+                syncManagers[0].getStoreClient(store, 
+                                               String.class, String.class);
+        IStoreClient<String, String> client2 =
+                syncManagers[2].getStoreClient(store, 
+                                               String.class, String.class);
+        
+        long then = System.currentTimeMillis();
+        
+        for (int i = 1; i <= count; i++) {
+            client0.put(""+i, ""+i);
+        }
+
+        long donewriting = System.currentTimeMillis();
+
+        waitForValue(client2, ""+count, null, count, "client2");
+        
+        long now = System.currentTimeMillis();
+        
+        logger.info("Simple write ({}): {} values in {}+/-100 " +
+                    "millis ({} synced writes/s) ({} local writes/s)",
+                    new Object[]{store, count, (now-then), 
+                                 1000.0*count/(now-then),
+                                 1000.0*count/(donewriting-then)});
+
+    }
+    
+    @Test
+    @Ignore // ignored just to speed up routine tests
+    public void testPerfSimpleWriteLocal() throws Exception {
+        testSimpleWritePerformance("local");
+    }
+
+    @Test
+    @Ignore // ignored just to speed up routine tests
+    public void testPerfSimpleWriteGlobal() throws Exception {
+        testSimpleWritePerformance("global");
+    }
+
+    @Test
+    @Ignore
+    public void testPerfOneNode() throws Exception {
+        tearDown();
+        tp = new ThreadPool();
+        tp.init(null);
+        tp.startUp(null);
+        nodes = new ArrayList<Node>();
+        nodes.add(new Node("localhost", 40101, (short)1, (short)1));
+        nodeString = mapper.writeValueAsString(nodes);
+        SyncManager sm = new SyncManager();
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        setupSyncManager(fmc, sm, nodes.get(0));
+        fmc.addService(ISyncService.class, sm);
+        SyncTorture st = new SyncTorture();
+        //fmc.addConfigParam(st, "iterations", "1");
+        st.init(fmc);
+        st.startUp(fmc);
+        Thread.sleep(10000);
+    }
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/TUtils.java b/src/test/java/org/sdnplatform/sync/internal/TUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..249a414d889c3a33c654be29ebb3838351430922
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/TUtils.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2008-2009 LinkedIn, Inc
+ * Copyright (c) 2013 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 org.sdnplatform.sync.internal;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Random;
+
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.internal.version.VectorClock;
+
+
+/**
+ * Helper utilities for tests
+ *
+ *
+ */
+public class TUtils {
+
+    public static final String DIGITS = "0123456789";
+    public static final String LETTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
+    public static final String CHARACTERS = LETTERS + DIGITS + "~!@#$%^&*()____+-=[];',,,./>?:{}";
+    public static final Random SEEDED_RANDOM = new Random(19873482374L);
+    public static final Random UNSEEDED_RANDOM = new Random();
+
+    /**
+     * Get a vector clock with events on the sequence of nodes given So
+     * getClock(1,1,2,2,2) means a clock that has two writes on node 1 and 3
+     * writes on node 2.  The timestamp will be the current time
+     *
+     * @param nodes The sequence of nodes
+     * @return A VectorClock initialized with the given sequence of events
+     */
+    public static VectorClock getClock(int... nodes) {
+        VectorClock clock = new VectorClock();
+        return increment(clock, nodes);
+    }
+    
+    /**
+     * Get a vector clock with a the given timestamp and events on the 
+     * sequence of nodes given So getClock(1,1,2,2,2) means a clock that has 
+     * two writes on node 1 and 3 writes on node 2.  
+     *
+     * @param nodes The sequence of nodes
+     * @return A VectorClock initialized with the given sequence of events
+     */
+    public static VectorClock getClockT(long timestamp, int... nodes) {
+        VectorClock clock = new VectorClock(timestamp);
+        return increment(clock, nodes);
+    }
+
+    /**
+     * Record events for the given sequence of nodes
+     *
+     * @param clock The VectorClock to record the events on
+     * @param nodes The sequences of node events
+     */
+    public static VectorClock increment(VectorClock clock, int... nodes) {
+        for(int n: nodes)
+            clock = clock.incremented((short) n, System.currentTimeMillis());
+        return clock;
+    }
+
+    /**
+     * Test two byte arrays for (deep) equality. I think this exists in java 6
+     * but not java 5
+     *
+     * @param a1 Array 1
+     * @param a2 Array 2
+     * @return True iff a1.length == a2.length and a1[i] == a2[i] for 0 <= i <
+     *         a1.length
+     */
+    public static boolean bytesEqual(byte[] a1, byte[] a2) {
+        if(a1 == a2) {
+            return true;
+        } else if(a1 == null || a2 == null) {
+            return false;
+        } else if(a1.length != a2.length) {
+            return false;
+        } else {
+            for(int i = 0; i < a1.length; i++)
+                if(a1[i] != a2[i])
+                    return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Create a string with some random letters
+     *
+     * @param length The length of the string to create
+     * @return The string
+     */
+    public static String randomLetters(int length) {
+        return randomString(LETTERS, length);
+    }
+
+    /**
+     * Create a string that is a random sample (with replacement) from the given
+     * string
+     *
+     * @param sampler The string to sample from
+     * @param length The length of the string to create
+     * @return The created string
+     */
+    public static String randomString(String sampler, int length) {
+        StringBuilder builder = new StringBuilder(length);
+        for(int i = 0; i < length; i++)
+            builder.append(sampler.charAt(SEEDED_RANDOM.nextInt(sampler.length())));
+        return builder.toString();
+    }
+
+    /**
+     * Generate an array of random bytes
+     *
+     * @param length
+     * @return
+     */
+    public static byte[] randomBytes(int length) {
+        byte[] bytes = new byte[length];
+        SEEDED_RANDOM.nextBytes(bytes);
+        return bytes;
+    }
+
+    /**
+     * Return an array of length count containing random integers in the range
+     * (0, max) generated off the test rng.
+     *
+     * @param max The bound on the random number size
+     * @param count The number of integers to generate
+     * @return The array of integers
+     */
+    public static int[] randomInts(int max, int count) {
+        int[] vals = new int[count];
+        for(int i = 0; i < count; i++)
+            vals[i] = SEEDED_RANDOM.nextInt(max);
+        return vals;
+    }
+
+    /**
+     * Weirdly java doesn't seem to have Arrays.shuffle(), this terrible hack
+     * does that.
+     *
+     * @return A shuffled copy of the input
+     */
+    public static int[] shuffle(int[] input) {
+        List<Integer> vals = new ArrayList<Integer>(input.length);
+        for(int i = 0; i < input.length; i++)
+            vals.add(input[i]);
+        Collections.shuffle(vals, SEEDED_RANDOM);
+        int[] copy = new int[input.length];
+        for(int i = 0; i < input.length; i++)
+            copy[i] = vals.get(i);
+        return copy;
+    }
+
+    /**
+     * Compute the requested quantile of the given array
+     *
+     * @param values The array of values
+     * @param quantile The quantile requested (must be between 0.0 and 1.0
+     *        inclusive)
+     * @return The quantile
+     */
+    public static long quantile(long[] values, double quantile) {
+        if(values == null)
+            throw new IllegalArgumentException("Values cannot be null.");
+        if(quantile < 0.0 || quantile > 1.0)
+            throw new IllegalArgumentException("Quantile must be between 0.0 and 1.0");
+
+        long[] copy = new long[values.length];
+        System.arraycopy(values, 0, copy, 0, copy.length);
+        Arrays.sort(copy);
+        int index = (int) (copy.length * quantile);
+        return copy[index];
+    }
+
+    /**
+     * Compute the mean of the given values
+     *
+     * @param values The values
+     * @return The mean
+     */
+    public static double mean(long[] values) {
+        double total = 0.0;
+        for(int i = 0; i < values.length; i++)
+            total += values[i];
+        return total / values.length;
+    }
+
+    /**
+     * Create a temporary directory in the directory given by java.io.tmpdir
+     *
+     * @return The directory created.
+     */
+    public static File createTempDir() {
+        return createTempDir(new File(System.getProperty("java.io.tmpdir")));
+    }
+
+    /**
+     * Create a temporary directory that is a child of the given directory
+     *
+     * @param parent The parent directory
+     * @return The temporary directory
+     */
+    public static File createTempDir(File parent) {
+        File temp = new File(parent,
+                             Integer.toString(Math.abs(UNSEEDED_RANDOM.nextInt()) % 1000000));
+        temp.delete();
+        temp.mkdir();
+        temp.deleteOnExit();
+        return temp;
+    }
+
+    /**
+     * Wrap the given string in quotation marks. This is slightly more readable
+     * then the java inline quotes that require escaping.
+     *
+     * @param s The string to wrap in quotes
+     * @return The string
+     */
+    public static String quote(String s) {
+        return "\"" + s + "\"";
+    }
+
+    /**
+     * Always uses UTF-8.
+     */
+    public static ByteArray toByteArray(String s) {
+        try {
+            return new ByteArray(s.getBytes("UTF-8"));
+        } catch(UnsupportedEncodingException e) {
+            /* Should not happen */
+            throw new IllegalStateException(e);
+        }
+    }
+/*
+    public static void assertWithBackoff(long timeout, Attempt attempt) throws Exception {
+        assertWithBackoff(30, timeout, attempt);
+    }
+
+    public static void assertWithBackoff(long initialDelay, long timeout, Attempt attempt)
+            throws Exception {
+        long delay = initialDelay;
+        long finishBy = System.currentTimeMillis() + timeout;
+
+        while(true) {
+            try {
+                attempt.checkCondition();
+                return;
+            } catch(AssertionError e) {
+                if(System.currentTimeMillis() < finishBy) {
+                    Thread.sleep(delay);
+                    delay *= 2;
+                } else {
+                    throw e;
+                }
+            }
+        }
+    }
+*/
+    /**
+     * Because java.beans.ReflectionUtils isn't public...
+     */
+
+    @SuppressWarnings("unchecked")
+    public static <T> T getPrivateValue(Object instance, String fieldName) throws Exception {
+        Field eventDataQueueField = instance.getClass().getDeclaredField(fieldName);
+        eventDataQueueField.setAccessible(true);
+        return (T) eventDataQueueField.get(instance);
+    }
+
+    /**
+     * Constructs a calendar object representing the given time
+     */
+    public static GregorianCalendar getCalendar(int year,
+                                                int month,
+                                                int day,
+                                                int hour,
+                                                int mins,
+                                                int secs) {
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.set(Calendar.YEAR, year);
+        cal.set(Calendar.MONTH, month);
+        cal.set(Calendar.DATE, day);
+        cal.set(Calendar.HOUR_OF_DAY, hour);
+        cal.set(Calendar.MINUTE, mins);
+        cal.set(Calendar.SECOND, secs);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal;
+    }
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/store/AbstractByteArrayStoreT.java b/src/test/java/org/sdnplatform/sync/internal/store/AbstractByteArrayStoreT.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c7ce1ee00fb500941f87633f986ee7c162875f5
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/store/AbstractByteArrayStoreT.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.store;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.junit.Test;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.internal.TUtils;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.util.ByteArray;
+
+import com.google.common.collect.Lists;
+
+/**
+ *
+ */
+public abstract class AbstractByteArrayStoreT extends
+        AbstractStoreT<ByteArray, byte[]> {
+
+    @Override
+    public List<ByteArray> getKeys(int numValues) {
+        List<ByteArray> keys = Lists.newArrayList();
+        for(byte[] array: this.getByteValues(numValues, 8))
+            keys.add(new ByteArray(array));
+        return keys;
+    }
+
+    @Override
+    public List<byte[]> getValues(int numValues) {
+        return this.getByteValues(numValues, 10);
+    }
+
+    @Override
+    protected boolean valuesEqual(byte[] t1, byte[] t2) {
+        return TUtils.bytesEqual(t1, t2);
+    }
+
+    @Test
+    public void testEmptyByteArray() throws Exception {
+        IStore<ByteArray, byte[]> store = getStore();
+        Versioned<byte[]> bytes = new Versioned<byte[]>(new byte[0]);
+        store.put(new ByteArray(new byte[0]), bytes);
+        List<Versioned<byte[]>> found = store.get(new ByteArray(new byte[0]));
+        assertEquals("Incorrect number of results.", 1, found.size());
+        bassertEquals("Get doesn't equal put.", bytes, found.get(0));
+    }
+
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/store/AbstractStorageEngineT.java b/src/test/java/org/sdnplatform/sync/internal/store/AbstractStorageEngineT.java
new file mode 100644
index 0000000000000000000000000000000000000000..aaae98011f36478c4f4596580bde59fee4d41cc4
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/store/AbstractStorageEngineT.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.store;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.junit.Test;
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.TUtils;
+import org.sdnplatform.sync.internal.store.IStorageEngine;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.util.ByteArray;
+
+import static org.junit.Assert.*;
+
+
+public abstract class AbstractStorageEngineT extends AbstractByteArrayStoreT {
+
+    @Override
+    public IStore<ByteArray, byte[]> getStore() {
+        return getStorageEngine();
+    }
+
+    public abstract IStorageEngine<ByteArray, byte[]> getStorageEngine();
+
+    public void testGetNoEntries() {
+        IClosableIterator<Entry<ByteArray, List<Versioned<byte[]>>>> it = null;
+        try {
+            IStorageEngine<ByteArray, byte[]> engine = getStorageEngine();
+            it = engine.entries();
+            while(it.hasNext())
+                fail("There shouldn't be any entries in this store.");
+        } finally {
+            if(it != null)
+                it.close();
+        }
+    }
+
+    @Test
+    public void testGetNoKeys() {
+        IClosableIterator<ByteArray> it = null;
+        try {
+            IStorageEngine<ByteArray, byte[]> engine = getStorageEngine();
+            it = engine.keys();
+            while(it.hasNext())
+                fail("There shouldn't be any entries in this store.");
+        } finally {
+            if(it != null)
+                it.close();
+        }
+    }
+
+    @Test
+    public void testPruneOnWrite() throws SyncException {
+        IStorageEngine<ByteArray, byte[]> engine = getStorageEngine();
+        Versioned<byte[]> v1 = new Versioned<byte[]>(new byte[] { 1 }, TUtils.getClock(1));
+        Versioned<byte[]> v2 = new Versioned<byte[]>(new byte[] { 2 }, TUtils.getClock(2));
+        Versioned<byte[]> v3 = new Versioned<byte[]>(new byte[] { 3 }, TUtils.getClock(1, 2));
+        ByteArray key = new ByteArray((byte) 3);
+        engine.put(key, v1);
+        engine.put(key, v2);
+        assertEquals(2, engine.get(key).size());
+        engine.put(key, v3);
+        assertEquals(1, engine.get(key).size());
+    }
+
+    @Test
+    public void testTruncate() throws Exception {
+        IStorageEngine<ByteArray, byte[]> engine = getStorageEngine();
+        Versioned<byte[]> v1 = new Versioned<byte[]>(new byte[] { 1 });
+        Versioned<byte[]> v2 = new Versioned<byte[]>(new byte[] { 2 });
+        Versioned<byte[]> v3 = new Versioned<byte[]>(new byte[] { 3 });
+        ByteArray key1 = new ByteArray((byte) 3);
+        ByteArray key2 = new ByteArray((byte) 4);
+        ByteArray key3 = new ByteArray((byte) 5);
+
+        engine.put(key1, v1);
+        engine.put(key2, v2);
+        engine.put(key3, v3);
+        engine.truncate();
+
+        IClosableIterator<Entry<ByteArray, List<Versioned<byte[]>>>> it = null;
+        try {
+            it = engine.entries();
+            while(it.hasNext()) {
+                fail("There shouldn't be any entries in this store.");
+            }
+        } finally {
+            if(it != null) {
+                it.close();
+            }
+        }
+    }
+
+    @Test
+    public void testCleanupTask() throws Exception {
+        IStorageEngine<ByteArray, byte[]> engine = getStorageEngine();
+        engine.setTombstoneInterval(500);
+
+        Versioned<byte[]> v1_1 = new Versioned<byte[]>(new byte[] { 1 }, TUtils.getClock(1));
+        Versioned<byte[]> v1_2 = new Versioned<byte[]>(null, TUtils.getClock(1, 1));
+
+        // add, update, delete
+        Versioned<byte[]> v2_1 = new Versioned<byte[]>(new byte[] { 1 }, TUtils.getClock(1));
+        Versioned<byte[]> v2_2 = new Versioned<byte[]>(new byte[] { 2 }, TUtils.getClock(1, 2));
+        Versioned<byte[]> v2_3 = new Versioned<byte[]>(null, TUtils.getClock(1, 2, 1));
+
+        // delete then add again
+        Versioned<byte[]> v3_1 = new Versioned<byte[]>(new byte[] { 1 }, TUtils.getClock(1));
+        Versioned<byte[]> v3_2 = new Versioned<byte[]>(null, TUtils.getClock(1, 2));
+        Versioned<byte[]> v3_3 = new Versioned<byte[]>(new byte[] { 2 }, TUtils.getClock(1, 2, 1));
+
+        // delete concurrent to update
+        Versioned<byte[]> v4_1 = new Versioned<byte[]>(new byte[] { 1 }, TUtils.getClock(1));
+        Versioned<byte[]> v4_2 = new Versioned<byte[]>(new byte[] { 2 }, TUtils.getClock(1, 2));
+        Versioned<byte[]> v4_3 = new Versioned<byte[]>(null, TUtils.getClock(1, 1));
+        
+        ByteArray key1 = new ByteArray((byte) 3);
+        ByteArray key2 = new ByteArray((byte) 4);
+        ByteArray key3 = new ByteArray((byte) 5);
+        ByteArray key4 = new ByteArray((byte) 6);
+
+        engine.put(key1, v1_1);
+        assertEquals(1, engine.get(key1).size());
+
+        engine.put(key1, v1_2);
+        List<Versioned<byte[]>> r = engine.get(key1);
+        assertEquals(1, r.size());
+        assertNull(r.get(0).getValue());
+
+        engine.put(key2, v2_1);
+        engine.put(key2, v2_2);
+        engine.put(key2, v2_3);
+        engine.put(key3, v3_1);
+        engine.put(key3, v3_2);
+        engine.put(key4, v4_1);
+        engine.put(key4, v4_2);
+        engine.put(key4, v4_3);
+        
+        engine.cleanupTask();
+        r = engine.get(key1);
+        assertEquals(1, r.size());
+        assertNull(r.get(0).getValue());
+
+        engine.put(key3, v3_3);
+        
+        Thread.sleep(501);
+        engine.cleanupTask();
+        r = engine.get(key1);
+        assertEquals(0, r.size());
+        r = engine.get(key2);
+        assertEquals(0, r.size());
+        r = engine.get(key3);
+        assertEquals(1, r.size());
+        r = engine.get(key4);
+        assertEquals(2, r.size());
+        
+    }
+
+    @SuppressWarnings("unused")
+    private boolean remove(List<byte[]> list, byte[] item) {
+        Iterator<byte[]> it = list.iterator();
+        boolean removedSomething = false;
+        while(it.hasNext()) {
+            if(TUtils.bytesEqual(item, it.next())) {
+                it.remove();
+                removedSomething = true;
+            }
+        }
+        return removedSomething;
+    }
+
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/store/AbstractStoreT.java b/src/test/java/org/sdnplatform/sync/internal/store/AbstractStoreT.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f8f22edec20172399954a919d3d3276dc51fcda
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/store/AbstractStoreT.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.store;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.junit.Test;
+import org.sdnplatform.sync.IClosableIterator;
+import org.sdnplatform.sync.IVersion;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.error.ObsoleteVersionException;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.internal.TUtils;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.internal.version.VectorClock;
+
+
+import static org.junit.Assert.*;
+import static org.sdnplatform.sync.internal.TUtils.*;
+
+import com.google.common.base.Objects;
+
+public abstract class AbstractStoreT<K, V> {
+
+    public abstract IStore<K, V> getStore() throws Exception;
+
+    public abstract List<V> getValues(int numValues);
+
+    public abstract List<K> getKeys(int numKeys);
+
+    public List<String> getStrings(int numKeys, int size) {
+        List<String> ts = new ArrayList<String>(numKeys);
+        for(int i = 0; i < numKeys; i++)
+            ts.add(randomLetters(size));
+        return ts;
+    }
+
+    public List<byte[]> getByteValues(int numValues, int size) {
+        List<byte[]> values = new ArrayList<byte[]>();
+        for(int i = 0; i < numValues; i++)
+            values.add(TUtils.randomBytes(size));
+        return values;
+    }
+
+    public List<ByteArray> getByteArrayValues(int numValues, int size) {
+        List<ByteArray> values = new ArrayList<ByteArray>();
+        for(int i = 0; i < numValues; i++)
+            values.add(new ByteArray(TUtils.randomBytes(size)));
+        return values;
+    }
+
+    public K getKey() {
+        return getKeys(1).get(0);
+    }
+
+    public V getValue() {
+        return getValues(1).get(0);
+    }
+
+    public IVersion getExpectedVersionAfterPut(IVersion version) {
+        return version;
+    }
+
+    protected boolean valuesEqual(V t1, V t2) {
+        if (t1 instanceof byte[]) return Arrays.equals((byte[])t1, (byte[])t2);
+        return Objects.equal(t1, t2);
+    }
+
+    protected void bassertEquals(String message, Versioned<V> v1, Versioned<V> v2) {
+        String assertTrueMessage = v1 + " != " + v2 + ".";
+        if(message != null)
+            assertTrueMessage += message;
+        assertTrue(assertTrueMessage, valuesEqual(v1.getValue(), v2.getValue()));
+        assertEquals(message, v1.getVersion(), v2.getVersion());
+    }
+
+    protected void bassertEquals(Versioned<V> v1, Versioned<V> v2) {
+        bassertEquals(null, v1, v2);
+    }
+
+    public void assertContains(Collection<Versioned<V>> collection, Versioned<V> value) {
+        boolean found = false;
+        for(Versioned<V> t: collection)
+            if(valuesEqual(t.getValue(), value.getValue()))
+                found = true;
+        assertTrue(collection + " does not contain " + value + ".", found);
+    }
+
+    @Test
+    public void testNullKeys() throws Exception {
+        IStore<K, V> store = getStore();
+        try {
+            store.put(null, new Versioned<V>(getValue()));
+            fail("Store should not put null keys!");
+        } catch(IllegalArgumentException e) {
+            // this is good
+        }
+        try {
+            store.get(null);
+            fail("Store should not get null keys!");
+        } catch(IllegalArgumentException e) {
+            // this is good
+        }
+    }
+
+    @Test
+    public void testPutNullValue() throws Exception {
+        IStore<K,V> store = getStore();
+        K key = getKey();
+        store.put(key, new Versioned<V>(null));
+        List<Versioned<V>> found = store.get(key);
+        assertEquals("Wrong number of values.", 1, found.size());
+        assertEquals("Returned non-null value.", null,
+                     found.get(0).getValue());
+    }
+
+    @Test
+    public void testGetAndDeleteNonExistentKey() throws Exception {
+        K key = getKey();
+        IStore<K, V> store = getStore();
+        List<Versioned<V>> found = store.get(key);
+        assertEquals("Found non-existent key: " + found, 0, found.size());
+    }
+
+    private void testObsoletePutFails(String message,
+                                      IStore<K, V> store,
+                                      K key,
+                                      Versioned<V> versioned) throws SyncException {
+        VectorClock clock = (VectorClock) versioned.getVersion();
+        clock = clock.clone();
+        try {
+            store.put(key, versioned);
+            fail(message);
+        } catch(ObsoleteVersionException e) {
+            // this is good, but check that we didn't fuck with the version
+            assertEquals(clock, versioned.getVersion());
+        }
+    }
+
+    @Test
+    public void testFetchedEqualsPut() throws Exception {
+        K key = getKey();
+        IStore<K, V> store = getStore();
+        VectorClock clock = getClock(1, 1, 2, 3, 3, 4);
+        V value = getValue();
+        assertEquals("Store not empty at start!", 0, store.get(key).size());
+        Versioned<V> versioned = new Versioned<V>(value, clock);
+        store.put(key, versioned);
+        List<Versioned<V>> found = store.get(key);
+        assertEquals("Should only be one version stored.", 1, found.size());
+        assertTrue("Values not equal!", valuesEqual(versioned.getValue(), found.get(0).getValue()));
+    }
+
+    @Test
+    public void testVersionedPut() throws Exception {
+        K key = getKey();
+        IStore<K, V> store = getStore();
+        VectorClock clock = getClock(1, 1);
+        VectorClock clockCopy = clock.clone();
+        V value = getValue();
+        assertEquals("Store not empty at start!", 0, store.get(key).size());
+        Versioned<V> versioned = new Versioned<V>(value, clock);
+
+        // put initial version
+        store.put(key, versioned);
+        assertContains(store.get(key), versioned);
+
+        // test that putting obsolete versions fails
+        testObsoletePutFails("Put of identical version/value succeeded.",
+                             store,
+                             key,
+                             new Versioned<V>(value, clockCopy));
+        testObsoletePutFails("Put of identical version succeeded.",
+                             store,
+                             key,
+                             new Versioned<V>(getValue(), clockCopy));
+        testObsoletePutFails("Put of obsolete version succeeded.",
+                             store,
+                             key,
+                             new Versioned<V>(getValue(), getClock(1)));
+        assertEquals("Should still only be one version in store.", store.get(key).size(), 1);
+        assertContains(store.get(key), versioned);
+
+        // test that putting a concurrent version succeeds
+        if(allowConcurrentOperations()) {
+            store.put(key, new Versioned<V>(getValue(), getClock(1, 2)));
+            assertEquals(2, store.get(key).size());
+        } else {
+            try {
+                store.put(key, new Versioned<V>(getValue(), getClock(1, 2)));
+                fail();
+            } catch(ObsoleteVersionException e) {
+                // expected
+            }
+        }
+
+        // test that putting an incremented version succeeds
+        Versioned<V> newest = new Versioned<V>(getValue(), getClock(1, 1, 2, 2));
+        store.put(key, newest);
+        assertContains(store.get(key), newest);
+    }
+
+    @Test
+    public void testGetVersions() throws Exception {
+        List<K> keys = getKeys(2);
+        K key = keys.get(0);
+        V value = getValue();
+        IStore<K, V> store = getStore();
+        store.put(key, Versioned.value(value));
+        List<Versioned<V>> versioneds = store.get(key);
+        List<IVersion> versions = store.getVersions(key);
+        assertEquals(1, versioneds.size());
+        assertTrue(versions.size() > 0);
+        for(int i = 0; i < versions.size(); i++)
+            assertEquals(versioneds.get(0).getVersion(), versions.get(i));
+
+        assertEquals(0, store.getVersions(keys.get(1)).size());
+    }
+
+    @Test
+    public void testCloseIsIdempotent() throws Exception {
+        IStore<K, V> store = getStore();
+        store.close();
+        // second close is okay, should not throw an exception
+        store.close();
+    }
+
+    @Test
+    public void testEntries() throws Exception {
+        IStore<K, V> store = getStore();
+        int putCount = 537;
+        List<K> keys = getKeys(putCount);
+        List<V> values = getValues(putCount);
+        assertEquals(putCount, values.size());
+        for(int i = 0; i < putCount; i++)
+            store.put(keys.get(i), new Versioned<V>(values.get(i)));
+        
+        HashMap<K, V> map = new HashMap<K, V>();
+        for (int i = 0; i < keys.size(); i++) {
+            map.put(keys.get(i), values.get(i));
+        }
+
+        IClosableIterator<Entry<K, List<Versioned<V>>>> iter = store.entries();
+        int size = 0;
+        try {
+            while (iter.hasNext()) {
+                Entry<K, List<Versioned<V>>> e = iter.next();
+                size += 1;
+                assertGetAllValues(map.get(e.getKey()), e.getValue());
+
+            }
+        } finally {
+            iter.close();
+        }
+        assertEquals("Number of entries", keys.size(), size);
+    }
+
+    protected void assertGetAllValues(V expectedValue, List<Versioned<V>> versioneds) {
+        assertEquals(1, versioneds.size());
+        valuesEqual(expectedValue, versioneds.get(0).getValue());
+    }
+
+    protected boolean allowConcurrentOperations() {
+        return true;
+    }
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/store/InMemoryStorageEngineTest.java b/src/test/java/org/sdnplatform/sync/internal/store/InMemoryStorageEngineTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..84bf376136d0cf3141abf7216ce6eb53b8f77047
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/store/InMemoryStorageEngineTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.store;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.sdnplatform.sync.internal.TUtils;
+import org.sdnplatform.sync.internal.store.IStorageEngine;
+import org.sdnplatform.sync.internal.store.InMemoryStorageEngine;
+import org.sdnplatform.sync.internal.util.ByteArray;
+
+
+public class InMemoryStorageEngineTest extends AbstractStorageEngineT {
+
+    private IStorageEngine<ByteArray, byte[]> store;
+
+    @Override
+    public IStorageEngine<ByteArray, byte[]> getStorageEngine() {
+        return store;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        this.store = new InMemoryStorageEngine<ByteArray, byte[]>("test");
+    }
+
+    @Override
+    public List<ByteArray> getKeys(int numKeys) {
+        List<ByteArray> keys = new ArrayList<ByteArray>(numKeys);
+        for(int i = 0; i < numKeys; i++)
+            keys.add(new ByteArray(TUtils.randomBytes(10)));
+        return keys;
+    }
+
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/store/JacksonStoreTest.java b/src/test/java/org/sdnplatform/sync/internal/store/JacksonStoreTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0846cf40c73bf5e1af7842a316fd3b9929b3b8d0
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/store/JacksonStoreTest.java
@@ -0,0 +1,46 @@
+package org.sdnplatform.sync.internal.store;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.store.InMemoryStorageEngine;
+import org.sdnplatform.sync.internal.store.JacksonStore;
+import org.sdnplatform.sync.internal.util.ByteArray;
+
+
+public class JacksonStoreTest extends AbstractStoreT<Key, TBean> {
+
+    @Override
+    public IStore<Key, TBean> getStore() throws Exception {
+        IStore<ByteArray,byte[]> ims =
+                new InMemoryStorageEngine<ByteArray,byte[]>("test");
+        IStore<Key,TBean> js =
+                new JacksonStore<Key, TBean>(ims, Key.class, TBean.class);
+        return js;
+    }
+
+    @Override
+    public List<TBean> getValues(int numValues) {
+        List<TBean> v = new ArrayList<TBean>(numValues);
+        for (int i = 0; i < numValues; i++) {
+            TBean tb = new TBean();
+            tb.setI(i);
+            tb.setS("" + i);
+            v.add(tb);
+        }
+        return v;
+    }
+
+    @Override
+    public List<Key> getKeys(int numKeys) {
+        List<Key> k = new ArrayList<Key>(numKeys);
+        for (int i = 0; i < numKeys; i++) {
+            Key tk = new Key("com.bigswitch.bigsync.internal.store", "" + i);
+            k.add(tk);
+        }
+        return k;
+    }
+
+
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/store/JavaDBStorageEngineTest.java b/src/test/java/org/sdnplatform/sync/internal/store/JavaDBStorageEngineTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f01246aef8f66b5287ddc77ac4d749b229644a4
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/store/JavaDBStorageEngineTest.java
@@ -0,0 +1,64 @@
+package org.sdnplatform.sync.internal.store;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.ConnectionPoolDataSource;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sdnplatform.sync.internal.TUtils;
+import org.sdnplatform.sync.internal.store.IStorageEngine;
+import org.sdnplatform.sync.internal.store.JavaDBStorageEngine;
+import org.sdnplatform.sync.internal.util.ByteArray;
+import org.sdnplatform.sync.internal.version.VectorClock;
+
+import static org.junit.Assert.*;
+import static org.sdnplatform.sync.internal.TUtils.*;
+
+
+public class JavaDBStorageEngineTest extends AbstractStorageEngineT {
+
+    private IStorageEngine<ByteArray, byte[]> store;
+
+    @Before
+    public void setUp() throws Exception {
+        ConnectionPoolDataSource dataSource = 
+                JavaDBStorageEngine.getDataSource(true);
+        this.store = new JavaDBStorageEngine("test", dataSource); 
+    }
+    
+    @After
+    public void tearDown() throws Exception {
+        this.store.truncate();
+        this.store.close();
+        this.store = null;
+    }
+    
+    @Override
+    public IStorageEngine<ByteArray, byte[]> getStorageEngine() {
+        return store;
+    }
+
+    @Override
+    public List<ByteArray> getKeys(int numKeys) {
+        List<ByteArray> keys = new ArrayList<ByteArray>(numKeys);
+        for(int i = 0; i < numKeys; i++)
+            keys.add(new ByteArray(TUtils.randomBytes(10)));
+        return keys;
+    }
+
+    @Test
+    public void testSerialization() throws Exception {
+        ObjectMapper mapper = new ObjectMapper();
+        VectorClock clock = getClock(1,2);
+        String cs = mapper.writeValueAsString(clock);
+        VectorClock reconstructed = 
+                mapper.readValue(cs, new TypeReference<VectorClock>() {});
+        assertEquals(clock, reconstructed);
+    }
+    
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/store/Key.java b/src/test/java/org/sdnplatform/sync/internal/store/Key.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f290fb377244d11e07f31ad74c168acb45bc853
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/store/Key.java
@@ -0,0 +1,67 @@
+package org.sdnplatform.sync.internal.store;
+
+import java.io.Serializable;
+
+/**
+ * Represent a key in the sync system.  Keys consist of a namespace and a
+ * key name.  Namespaces should be dot-separated such as "com.bigswitch.device"
+ * @author readams
+ *
+ */
+public class Key implements Serializable {
+
+    private static final long serialVersionUID = -3998115385199627376L;
+
+    private String namespace;
+    private String key;
+
+    public Key() {
+        super();
+    }
+
+    public Key(String namespace, String key) {
+        super();
+        this.namespace = namespace;
+        this.key = key;
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+    public String getKey() {
+        return key;
+    }
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((key == null) ? 0 : key.hashCode());
+        result =
+                prime * result
+                        + ((namespace == null) ? 0 : namespace.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;
+        Key other = (Key) obj;
+        if (key == null) {
+            if (other.key != null) return false;
+        } else if (!key.equals(other.key)) return false;
+        if (namespace == null) {
+            if (other.namespace != null) return false;
+        } else if (!namespace.equals(other.namespace)) return false;
+        return true;
+    }
+
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/store/RemoteStoreTest.java b/src/test/java/org/sdnplatform/sync/internal/store/RemoteStoreTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4794abec08601a7494093ad50315804bbf939316
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/store/RemoteStoreTest.java
@@ -0,0 +1,79 @@
+package org.sdnplatform.sync.internal.store;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.debugcounter.IDebugCounterService;
+import net.floodlightcontroller.debugcounter.NullDebugCounter;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.threadpool.ThreadPool;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.After;
+import org.junit.Before;
+import org.sdnplatform.sync.ISyncService.Scope;
+import org.sdnplatform.sync.internal.SyncManager;
+import org.sdnplatform.sync.internal.remote.RemoteSyncManager;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.util.ByteArray;
+
+
+public class RemoteStoreTest extends AbstractStoreT<ByteArray,byte[]> {
+    ThreadPool tp;
+    SyncManager syncManager;
+    protected final static ObjectMapper mapper = new ObjectMapper();
+    
+    RemoteSyncManager remoteSyncManager;
+
+    @Before
+    public void setUp() throws Exception {
+        FloodlightModuleContext fmc = new FloodlightModuleContext();
+        tp = new ThreadPool();
+
+        fmc.addService(IThreadPoolService.class, tp);
+        fmc.addService(IDebugCounterService.class, new NullDebugCounter());
+        syncManager = new SyncManager();
+        syncManager.registerStore("local", Scope.LOCAL);
+
+        remoteSyncManager = new RemoteSyncManager();
+        
+        tp.init(fmc);
+        syncManager.init(fmc);
+        remoteSyncManager.init(fmc);
+        tp.startUp(fmc);
+        syncManager.startUp(fmc);
+        remoteSyncManager.startUp(fmc);
+    }
+
+    @After
+    public void tearDown() {
+        tp.getScheduledExecutor().shutdownNow();
+        tp = null;
+        syncManager.shutdown();
+        remoteSyncManager.shutdown();
+    }
+
+    @Override
+    public IStore<ByteArray, byte[]> getStore() throws Exception {
+        return remoteSyncManager.getStore("local");
+    }
+
+    @Override
+    public List<byte[]> getValues(int numValues) {
+        ArrayList<byte[]> r = new ArrayList<byte[]>();
+        for (int i = 0; i < numValues; i++) {
+            r.add(Integer.toString(i).getBytes());
+        }
+        return r;
+    }
+
+    @Override
+    public List<ByteArray> getKeys(int numKeys) {
+        ArrayList<ByteArray> r = new ArrayList<ByteArray>();
+        for (int i = 0; i < numKeys; i++) {
+            r.add(new ByteArray(Integer.toString(i).getBytes()));
+        }
+        return r;
+    }
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/store/TBean.java b/src/test/java/org/sdnplatform/sync/internal/store/TBean.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a720438335d420a466f32c4392a3096e515c9dc
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/store/TBean.java
@@ -0,0 +1,51 @@
+package org.sdnplatform.sync.internal.store;
+
+public class TBean {
+    String s;
+    int i;
+
+    public TBean(String s, int i) {
+        super();
+        this.s = s;
+        this.i = i;
+    }
+    public TBean() {
+        super();
+    }
+    public String getS() {
+        return s;
+    }
+    public void setS(String s) {
+        this.s = s;
+    }
+    public int getI() {
+        return i;
+    }
+    public void setI(int i) {
+        this.i = i;
+    }
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + i;
+        result = prime * result + ((s == null) ? 0 : s.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;
+        TBean other = (TBean) obj;
+        if (i != other.i) return false;
+        if (s == null) {
+            if (other.s != null) return false;
+        } else if (!s.equals(other.s)) return false;
+        return true;
+    }
+    @Override
+    public String toString() {
+        return "TestBean [s=" + s + ", i=" + i + "]";
+    }
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/version/ClockEntryTest.java b/src/test/java/org/sdnplatform/sync/internal/version/ClockEntryTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..46ae2cddd49e6a1ed2a1096a7d2445c1050d8c46
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/version/ClockEntryTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.version;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.sdnplatform.sync.internal.version.ClockEntry;
+
+
+/**
+ *
+ */
+public class ClockEntryTest {
+
+    @Test
+    public void testEquality() {
+        ClockEntry v1 = new ClockEntry((short) 0, 1);
+        ClockEntry v2 = new ClockEntry((short) 0, 1);
+        assertTrue(v1.equals(v1));
+        assertTrue(!v1.equals(null));
+        assertEquals(v1, v2);
+
+        v1 = new ClockEntry((short) 0, 1);
+        v2 = new ClockEntry((short) 0, 2);
+        assertTrue(!v1.equals(v2));
+
+        v1 = new ClockEntry(Short.MAX_VALUE, 256);
+        v2 = new ClockEntry(Short.MAX_VALUE, 256);
+        assertEquals(v1, v2);
+    }
+
+    @Test
+    public void testIncrement() {
+        ClockEntry v = new ClockEntry((short) 0, 1);
+        assertEquals(v.getNodeId(), 0);
+        assertEquals(v.getVersion(), 1);
+        ClockEntry v2 = v.incremented();
+        assertEquals(v.getVersion(), 1);
+        assertEquals(v2.getVersion(), 2);
+    }
+
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/version/VectorClockInconsistencyResolverTest.java b/src/test/java/org/sdnplatform/sync/internal/version/VectorClockInconsistencyResolverTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ad45ec9cd15400658c48085d8003a82415ce0fd
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/version/VectorClockInconsistencyResolverTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.version;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sdnplatform.sync.IInconsistencyResolver;
+import org.sdnplatform.sync.Versioned;
+import org.sdnplatform.sync.internal.TUtils;
+import org.sdnplatform.sync.internal.version.VectorClockInconsistencyResolver;
+
+
+public class VectorClockInconsistencyResolverTest {
+
+    private IInconsistencyResolver<Versioned<String>> resolver;
+    private Versioned<String> later;
+    private Versioned<String> prior;
+    private Versioned<String> current;
+    private Versioned<String> concurrent;
+    private Versioned<String> concurrent2;
+
+    @Before
+    public void setUp() {
+        resolver = new VectorClockInconsistencyResolver<String>();
+        current = getVersioned(1, 1, 2, 3);
+        prior = getVersioned(1, 2, 3);
+        concurrent = getVersioned(1, 2, 3, 3);
+        concurrent2 = getVersioned(1, 2, 3, 4);
+        later = getVersioned(1, 1, 2, 2, 3);
+    }
+
+    private Versioned<String> getVersioned(int... nodes) {
+        return new Versioned<String>("my-value", TUtils.getClock(nodes));
+    }
+
+    @Test
+    public void testEmptyList() {
+        assertEquals(0, resolver.resolveConflicts(new ArrayList<Versioned<String>>()).size());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testDuplicatesResolve() {
+        assertEquals(2, resolver.resolveConflicts(Arrays.asList(concurrent,
+                                                                current,
+                                                                current,
+                                                                concurrent,
+                                                                current)).size());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testResolveNormal() {
+        assertEquals(later, resolver.resolveConflicts(Arrays.asList(current, prior, later)).get(0));
+        assertEquals(later, resolver.resolveConflicts(Arrays.asList(prior, current, later)).get(0));
+        assertEquals(later, resolver.resolveConflicts(Arrays.asList(later, current, prior)).get(0));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testResolveConcurrent() {
+        List<Versioned<String>> resolved = resolver.resolveConflicts(Arrays.asList(current,
+                                                                                   concurrent,
+                                                                                   prior));
+        assertEquals(2, resolved.size());
+        assertTrue("Version not found", resolved.contains(current));
+        assertTrue("Version not found", resolved.contains(concurrent));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testResolveLargerConcurrent() {
+        assertEquals(3, resolver.resolveConflicts(Arrays.asList(concurrent,
+                                                                concurrent2,
+                                                                current,
+                                                                concurrent2,
+                                                                current,
+                                                                concurrent,
+                                                                current)).size());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testResolveConcurrentPairWithLater() {
+        Versioned<String> later2 = getVersioned(1, 2, 3, 3, 4, 4);
+        List<Versioned<String>> resolved = resolver.resolveConflicts(Arrays.asList(concurrent,
+                                                                                   concurrent2,
+                                                                                   later2));
+        assertEquals(1, resolved.size());
+        assertEquals(later2, resolved.get(0));
+    }
+}
diff --git a/src/test/java/org/sdnplatform/sync/internal/version/VectorClockTest.java b/src/test/java/org/sdnplatform/sync/internal/version/VectorClockTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a15c77b4df99b8b5d0e07f79d954bfb9baf58e04
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/internal/version/VectorClockTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2008-2009 LinkedIn, 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 org.sdnplatform.sync.internal.version;
+
+import static org.junit.Assert.*;
+import static org.sdnplatform.sync.internal.TUtils.getClockT;
+
+import org.junit.Test;
+import org.sdnplatform.sync.IVersion.Occurred;
+import static org.sdnplatform.sync.internal.TUtils.*;
+
+import org.sdnplatform.sync.internal.version.ClockEntry;
+import org.sdnplatform.sync.internal.version.VectorClock;
+
+import com.google.common.collect.Lists;
+
+/**
+ * VectorClock tests
+ *
+ *
+ */
+
+public class VectorClockTest {
+    @Test
+    public void testEqualsAndHashcode() {
+        long now = 5555555555L;
+        VectorClock one = getClockT(now, 1, 2);
+        VectorClock other = getClockT(now, 1, 2);
+        assertEquals(one, other);
+        assertEquals(one.hashCode(), other.hashCode());
+    }
+
+    @Test
+    public void testComparisons() {
+        assertTrue("The empty clock should not happen before itself.",
+                   getClock().compare(getClock()) != Occurred.CONCURRENTLY);
+        assertTrue("A clock should not happen before an identical clock.",
+                   getClock(1, 1, 2).compare(getClock(1, 1, 2)) != Occurred.CONCURRENTLY);
+        assertTrue(" A clock should happen before an identical clock with a single additional event.",
+                   getClock(1, 1, 2).compare(getClock(1, 1, 2, 3)) == Occurred.BEFORE);
+        assertTrue("Clocks with different events should be concurrent.",
+                   getClock(1).compare(getClock(2)) == Occurred.CONCURRENTLY);
+        assertTrue("Clocks with different events should be concurrent.",
+                   getClock(1, 1, 2).compare(getClock(1, 1, 3)) == Occurred.CONCURRENTLY);
+        assertTrue(getClock(2, 2).compare(getClock(1, 2, 2, 3)) == Occurred.BEFORE
+                   && getClock(1, 2, 2, 3).compare(getClock(2, 2)) == Occurred.AFTER);
+    }
+
+    @Test
+    public void testMerge() {
+        // merging two clocks should create a clock contain the element-wise
+        // maximums
+        
+        assertEquals("Two empty clocks merge to an empty clock.",
+                     getClock().merge(getClock()).getEntries(),
+                     getClock().getEntries());
+        assertEquals("Merge of a clock with itself does nothing",
+                     getClock(1).merge(getClock(1)).getEntries(),
+                     getClock(1).getEntries());
+        assertEquals(getClock(1).merge(getClock(2)).getEntries(), getClock(1, 2).getEntries());
+        assertEquals(getClock(1).merge(getClock(1, 2)).getEntries(), getClock(1, 2).getEntries());
+        assertEquals(getClock(1, 2).merge(getClock(1)).getEntries(), getClock(1, 2).getEntries());
+        assertEquals("Two-way merge fails.",
+                     getClock(1, 1, 1, 2, 3, 5).merge(getClock(1, 2, 2, 4)).getEntries(),
+                     getClock(1, 1, 1, 2, 2, 3, 4, 5).getEntries());
+        assertEquals(getClock(2, 3, 5).merge(getClock(1, 2, 2, 4, 7)).getEntries(),
+                     getClock(1, 2, 2, 3, 4, 5, 7).getEntries());
+    }
+
+    /**
+     * See gihub issue #25: Incorrect coersion of version to short before
+     * passing to ClockEntry constructor
+     */
+    @Test
+    public void testMergeWithLargeVersion() {
+        VectorClock clock1 = getClock(1);
+        VectorClock clock2 = new VectorClock(Lists.newArrayList(new ClockEntry((short) 1,
+                                                                               Short.MAX_VALUE + 1)),
+                                             System.currentTimeMillis());
+        VectorClock mergedClock = clock1.merge(clock2);
+        assertEquals(mergedClock.getMaxVersion(), Short.MAX_VALUE + 1);
+    }
+
+    @Test
+    public void testIncrementOrderDoesntMatter() {
+        // Clocks should have the property that no matter what order the
+        // increment operations are done in the resulting clocks are equal
+        int numTests = 10;
+        int numNodes = 10;
+        int numValues = 100;
+        VectorClock[] clocks = new VectorClock[numNodes];
+        for(int t = 0; t < numTests; t++) {
+            int[] test = randomInts(numNodes, numValues);
+            for(int n = 0; n < numNodes; n++)
+                clocks[n] = getClock(shuffle(test));
+            // test all are equal
+            for(int n = 0; n < numNodes - 1; n++)
+                assertEquals("Clock " + n + " and " + (n + 1) + " are not equal.",
+                             clocks[n].getEntries(),
+                             clocks[n + 1].getEntries());
+        }
+    }
+/*
+    public void testIncrementAndSerialize() {
+        int node = 1;
+        VectorClock vc = getClock(node);
+        assertEquals(node, vc.getMaxVersion());
+        int increments = 3000;
+        for(int i = 0; i < increments; i++) {
+            vc.incrementVersion(node, 45);
+            // serialize
+            vc = new VectorClock(vc.toBytes());
+        }
+        assertEquals(increments + 1, vc.getMaxVersion());
+    } */
+
+}
diff --git a/src/test/java/org/sdnplatform/sync/test/MockSyncService.java b/src/test/java/org/sdnplatform/sync/test/MockSyncService.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4f1c63261b897bc55875d5db0c8bdfb4a51c5e9
--- /dev/null
+++ b/src/test/java/org/sdnplatform/sync/test/MockSyncService.java
@@ -0,0 +1,122 @@
+package org.sdnplatform.sync.test;
+
+import java.util.Collection;
+import java.util.HashMap;
+
+import org.sdnplatform.sync.ISyncService;
+import org.sdnplatform.sync.error.SyncException;
+import org.sdnplatform.sync.error.UnknownStoreException;
+import org.sdnplatform.sync.internal.AbstractSyncManager;
+import org.sdnplatform.sync.internal.store.IStorageEngine;
+import org.sdnplatform.sync.internal.store.IStore;
+import org.sdnplatform.sync.internal.store.InMemoryStorageEngine;
+import org.sdnplatform.sync.internal.store.ListenerStorageEngine;
+import org.sdnplatform.sync.internal.store.MappingStoreListener;
+import org.sdnplatform.sync.internal.util.ByteArray;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+
+/**
+ * Mock sync service useful for testing
+ * @author readams
+ */
+public class MockSyncService extends AbstractSyncManager {
+    /**
+     * The storage engines that contain the locally-stored data
+     */
+    private HashMap<String,ListenerStorageEngine> localStores =
+            new HashMap<String, ListenerStorageEngine>();
+    
+    
+    // ************
+    // ISyncService
+    // ************
+
+    @Override
+    public void registerStore(String storeName, Scope scope)
+            throws SyncException {
+        ListenerStorageEngine store = localStores.get(storeName);
+        if (store != null) return;
+        IStorageEngine<ByteArray, byte[]> memstore =
+                new InMemoryStorageEngine<ByteArray, byte[]>(storeName);
+        store = new ListenerStorageEngine(memstore, null);
+        localStores.put(storeName, store);
+    }
+
+    /**
+     * Persistent stores are not actually persistent in the mock sync service
+     * @see ISyncService#registerPersistentStore(String, 
+     * org.sdnplatform.sync.ISyncService.Scope)
+     */
+    @Override
+    public void registerPersistentStore(String storeName, Scope scope)
+            throws SyncException {
+        registerStore(storeName, scope);
+    }
+    
+    // *****************
+    // IFloodlightModule
+    // *****************
+
+    @Override
+    public void init(FloodlightModuleContext context)
+                    throws FloodlightModuleException {
+        
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+        
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        return null;
+    }
+
+    // *******************
+    // AbstractSyncManager
+    // *******************
+    
+    @Override
+    public IStore<ByteArray, byte[]>
+            getStore(String storeName) throws UnknownStoreException {
+        return localStores.get(storeName);
+    }
+
+    @Override
+    public short getLocalNodeId() {
+        return Short.MAX_VALUE;
+    }
+
+    @Override
+    public void addListener(String storeName, MappingStoreListener listener)
+            throws UnknownStoreException {
+        ListenerStorageEngine store = localStores.get(storeName);
+        if (store == null) 
+            throw new UnknownStoreException("Store " + storeName + 
+                                            " has not been registered");
+        store.addListener(listener);
+    }
+
+    @Override
+    public void shutdown() {
+        
+    }
+
+    // ***************
+    // MockSyncService
+    // ***************
+    
+    /**
+     * Reset to pristine condition
+     */
+    public void reset() {
+        localStores = new HashMap<String, ListenerStorageEngine>();
+    }
+}