Fix State

Change-Id: Ifced47b044cfd994eeaf53991f516ef702dd8eaa
diff --git a/common.hpp b/common.hpp
index 50555fc..98a884c 100644
--- a/common.hpp
+++ b/common.hpp
@@ -50,10 +50,12 @@
 #include <boost/algorithm/string.hpp>
 #include <boost/asio.hpp>
 #include <boost/assert.hpp>
+#include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/property_tree/ptree.hpp>
 #include <boost/scoped_ptr.hpp>
+#include <boost/tuple/tuple.hpp>
 
 namespace chronosync {
 
diff --git a/src/state.cpp b/src/state.cpp
new file mode 100644
index 0000000..9d434ec
--- /dev/null
+++ b/src/state.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
+/*
+ * Copyright (c) 2012-2014 University of California, Los Angeles
+ *
+ * This file is part of ChronoSync, synchronization library for distributed realtime
+ * applications for NDN.
+ *
+ * ChronoSync is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ChronoSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ChronoSync, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Zhenkai Zhu <http://irl.cs.ucla.edu/~zhenkai/>
+ * @author Chaoyi Bian <bcy@pku.edu.cn>
+ * @author Alexander Afanasyev <http://lasr.cs.ucla.edu/afanasyev/index.html>
+ * @author Yingdi Yu <yingdi@cs.ucla.edu>
+ */
+
+#include "state.hpp"
+
+namespace chronosync {
+
+using boost::make_tuple;
+
+State::~State()
+{
+}
+
+/**
+ * @brief Add or update leaf to the sync tree
+ *
+ * @param info session name of the leaf
+ * @param seq  sequence number of the leaf
+ * @return 3-tuple (isInserted, isUpdated, oldSeqNo)
+ */
+boost::tuple<bool, bool, SeqNo>
+State::update(const Name& info, const SeqNo& seq)
+{
+  m_wire.reset();
+
+  LeafContainer::iterator leaf = m_leaves.find(info);
+
+  if (leaf == m_leaves.end()) {
+    m_leaves.insert(make_shared<Leaf>(info, cref(seq)));
+    return make_tuple(true, false, 0);
+  }
+  else {
+    if ((*leaf)->getSeq() == seq || seq < (*leaf)->getSeq()) {
+      return make_tuple(false, false, 0);
+    }
+
+    SeqNo old = (*leaf)->getSeq();
+    m_leaves.modify(leaf,
+                    bind(&Leaf::setSeq, _1, seq));
+    return make_tuple(false, true, old);
+  }
+}
+
+ndn::ConstBufferPtr
+State::getRootDigest() const
+{
+  m_digest.reset();
+
+  BOOST_FOREACH (ConstLeafPtr leaf, m_leaves.get<ordered>())
+    {
+      BOOST_ASSERT(leaf != 0);
+      m_digest.update(leaf->getDigest()->buf(), leaf->getDigest()->size());
+    }
+
+  return m_digest.computeDigest();
+}
+
+
+void
+State::reset()
+{
+  m_leaves.clear();
+}
+
+State&
+State::operator+=(const State& state)
+{
+  BOOST_FOREACH (ConstLeafPtr leaf, state.getLeaves())
+    {
+      BOOST_ASSERT(leaf != 0);
+      update(leaf->getSessionName(), leaf->getSeq());
+    }
+
+  return *this;
+}
+
+template<bool T>
+size_t
+State::wireEncode(ndn::EncodingImpl<T>& block) const
+{
+  size_t totalLength = 0;
+
+  BOOST_REVERSE_FOREACH (ConstLeafPtr leaf, m_leaves.get<ordered>())
+    {
+      size_t entryLength = 0;
+      entryLength += prependNonNegativeIntegerBlock(block, tlv::SeqNo, leaf->getSeq());
+      entryLength += leaf->getSessionName().wireEncode(block);
+      entryLength += block.prependVarNumber(entryLength);
+      entryLength += block.prependVarNumber(tlv::StateLeaf);
+      totalLength += entryLength;
+    }
+
+  totalLength += block.prependVarNumber(totalLength);
+  totalLength += block.prependVarNumber(tlv::SyncReply);
+
+  return totalLength;
+}
+
+template size_t
+State::wireEncode<true>(ndn::EncodingImpl<true>& block) const;
+
+template size_t
+State::wireEncode<false>(ndn::EncodingImpl<false>& block) const;
+
+const Block&
+State::wireEncode() const
+{
+  if (m_wire.hasWire())
+    return m_wire;
+
+  ndn::EncodingEstimator estimator;
+  size_t estimatedSize = wireEncode(estimator);
+
+  ndn::EncodingBuffer buffer(estimatedSize, 0);
+  wireEncode(buffer);
+
+  m_wire = buffer.block();
+  return m_wire;
+}
+
+void
+State::wireDecode(const Block& wire)
+{
+  if (!wire.hasWire())
+    throw Error("The supplied block does not contain wire format");
+
+  if (wire.type() != tlv::SyncReply)
+    throw Error("Unexpected TLV type when decoding SyncReply: " +
+                boost::lexical_cast<std::string>(m_wire.type()));
+
+  wire.parse();
+  m_wire = wire;
+
+  for (Block::element_const_iterator it = wire.elements_begin();
+       it != wire.elements_end(); it++) {
+    if (it->type() == tlv::StateLeaf) {
+      it->parse();
+
+      Block::element_const_iterator val = it->elements_begin();
+      Name info(*val);
+      val++;
+
+      if (val != it->elements_end())
+        update(info, readNonNegativeInteger(*val));
+      else
+        throw Error("No seqNo when decoding SyncReply");
+    }
+  }
+}
+
+} // namespace chronosync
diff --git a/src/state.hpp b/src/state.hpp
new file mode 100644
index 0000000..4fa82e9
--- /dev/null
+++ b/src/state.hpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
+/*
+ * Copyright (c) 2012-2014 University of California, Los Angeles
+ *
+ * This file is part of ChronoSync, synchronization library for distributed realtime
+ * applications for NDN.
+ *
+ * ChronoSync is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ChronoSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ChronoSync, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Zhenkai Zhu <http://irl.cs.ucla.edu/~zhenkai/>
+ * @author Chaoyi Bian <bcy@pku.edu.cn>
+ * @author Alexander Afanasyev <http://lasr.cs.ucla.edu/afanasyev/index.html>
+ * @author Yingdi Yu <yingdi@cs.ucla.edu>
+ */
+
+#ifndef CHRONOSYNC_STATE_HPP
+#define CHRONOSYNC_STATE_HPP
+
+#include "tlv.hpp"
+#include "leaf-container.hpp"
+#include <ndn-cxx/util/digest.hpp>
+
+namespace chronosync {
+
+class State;
+typedef shared_ptr<State> StatePtr;
+typedef shared_ptr<const State> ConstStatePtr;
+
+/**
+ * @brief Abstraction of state tree.
+ *
+ * State is used to represent sync tree, it is also the base class of DiffState,
+ * which represent the diff between two states. Due to the second usage, State
+ * should be copyable.
+ */
+class State
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+  virtual
+  ~State();
+
+  /**
+   * @brief Add or update leaf to the sync tree
+   *
+   * @param info session name of the leaf
+   * @param seq  sequence number of the leaf
+   * @return 3-tuple (isInserted, isUpdated, oldSeqNo)
+   */
+  boost::tuple<bool, bool, SeqNo>
+  update(const Name& info, const SeqNo& seq);
+
+  /**
+   * @brief Get state leaves
+   */
+  const LeafContainer&
+  getLeaves() const
+  {
+    return m_leaves;
+  }
+
+  ndn::ConstBufferPtr
+  getRootDigest() const;
+
+  /**
+   * @brief Reset the sync tree, remove all state leaves
+   */
+  void
+  reset();
+
+  /**
+   * @brief Combine `this' state and the supplied state
+   *
+   * The combination result contains all leaves in two states.
+   * When leaves conflict, keep the one with largest seq.
+   *
+   * @param state another state to combine with
+   * @return Combined state
+   */
+  State&
+  operator+=(const State& state);
+
+  /**
+   * @brief Encode to a wire format
+   */
+  const Block&
+  wireEncode() const;
+
+  /**
+   * @brief Decode from the wire format
+   */
+  void
+  wireDecode(const Block& wire);
+
+protected:
+  template<bool T>
+  size_t
+  wireEncode(ndn::EncodingImpl<T>& block) const;
+
+protected:
+  LeafContainer m_leaves;
+
+  mutable ndn::util::Sha256 m_digest;
+  mutable Block m_wire;
+};
+
+} // namespace chronosync
+
+#endif // CHRONOSYNC_STATE_HPP
diff --git a/src/sync-full-state.cc b/src/sync-full-state.cc
deleted file mode 100644
index 651f6ab..0000000
--- a/src/sync-full-state.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
-/*
- * Copyright (c) 2012-2014 University of California, Los Angeles
- *
- * This file is part of ChronoSync, synchronization library for distributed realtime
- * applications for NDN.
- *
- * ChronoSync is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * ChronoSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * ChronoSync, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @author Zhenkai Zhu <http://irl.cs.ucla.edu/~zhenkai/>
- * @author Chaoyi Bian <bcy@pku.edu.cn>
- * @author Alexander Afanasyev <http://lasr.cs.ucla.edu/afanasyev/index.html>
- */
-
-#include "sync-full-state.h"
-
-#include <boost/make_shared.hpp>
-#include <boost/lambda/lambda.hpp>
-#include <boost/lambda/bind.hpp>
-#include <boost/foreach.hpp>
-#include <boost/assert.hpp>
-
-#include "sync-full-leaf.h"
-
-using namespace boost;
-namespace ll = boost::lambda;
-
-namespace Sync {
-
-
-FullState::FullState ()
-// m_lastUpdated is initialized to "not_a_date_time" in normal lib mode and to "0" time in NS-3 mode
-{
-}
-
-FullState::~FullState ()
-{
-}
-
-ndn::time::system_clock::Duration
-FullState::getTimeFromLastUpdate () const
-{
-  return ndn::time::system_clock::now() - m_lastUpdated;
-}
-
-DigestConstPtr
-FullState::getDigest ()
-{
-  if (!m_digest)
-    {
-      m_digest = make_shared<Digest> ();
-      if (m_leaves.get<ordered> ().size () > 0)
-        {
-          BOOST_FOREACH (LeafConstPtr leaf, m_leaves.get<ordered> ())
-            {
-              FullLeafConstPtr fullLeaf = dynamic_pointer_cast<const FullLeaf> (leaf);
-              BOOST_ASSERT (fullLeaf != 0);
-              *m_digest << fullLeaf->getDigest ();
-            }
-          m_digest->finalize ();
-        }
-      else
-        {
-          std::istringstream is ("00"); //zero state
-          is >> *m_digest;
-        }
-    }
-
-  return m_digest;
-}
-
-// from State
-boost::tuple<bool/*inserted*/, bool/*updated*/, SeqNo/*oldSeqNo*/>
-FullState::update (NameInfoConstPtr info, const SeqNo &seq)
-{
-  m_lastUpdated = ndn::time::system_clock::now();
-
-
-  m_digest.reset ();
-
-  LeafContainer::iterator item = m_leaves.find (info);
-  if (item == m_leaves.end ())
-    {
-      m_leaves.insert (make_shared<FullLeaf> (info, cref (seq)));
-      return make_tuple (true, false, SeqNo ());
-    }
-  else
-    {
-      if ((*item)->getSeq () == seq || seq < (*item)->getSeq ())
-        {
-          return make_tuple (false, false, SeqNo ());
-        }
-
-      SeqNo old = (*item)->getSeq ();
-      m_leaves.modify (item,
-                       ll::bind (&Leaf::setSeq, *ll::_1, seq));
-      return make_tuple (false, true, old);
-    }
-}
-
-bool
-FullState::remove (NameInfoConstPtr info)
-{
-  m_lastUpdated = ndn::time::system_clock::now();
-
-  m_digest.reset ();
-
-  LeafContainer::iterator item = m_leaves.find (info);
-  if (item != m_leaves.end ())
-    {
-      m_leaves.erase (item);
-      return true;
-    }
-  else
-    return false;
-}
-
-} // Sync
diff --git a/src/sync-full-state.h b/src/sync-full-state.h
deleted file mode 100644
index 77533b2..0000000
--- a/src/sync-full-state.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
-/*
- * Copyright (c) 2012-2014 University of California, Los Angeles
- *
- * This file is part of ChronoSync, synchronization library for distributed realtime
- * applications for NDN.
- *
- * ChronoSync is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * ChronoSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * ChronoSync, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @author Zhenkai Zhu <http://irl.cs.ucla.edu/~zhenkai/>
- * @author Chaoyi Bian <bcy@pku.edu.cn>
- * @author Alexander Afanasyev <http://lasr.cs.ucla.edu/afanasyev/index.html>
- */
-
-#ifndef SYNC_FULL_STATE_H
-#define SYNC_FULL_STATE_H
-
-#include <ndn-cxx/util/time.hpp>
-#include "sync-state.h"
-
-namespace Sync {
-
-class FullState;
-typedef boost::shared_ptr<FullState> FullStatePtr;
-typedef boost::shared_ptr<FullState> FullStateConstPtr;
-
-
-/**
- * \ingroup sync
- * @brief Cumulative SYNC state
- */
-class FullState : public State
-{
-public:
-  /**
-   * @brief Default constructor
-   */
-  FullState ();
-  virtual ~FullState ();
-
-  /**
-   * @brief Get time period since last state update
-   *
-   * This value can be used to randomize reconciliation waiting time in SyncApp
-   */
-  ndn::time::system_clock::Duration
-  getTimeFromLastUpdate () const;
-
-  /**
-   * @brief Obtain a read-only copy of the digest
-   *
-   * If m_digest is 0, then it is automatically created.  On every update and removal, m_digest is reset to 0
-   */
-  DigestConstPtr
-  getDigest ();
-
-  // from State
-  virtual boost::tuple<bool/*inserted*/, bool/*updated*/, SeqNo/*oldSeqNo*/>
-  update (NameInfoConstPtr info, const SeqNo &seq);
-
-  virtual bool
-  remove (NameInfoConstPtr info);
-
-private:
-  ndn::time::system_clock::TimePoint m_lastUpdated; ///< @brief Time when state was updated last time
-  DigestPtr m_digest;
-};
-
-} // Sync
-
-#endif // SYNC_STATE_H
diff --git a/src/sync-state.cc b/src/sync-state.cc
deleted file mode 100644
index efb53c7..0000000
--- a/src/sync-state.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
-/*
- * Copyright (c) 2012-2014 University of California, Los Angeles
- *
- * This file is part of ChronoSync, synchronization library for distributed realtime
- * applications for NDN.
- *
- * ChronoSync is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * ChronoSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * ChronoSync, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @author Zhenkai Zhu <http://irl.cs.ucla.edu/~zhenkai/>
- * @author Chaoyi Bian <bcy@pku.edu.cn>
- * @author Alexander Afanasyev <http://lasr.cs.ucla.edu/afanasyev/index.html>
- */
-
-#include "sync-state.h"
-#include "sync-diff-leaf.h"
-#include "sync-std-name-info.h"
-
-#include <boost/assert.hpp>
-#include <boost/foreach.hpp>
-#include <boost/shared_ptr.hpp>
-#include <boost/throw_exception.hpp>
-#include <boost/lexical_cast.hpp>
-
-// using namespace std;
-using namespace boost;
-
-typedef error_info<struct tag_errmsg, std::string> info_str;
-
-using namespace Sync::Error;
-
-namespace Sync {
-
-/*
-std::ostream &
-operator << (std::ostream &os, const State &state)
-{
-  os << "<state>"; DEBUG_ENDL;
-
-  BOOST_FOREACH (shared_ptr<const Leaf> leaf, state.getLeaves ().get<ordered> ())
-    {
-      shared_ptr<const DiffLeaf> diffLeaf = dynamic_pointer_cast<const DiffLeaf> (leaf);
-      if (diffLeaf != 0)
-        {
-          os << "<item action=\"" << diffLeaf->getOperation () << "\">"; DEBUG_ENDL;
-        }
-      else
-        {
-          os << "<item>"; DEBUG_ENDL;
-        }
-      os << "<name>" << *leaf->getInfo () << "</name>"; DEBUG_ENDL;
-      if (diffLeaf == 0 || (diffLeaf != 0 && diffLeaf->getOperation () == UPDATE))
-        {
-          os << "<seq>" << leaf->getSeq () << "</seq>"; DEBUG_ENDL;
-        }
-      os << "</item>"; DEBUG_ENDL;
-    }
-  os << "</state>";
-}
-*/
-
-SyncStateMsg &
-operator << (SyncStateMsg &ossm, const State &state)
-{
-  BOOST_FOREACH (shared_ptr<const Leaf> leaf, state.getLeaves ().get<ordered> ())
-  {
-    SyncState *oss = ossm.add_ss();
-    shared_ptr<const DiffLeaf> diffLeaf = dynamic_pointer_cast<const DiffLeaf> (leaf);
-    if (diffLeaf != 0 && diffLeaf->getOperation() != UPDATE)
-    {
-      oss->set_type(SyncState::DELETE);
-    }
-    else
-    {
-      oss->set_type(SyncState::UPDATE);
-    }
-
-    std::ostringstream os;
-    os << *leaf->getInfo();
-    oss->set_name(os.str());
-
-    if (diffLeaf == 0 || (diffLeaf != 0 && diffLeaf->getOperation () == UPDATE))
-    {
-      SyncState::SeqNo *seqNo = oss->mutable_seqno();
-      seqNo->set_session(leaf->getSeq().getSession());
-      seqNo->set_seq(leaf->getSeq().getSeq());
-    }
-  }
-  return ossm;
-}
-
-/*
-std::istream &
-operator >> (std::istream &in, State &state)
-{
-  TiXmlDocument doc;
-  in >> doc;
-
-  if (doc.RootElement() == 0)
-        BOOST_THROW_EXCEPTION (SyncXmlDecodingFailure () << info_str ("Empty XML"));
-
-  for (TiXmlElement *iterator = doc.RootElement()->FirstChildElement ("item");
-       iterator != 0;
-       iterator = iterator->NextSiblingElement("item"))
-    {
-      TiXmlElement *name = iterator->FirstChildElement ("name");
-      if (name == 0 || name->GetText() == 0)
-        BOOST_THROW_EXCEPTION (SyncXmlDecodingFailure () << info_str ("<name> element is missing"));
-
-      NameInfoConstPtr info = StdNameInfo::FindOrCreate (name->GetText());
-
-      if (iterator->Attribute("action") == 0 || strcmp(iterator->Attribute("action"), "update") == 0)
-        {
-          TiXmlElement *seq = iterator->FirstChildElement ("seq");
-          if (seq == 0)
-            BOOST_THROW_EXCEPTION (SyncXmlDecodingFailure () << info_str ("<seq> element is missing"));
-
-          TiXmlElement *session = seq->FirstChildElement ("session");
-          TiXmlElement *seqno = seq->FirstChildElement ("seqno");
-
-          if (session == 0 || session->GetText() == 0)
-            BOOST_THROW_EXCEPTION (SyncXmlDecodingFailure () << info_str ("<session> element is missing"));
-          if (seqno == 0 || seqno->GetText() == 0)
-            BOOST_THROW_EXCEPTION (SyncXmlDecodingFailure () << info_str ("<seqno> element is missing"));
-
-          state.update (info, SeqNo (
-                                     lexical_cast<uint32_t> (session->GetText()),
-                                     lexical_cast<uint32_t> (seqno->GetText())
-                                     ));
-        }
-      else
-        {
-          state.remove (info);
-        }
-    }
-
-  return in;
-}
-*/
-
-SyncStateMsg &
-operator >> (SyncStateMsg &issm, State &state)
-{
-  int n = issm.ss_size();
-  for (int i = 0; i < n; i++)
-  {
-    const SyncState &ss = issm.ss(i);
-    NameInfoConstPtr info = StdNameInfo::FindOrCreate (ss.name());
-    if (ss.type() == SyncState::UPDATE)
-    {
-      uint64_t session = lexical_cast<uint64_t>(ss.seqno().session());
-      uint64_t seq = lexical_cast<uint64_t>(ss.seqno().seq());
-      SeqNo seqNo(session, seq);
-      state.update(info, seqNo);
-    }
-    else
-    {
-      state.remove(info);
-    }
-  }
-  return issm;
-}
-
-}
diff --git a/src/sync-state.h b/src/sync-state.h
deleted file mode 100644
index 4119c95..0000000
--- a/src/sync-state.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
-/*
- * Copyright (c) 2012-2014 University of California, Los Angeles
- *
- * This file is part of ChronoSync, synchronization library for distributed realtime
- * applications for NDN.
- *
- * ChronoSync is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * ChronoSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * ChronoSync, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @author Zhenkai Zhu <http://irl.cs.ucla.edu/~zhenkai/>
- * @author Chaoyi Bian <bcy@pku.edu.cn>
- * @author Alexander Afanasyev <http://lasr.cs.ucla.edu/afanasyev/index.html>
- */
-
-#ifndef SYNC_STATE_H
-#define SYNC_STATE_H
-
-#include "sync-state-leaf-container.h"
-#include <boost/exception/all.hpp>
-#include "boost/tuple/tuple.hpp"
-#include "sync-state.pb.h"
-
-/**
- * \defgroup sync SYNC protocol
- *
- * Implementation of SYNC protocol
- */
-namespace Sync {
-
-/**
- * \ingroup sync
- * @brief this prefix will be used for the dummy node which increases its sequence number whenever
- * a remove operation happens; this is to prevent the reversion of root digest when we prune
- * a branch, i.e. help the root digest to be forward only
- * No corresponding data msg would be published and no attempt would be made to retrieve the
- * data msg
- */
-const std::string forwarderPrefix = "/d0n0t18ak/t0ps8cr8t";
-
-class State;
-typedef boost::shared_ptr<State> StatePtr;
-typedef boost::shared_ptr<State> StateConstPtr;
-
-/**
- * \ingroup sync
- * @brief Container for state leaves and definition of the abstract interface to work with State objects
- */
-class State
-{
-public:
-  virtual ~State () { };
-
-  /**
-   * @brief Add or update leaf to the state tree
-   *
-   * @param info name of the leaf
-   * @param seq  sequence number of the leaf
-   */
-  virtual boost::tuple<bool/*inserted*/, bool/*updated*/, SeqNo/*oldSeqNo*/>
-  update (NameInfoConstPtr info, const SeqNo &seq) = 0;
-
-  /**
-   * @brief Remove leaf from the state tree
-   * @param info name of the leaf
-   */
-  virtual bool
-  remove (NameInfoConstPtr info) = 0;
-
-  /**
-   * @brief Get state leaves
-   */
-  const LeafContainer &
-  getLeaves () const
-  { return m_leaves; }
-
-protected:
-  LeafContainer m_leaves;
-};
-
-
-/**
- * @brief Formats a protobuf SyncStateMsg msg
- * @param oss output SyncStateMsg msg
- * @param state state
- * @returns output SyncStateMsg msg
- */
-SyncStateMsg &
-operator << (SyncStateMsg &ossm, const State &state);
-
-
-/**
- * @brief Parse a protobuf SyncStateMsg msg
- * @param iss input SyncStateMsg msg
- * @param state state
- * @returns SyncStateMsg msg
- */
-SyncStateMsg &
-operator >> (SyncStateMsg &issm, State &state);
-
-namespace Error {
-/**
- * @brief Will be thrown when data cannot be properly decoded to SyncStateMsg
- */
-struct SyncStateMsgDecodingFailure : virtual boost::exception, virtual std::exception { };
-}
-
-} // Sync
-
-#endif // SYNC_STATE_H
diff --git a/src/sync-state.proto b/src/sync-state.proto
deleted file mode 100644
index 7cc6b18..0000000
--- a/src/sync-state.proto
+++ /dev/null
@@ -1,24 +0,0 @@
-package Sync;
-
-message SyncState
-{
-  required string name = 1;
-  enum ActionType
-  {
-    UPDATE = 0;
-    DELETE = 1;
-    OTHER = 2;
-  }
-  required ActionType type = 2;
-  message SeqNo
-  {
-    required uint64 seq = 1;
-    required uint64 session = 2;
-  }
-  optional SeqNo seqno = 3;
-}
-
-message SyncStateMsg
-{
-  repeated SyncState ss = 1;
-}
diff --git a/src/tlv.hpp b/src/tlv.hpp
new file mode 100644
index 0000000..6b5f48a
--- /dev/null
+++ b/src/tlv.hpp
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
+/*
+ * Copyright (c) 2012-2014 University of California, Los Angeles
+ *
+ * This file is part of ChronoSync, synchronization library for distributed realtime
+ * applications for NDN.
+ *
+ * ChronoSync is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ChronoSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ChronoSync, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Yingdi Yu <yingdi@cs.ucla.edu>
+ */
+
+#ifndef CHRONOSYNC_TLV_HPP
+#define CHRONOSYNC_TLV_HPP
+
+namespace chronosync {
+namespace tlv {
+
+/**
+ * @brief Type value of sync reply related TLVs
+ * @sa docs/design.rst
+ */
+enum {
+  SyncReply   = 128, // 0x80
+  StateLeaf   = 129, // 0x81
+  SeqNo       = 130  // 0x82
+};
+
+} // namespace tlv
+} // namespace chronosync
+
+#endif // CHRONOSYNC_TLV_HPP
diff --git a/tests/unit-tests/test-state.cc.outdated b/tests/unit-tests/test-state.cc.outdated
deleted file mode 100644
index 433f8d9..0000000
--- a/tests/unit-tests/test-state.cc.outdated
+++ /dev/null
@@ -1,291 +0,0 @@
-/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
-/*
- * Copyright (c) 2012-2014 University of California, Los Angeles
- *
- * This file is part of ChronoSync, synchronization library for distributed realtime
- * applications for NDN.
- *
- * ChronoSync is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * ChronoSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * ChronoSync, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#define BOOST_TEST_DYN_LINK 1
-#define BOOST_TEST_NO_MAIN 1
-// #define BOOST_TEST_MODULE StateTests
-#include <boost/test/unit_test.hpp>
-#include <boost/test/output_test_stream.hpp>
-using boost::test_tools::output_test_stream;
-
-#include <boost/make_shared.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-
-#include "sync-std-name-info.h"
-#include "sync-full-state.h"
-#include "sync-diff-state.h"
-
-using namespace Sync;
-using namespace std;
-using namespace boost;
-
-BOOST_AUTO_TEST_SUITE(StateTests)
-
-BOOST_AUTO_TEST_CASE (FullStateTest)
-{
-  BOOST_CHECK_NO_THROW (FullState ());
-  FullState state;
-  BOOST_CHECK_EQUAL (state.getLeaves ().size (), 0);
-
-  output_test_stream output;
-  output << state.getTimeFromLastUpdate ();
-  BOOST_CHECK (output.is_equal ("not-a-date-time", true));
-
-  NameInfoConstPtr name = StdNameInfo::FindOrCreate ("/test/name");
-  BOOST_CHECK_NO_THROW (state.update (name, SeqNo (12)));
-  BOOST_CHECK_NO_THROW (state.update (name, SeqNo (12)));
-  BOOST_CHECK_NO_THROW (state.update (name, SeqNo (12)));
-  BOOST_CHECK_EQUAL (state.getLeaves ().size (), 1);
-  BOOST_CHECK_EQUAL ((*state.getLeaves ().begin ())->getSeq ().getSeq (), 12);
-
-  BOOST_CHECK_NO_THROW (state.update (name, SeqNo (13)));
-  BOOST_CHECK_EQUAL ((*state.getLeaves ().begin ())->getSeq ().getSeq (), 13);
-
-  BOOST_CHECK_NO_THROW (state.remove (name));
-  BOOST_CHECK_EQUAL (state.getLeaves ().size (), 0);
-
-  BOOST_CHECK_EQUAL (state.getTimeFromLastUpdate ().total_milliseconds (), 0);
-}
-
-BOOST_AUTO_TEST_CASE (DiffStateTest)
-{
-  BOOST_CHECK_NO_THROW (DiffState ());
-  DiffState state;
-  BOOST_CHECK_EQUAL (state.getLeaves ().size (), 0);
-
-  NameInfoConstPtr name = StdNameInfo::FindOrCreate ("/test/name");
-  BOOST_CHECK_NO_THROW (state.update (name, SeqNo (12)));
-  BOOST_CHECK_NO_THROW (state.update (name, SeqNo (12)));
-  BOOST_CHECK_NO_THROW (state.update (name, SeqNo (12)));
-  BOOST_CHECK_EQUAL (state.getLeaves ().size (), 1);
-  BOOST_CHECK_EQUAL ((*state.getLeaves ().begin ())->getSeq ().getSeq (), 12);
-
-  BOOST_CHECK_NO_THROW (state.update (name, SeqNo (13)));
-  BOOST_CHECK_EQUAL ((*state.getLeaves ().begin ())->getSeq ().getSeq (), 13);
-
-  BOOST_CHECK_NO_THROW (state.remove (name));
-  BOOST_CHECK_EQUAL (state.getLeaves ().size (), 1);
-  BOOST_CHECK_EQUAL ((*state.getLeaves ().begin ())->getSeq ().getSeq (), 0);
-}
-
-BOOST_AUTO_TEST_CASE (FullStateDigestTest)
-{
-  FullState state;
-  BOOST_CHECK_EQUAL (state.getLeaves ().size (), 0);
-
-  NameInfoConstPtr name3 = StdNameInfo::FindOrCreate ("3");
-  NameInfoConstPtr name2 = StdNameInfo::FindOrCreate ("2");
-  NameInfoConstPtr name1 = StdNameInfo::FindOrCreate ("1");
-
-  state.update (name1, SeqNo (10));
-  DigestConstPtr digest1 = state.getDigest ();
-
-  state.update (name2, SeqNo (12));
-  DigestConstPtr digest2 = state.getDigest ();
-
-  BOOST_CHECK (digest1.get () != digest2.get ());
-  BOOST_CHECK (!digest1->empty ());
-  BOOST_CHECK (!digest2->empty ());
-
-  state.update (name3, SeqNo (8));
-  DigestConstPtr digest3 = state.getDigest ();
-
-  BOOST_CHECK (digest1.get () != digest2.get ());
-  BOOST_CHECK (digest2.get () != digest3.get ());
-  BOOST_CHECK (digest1.get () != digest3.get ());
-
-  BOOST_CHECK (*digest1 != *digest2);
-  BOOST_CHECK (*digest2 != *digest3);
-  BOOST_CHECK (*digest1 != *digest3);
-
-  // removing elements. Digest should get reverted to digest1
-  state.remove (name2);
-  state.remove (name3);
-  DigestConstPtr digest4 = state.getDigest ();
-  BOOST_CHECK (*digest1 == *digest4);
-
-  name2.reset (); // force destructor
-  name3.reset (); // force destructor
-  name3 = StdNameInfo::FindOrCreate ("3"); // this will enforce different (larger) hashing ID of name
-  name2 = StdNameInfo::FindOrCreate ("2"); // this will enforce different (larger) hashing ID of name
-
-  // adding in different order
-  state.update (name3, SeqNo (8));
-  state.update (name2, SeqNo (12));
-  DigestConstPtr digest5 = state.getDigest ();
-  BOOST_CHECK (*digest5 == *digest3);
-}
-
-BOOST_AUTO_TEST_CASE (FullStateXml)
-{
-  FullState state;
-
-  NameInfoConstPtr name3 = StdNameInfo::FindOrCreate ("3");
-  NameInfoConstPtr name2 = StdNameInfo::FindOrCreate ("2");
-  NameInfoConstPtr name1 = StdNameInfo::FindOrCreate ("1");
-
-  state.update (name1, SeqNo (10));
-  state.update (name2, SeqNo (12));
-  state.update (name3, SeqNo (8));
-
-  string xml1 = "<state>"
-    "<item><name>1</name><seq><session>0</session><seqno>10</seqno></seq></item>"
-    "<item><name>2</name><seq><session>0</session><seqno>12</seqno></seq></item>"
-    "<item><name>3</name><seq><session>0</session><seqno>8</seqno></seq></item>"
-    "</state>";
-  {
-  ostringstream os;
-  os << state;
-  string s = os.str ();
-  // cout << s << endl;
-  erase_all (s, "\n");
-  BOOST_CHECK_EQUAL (s, xml1);
-  }
-
-  state.remove (name2);
-  string xml2 = "<state>"
-    "<item><name>1</name><seq><session>0</session><seqno>10</seqno></seq></item>"
-    "<item><name>3</name><seq><session>0</session><seqno>8</seqno></seq></item>"
-    "</state>";
-  {
-  ostringstream os;
-  os << state;
-  string s = os.str ();
-  erase_all (s, "\n");
-  BOOST_CHECK_EQUAL (s, xml2);
-  }
-
-  FullState state2;
-  istringstream xml1_is (xml1);
-  BOOST_CHECK_NO_THROW (xml1_is >> state2);
-  {
-  ostringstream os;
-  os << state2;
-  string xml1_test = os.str ();
-  erase_all (xml1_test, "\n");
-  BOOST_CHECK_EQUAL (xml1_test, xml1);
-  }
-
-  istringstream xml2_is ("<state><item action=\"remove\"><name>2</name></item></state>");
-  BOOST_CHECK_NO_THROW (xml2_is >> state2);
-
-  {
-  ostringstream os;
-  os << state2;
-  string xml2_test = os.str ();
-  erase_all (xml2_test, "\n");
-  BOOST_CHECK_EQUAL (xml2_test, xml2);
-  }
-}
-
-BOOST_AUTO_TEST_CASE (DiffStateXml)
-{
-  DiffState state;
-
-  NameInfoConstPtr name3 = StdNameInfo::FindOrCreate ("3");
-  NameInfoConstPtr name2 = StdNameInfo::FindOrCreate ("2");
-  NameInfoConstPtr name1 = StdNameInfo::FindOrCreate ("1");
-
-  state.update (name1, SeqNo (10));
-  state.update (name2, SeqNo (12));
-  state.update (name3, SeqNo (8));
-
-  string xml1 = "<state>"
-    "<item action=\"update\"><name>1</name><seq><session>0</session><seqno>10</seqno></seq></item>"
-    "<item action=\"update\"><name>2</name><seq><session>0</session><seqno>12</seqno></seq></item>"
-    "<item action=\"update\"><name>3</name><seq><session>0</session><seqno>8</seqno></seq></item>"
-    "</state>";
-  {
-  ostringstream os;
-  os << state;
-  string xml1_test = os.str ();
-  erase_all (xml1_test, "\n");
-  BOOST_CHECK_EQUAL (xml1_test, xml1);
-  }
-
-  state.remove (name2);
-  string xml2 = "<state>"
-    "<item action=\"update\"><name>1</name><seq><session>0</session><seqno>10</seqno></seq></item>"
-    "<item action=\"remove\"><name>2</name></item>"
-    "<item action=\"update\"><name>3</name><seq><session>0</session><seqno>8</seqno></seq></item>"
-    "</state>";
-  {
-  ostringstream os;
-  os << state;
-  string xml2_test = os.str ();
-  erase_all (xml2_test, "\n");
-  BOOST_CHECK_EQUAL (xml2_test, xml2);
-  }
-
-  ////////////  ////////////  ////////////  ////////////  ////////////  ////////////
-
-  DiffState state2;
-  istringstream xml1_is (xml1);
-  BOOST_CHECK_NO_THROW (xml1_is >> state2);
-
-  {
-  ostringstream os;
-  os << state2;
-  string xml1_test = os.str ();
-  erase_all (xml1_test, "\n");
-  BOOST_CHECK_EQUAL (xml1_test, xml1);
-  }
-
-  istringstream xml2_is ("<state><item action=\"remove\"><name>2</name></item></state>");
-  BOOST_CHECK_NO_THROW (xml2_is >> state2);
-
-  {
-  ostringstream os;
-  os << state2;
-  string xml2_test = os.str ();
-  erase_all (xml2_test, "\n");
-  BOOST_CHECK_EQUAL (xml2_test, xml2);
-  }
-
-}
-
-BOOST_AUTO_TEST_CASE (DiffStateDiffTest)
-{
-  DiffStatePtr root = make_shared<DiffState> ();
-
-  DiffStatePtr head = make_shared<DiffState> ();
-  root->setNext (head);
-
-  head->update (StdNameInfo::FindOrCreate ("3"), SeqNo (1));
-  head->remove (StdNameInfo::FindOrCreate ("1"));
-
-  DiffStatePtr tail = make_shared<DiffState> ();
-  head->setNext (tail);
-
-  tail->update (StdNameInfo::FindOrCreate ("3"), SeqNo (2));
-
-  {
-  ostringstream os;
-  os << *root->diff ();
-  string diffState = os.str ();
-  erase_all (diffState, "\n");
-  BOOST_CHECK_EQUAL (diffState,
-                     "<state>"
-                     "<item action=\"remove\"><name>1</name></item>"
-                     "<item action=\"update\"><name>3</name><seq><session>0</session><seqno>2</seqno></seq></item>"
-                     "</state>");
-  }
-}
-
-BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/unit-tests/test-state.cpp b/tests/unit-tests/test-state.cpp
new file mode 100644
index 0000000..ed88373
--- /dev/null
+++ b/tests/unit-tests/test-state.cpp
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
+/*
+ * Copyright (c) 2012-2014 University of California, Los Angeles
+ *
+ * This file is part of ChronoSync, synchronization library for distributed realtime
+ * applications for NDN.
+ *
+ * ChronoSync is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ChronoSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ChronoSync, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "state.hpp"
+#include "boost-test.hpp"
+
+namespace chronosync {
+namespace test {
+
+using boost::tuple;
+
+BOOST_AUTO_TEST_SUITE(StateTests)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  BOOST_CHECK_NO_THROW(State());
+  State state;
+  BOOST_CHECK_EQUAL(state.getLeaves().size(), 0);
+
+  Name info("/test/name");
+  info.appendNumber(0);
+
+  BOOST_CHECK_NO_THROW(state.update(info, 12));
+
+  BOOST_CHECK_NO_THROW(state.reset());
+  BOOST_CHECK_EQUAL(state.getLeaves().size(), 0);
+
+  tuple<bool, bool, SeqNo> result;
+  result = state.update(info, 12);
+  BOOST_CHECK_EQUAL(result.get<0>(), true);
+  BOOST_CHECK_EQUAL(result.get<1>(), false);
+  BOOST_CHECK_EQUAL(result.get<2>(), 0);
+
+  BOOST_CHECK_NO_THROW(state.update(info, 12));
+  result = state.update(info, 12);
+  BOOST_CHECK_EQUAL(result.get<0>(), false);
+  BOOST_CHECK_EQUAL(result.get<1>(), false);
+  BOOST_CHECK_EQUAL(result.get<2>(), 0);
+
+  BOOST_CHECK_NO_THROW(state.update(info, 11));
+  result = state.update(info, 11);
+  BOOST_CHECK_EQUAL(result.get<0>(), false);
+  BOOST_CHECK_EQUAL(result.get<1>(), false);
+  BOOST_CHECK_EQUAL(result.get<2>(), 0);
+
+  BOOST_CHECK_EQUAL(state.getLeaves().size(), 1);
+  BOOST_CHECK_EQUAL((*state.getLeaves().begin())->getSeq(), 12);
+
+  BOOST_CHECK_NO_THROW(state.update(info, 13));
+  BOOST_CHECK_EQUAL(state.getLeaves().size(), 1);
+  BOOST_CHECK_EQUAL((*state.getLeaves().begin())->getSeq(), 13);
+
+  result = state.update(info, 14);
+  BOOST_CHECK_EQUAL(result.get<0>(), false);
+  BOOST_CHECK_EQUAL(result.get<1>(), true);
+  BOOST_CHECK_EQUAL(result.get<2>(), 13);
+
+  BOOST_CHECK_EQUAL(state.getLeaves().size(), 1);
+  BOOST_CHECK_EQUAL((*state.getLeaves().begin())->getSeq(), 14);
+
+  Name info2("/test/name");
+  info2.appendNumber(1);
+  BOOST_CHECK_NO_THROW(state.update(info2, 3));
+  BOOST_CHECK_EQUAL(state.getLeaves().size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(StateDigest)
+{
+  State state;
+  BOOST_CHECK_EQUAL(state.getLeaves().size(), 0);
+
+  Name info1("/test/name");
+  info1.appendNumber(0);
+
+  Name info2("/test/name");
+  info2.appendNumber(1);
+
+  Name info3("/test/mane");
+  info3.appendNumber(0);
+
+  state.update(info1, 10);
+  ndn::ConstBufferPtr digest1 = state.getRootDigest();
+
+  state.update(info2, 12);
+  ndn::ConstBufferPtr digest2 = state.getRootDigest();
+
+  state.update(info3, 8);
+  ndn::ConstBufferPtr digest3 = state.getRootDigest();
+
+  BOOST_CHECK(*digest1 != *digest2);
+  BOOST_CHECK(*digest2 != *digest3);
+  BOOST_CHECK(*digest1 != *digest3);
+
+  state.reset();
+
+  state.update(info1, 10);
+  ndn::ConstBufferPtr digest4 = state.getRootDigest();
+
+  state.update(info3, 8);
+  ndn::ConstBufferPtr digest5 = state.getRootDigest();
+
+  state.update(info2, 12);
+  ndn::ConstBufferPtr digest6 = state.getRootDigest();
+
+  BOOST_CHECK(*digest4 == *digest1);
+  BOOST_CHECK(*digest5 != *digest2);
+  BOOST_CHECK(*digest6 == *digest3);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeEncode)
+{
+  const uint8_t wire[] = {
+    0x80, 0x2c, // SyncReply
+      0x81, 0x14, // StateLeaf
+        0x07, 0x0f, // Name: /test/name/[0]
+          0x08, 0x04,
+            0x74, 0x65, 0x73, 0x74,
+          0x08, 0x04,
+            0x6e, 0x61, 0x6d, 0x65,
+          0x08, 0x01,
+            0x00,
+        0x82, 0x1, // SeqNo: 14
+          0x0e,
+      0x81, 0x14, // StateLeaf
+        0x07, 0x0f, // Name: /test/name/[1]
+          0x08, 0x04,
+            0x74, 0x65, 0x73, 0x74,
+          0x08, 0x04,
+            0x6e, 0x61, 0x6d, 0x65,
+          0x08, 0x01,
+            0x01,
+        0x82, 0x1, // SeqNo: 4
+          0x04
+  };
+
+  Block block(wire, sizeof(wire));
+  State state;
+  BOOST_REQUIRE_NO_THROW(state.wireDecode(block));
+
+  BOOST_CHECK_EQUAL(state.getLeaves().size(), 2);
+  LeafContainer::index<ordered>::type::iterator it = state.getLeaves().get<ordered>().begin();
+  BOOST_CHECK_EQUAL((*it)->getSeq(), 14);
+  it++;
+  BOOST_CHECK_EQUAL((*it)->getSeq(), 4);
+
+
+  State state2;
+
+  Name info1("/test/name");
+  info1.appendNumber(0);
+  state2.update(info1, 14);
+
+  Name info2("/test/name");
+  info2.appendNumber(1);
+  state2.update(info2, 4);
+
+  BOOST_REQUIRE_NO_THROW(state2.wireEncode());
+  Block block2 = state2.wireEncode();
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(block.wire(),
+                                block.wire() + block.size(),
+                                block2.wire(),
+                                block2.wire() + block2.size());
+
+  BOOST_CHECK(*state.getRootDigest() == *state2.getRootDigest());
+}
+
+BOOST_AUTO_TEST_CASE(Combine)
+{
+  State state1;
+  State state2;
+
+  Name info1("/test/name");
+  info1.appendNumber(0);
+
+  Name info2("/test/name");
+  info2.appendNumber(1);
+
+  Name info3("/test/name");
+  info3.appendNumber(2);
+
+  state1.update(info1, 4);
+  state1.update(info2, 14);
+
+  state2.update(info2, 15);
+  state2.update(info3, 25);
+
+  BOOST_CHECK_EQUAL(state1.getLeaves().size(), 2);
+  BOOST_CHECK_EQUAL(state2.getLeaves().size(), 2);
+
+  BOOST_REQUIRE_NO_THROW(state2 += state1);
+
+  BOOST_CHECK_EQUAL(state1.getLeaves().size(), 2);
+  BOOST_CHECK_EQUAL(state2.getLeaves().size(), 3);
+
+  LeafContainer::index<ordered>::type::iterator it = state2.getLeaves().get<ordered>().begin();
+  BOOST_CHECK_EQUAL((*it)->getSeq(), 4);
+  it++;
+  BOOST_CHECK_EQUAL((*it)->getSeq(), 15);
+  it++;
+  BOOST_CHECK_EQUAL((*it)->getSeq(), 25);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace test
+} // namespace chronosync