mgmt: Dispatcher

Change-Id: I92b3dc9daae75abac9d791632b6a0bec111b4573
refs: #2107
diff --git a/AUTHORS.md b/AUTHORS.md
index 5cd6e45..e9bfc0a 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -35,3 +35,4 @@
 * Eric Newberry         <http://ericnewberry.com>
 * João Pereira          <http://website.jpereira.co.uk>
 * Mickey Sweatt         <https://www.linkedin.com/in/michaelsweatt>
+* Yanbiao Li            <https://www.linkedin.com/pub/yanbiao-li/24/7a1/4ba>
diff --git a/src/management/nfd-control-command.hpp b/src/management/nfd-control-command.hpp
index a7b8bc6..9d3cbb3 100644
--- a/src/management/nfd-control-command.hpp
+++ b/src/management/nfd-control-command.hpp
@@ -307,7 +307,6 @@
   validateResponse(const ControlParameters& parameters) const;
 };
 
-
 } // namespace nfd
 } // namespace ndn
 
diff --git a/src/management/nfd-control-parameters.cpp b/src/management/nfd-control-parameters.cpp
index f8ce363..d777843 100644
--- a/src/management/nfd-control-parameters.cpp
+++ b/src/management/nfd-control-parameters.cpp
@@ -95,7 +95,7 @@
 template size_t
 ControlParameters::wireEncode<encoding::EstimatorTag>(EncodingImpl<encoding::EstimatorTag>&) const;
 
-const Block&
+Block
 ControlParameters::wireEncode() const
 {
   if (m_wire.hasWire())
diff --git a/src/management/nfd-control-parameters.hpp b/src/management/nfd-control-parameters.hpp
index 29434fc..3a099cc 100644
--- a/src/management/nfd-control-parameters.hpp
+++ b/src/management/nfd-control-parameters.hpp
@@ -25,6 +25,7 @@
 #include "../encoding/nfd-constants.hpp"
 #include "../name.hpp"
 #include "../util/time.hpp"
+#include "../mgmt/control-parameters.hpp"
 
 namespace ndn {
 namespace nfd {
@@ -71,7 +72,7 @@
  * @sa http://redmine.named-data.net/projects/nfd/wiki/ControlCommand#ControlParameters
  * @detail This type is copyable because it's an abstraction of a TLV type.
  */
-class ControlParameters
+class ControlParameters : public ndn::mgmt::ControlParameters
 {
 public:
   class Error : public tlv::Error
@@ -93,11 +94,11 @@
   size_t
   wireEncode(EncodingImpl<TAG>& encoder) const;
 
-  const Block&
-  wireEncode() const;
+  virtual Block
+  wireEncode() const NDN_CXX_DECL_FINAL;
 
-  void
-  wireDecode(const Block& wire);
+  virtual void
+  wireDecode(const Block& wire) NDN_CXX_DECL_FINAL;
 
 public: // getters & setters
 
diff --git a/src/management/nfd-control-response.cpp b/src/management/nfd-control-response.cpp
deleted file mode 100644
index 7056162..0000000
--- a/src/management/nfd-control-response.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2013-2015 Regents of the University of California.
- *
- * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
- *
- * ndn-cxx library is free software: you can redistribute it and/or modify it under the
- * terms of the GNU Lesser General Public License as published by the Free Software
- * Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * ndn-cxx library 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 Lesser General Public License for more details.
- *
- * You should have received copies of the GNU General Public License and GNU Lesser
- * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
- */
-
-#include "nfd-control-response.hpp"
-#include "encoding/tlv-nfd.hpp"
-#include "encoding/block-helpers.hpp"
-
-namespace ndn {
-namespace nfd {
-
-//BOOST_CONCEPT_ASSERT((boost::EqualityComparable<ControlResponse>));
-BOOST_CONCEPT_ASSERT((WireEncodable<ControlResponse>));
-BOOST_CONCEPT_ASSERT((WireDecodable<ControlResponse>));
-static_assert(std::is_base_of<tlv::Error, ControlResponse::Error>::value,
-              "ControlResponse::Error must inherit from tlv::Error");
-
-ControlResponse::ControlResponse()
-  : m_code(200)
-{
-}
-
-ControlResponse::ControlResponse(uint32_t code, const std::string& text)
-  : m_code(code)
-  , m_text(text)
-{
-}
-
-ControlResponse::ControlResponse(const Block& block)
-{
-  wireDecode(block);
-}
-
-const Block&
-ControlResponse::wireEncode() const
-{
-  if (m_wire.hasWire())
-    return m_wire;
-
-  m_wire = Block(tlv::nfd::ControlResponse);
-  m_wire.push_back(makeNonNegativeIntegerBlock(tlv::nfd::StatusCode, m_code));
-
-  m_wire.push_back(makeBinaryBlock(tlv::nfd::StatusText, m_text.c_str(), m_text.size()));
-
-  if (m_body.hasWire()) {
-    m_wire.push_back(m_body);
-  }
-
-  m_wire.encode();
-  return m_wire;
-}
-
-void
-ControlResponse::wireDecode(const Block& wire)
-{
-  m_wire = wire;
-  m_wire.parse();
-
-  if (m_wire.type() != tlv::nfd::ControlResponse)
-    BOOST_THROW_EXCEPTION(Error("Requested decoding of ControlResponse, but Block is of different "
-                                "type"));
-
-  Block::element_const_iterator val = m_wire.elements_begin();
-  if (val == m_wire.elements_end() ||
-      val->type() != tlv::nfd::StatusCode)
-    {
-      BOOST_THROW_EXCEPTION(Error("Incorrect ControlResponse format (StatusCode missing or not the "
-                                  "first item)"));
-    }
-
-  m_code = readNonNegativeInteger(*val);
-  ++val;
-
-  if (val == m_wire.elements_end() ||
-      val->type() != tlv::nfd::StatusText)
-    {
-      BOOST_THROW_EXCEPTION(Error("Incorrect ControlResponse format (StatusText missing or not the "
-                                  "second item)"));
-    }
-  m_text.assign(reinterpret_cast<const char*>(val->value()), val->value_size());
-  ++val;
-
-  if (val != m_wire.elements_end())
-    m_body = *val;
-  else
-    m_body = Block();
-}
-
-std::ostream&
-operator<<(std::ostream& os, const ControlResponse& response)
-{
-  os << response.getCode() << " " << response.getText();
-  return os;
-}
-
-} // namespace nfd
-} // namespace ndn
diff --git a/src/management/nfd-control-response.hpp b/src/management/nfd-control-response.hpp
index 42ffd3d..83ba11d 100644
--- a/src/management/nfd-control-response.hpp
+++ b/src/management/nfd-control-response.hpp
@@ -22,112 +22,12 @@
 #ifndef NDN_MANAGEMENT_CONTROL_RESPONSE_HPP
 #define NDN_MANAGEMENT_CONTROL_RESPONSE_HPP
 
-#include "../encoding/block.hpp"
+#include "../mgmt/dispatcher.hpp"
 
 namespace ndn {
 namespace nfd {
 
-/**
- * @ingroup management
- * @brief Class defining abstraction of ControlResponse for NFD Control Protocol
- *
- * @see http://redmine.named-data.net/projects/nfd/wiki/ControlCommand#Response-format
- * @detail This type is copyable because it's an abstraction of a TLV type.
- */
-class ControlResponse
-{
-public:
-  class Error : public tlv::Error
-  {
-  public:
-    explicit
-    Error(const std::string& what)
-      : tlv::Error(what)
-    {
-    }
-  };
-
-  ControlResponse();
-
-  ControlResponse(uint32_t code, const std::string& text);
-
-  explicit
-  ControlResponse(const Block& block);
-
-  uint32_t
-  getCode() const;
-
-  void
-  setCode(uint32_t code);
-
-  const std::string&
-  getText() const;
-
-  void
-  setText(const std::string& text);
-
-  const Block&
-  getBody() const;
-
-  void
-  setBody(const Block& body);
-
-  const Block&
-  wireEncode() const;
-
-  void
-  wireDecode(const Block& block);
-
-protected:
-  uint32_t m_code;
-  std::string m_text;
-  Block m_body;
-
-  mutable Block m_wire;
-};
-
-inline uint32_t
-ControlResponse::getCode() const
-{
-  return m_code;
-}
-
-inline void
-ControlResponse::setCode(uint32_t code)
-{
-  m_code = code;
-  m_wire.reset();
-}
-
-inline const std::string&
-ControlResponse::getText() const
-{
-  return m_text;
-}
-
-inline void
-ControlResponse::setText(const std::string& text)
-{
-  m_text = text;
-  m_wire.reset();
-}
-
-inline const Block&
-ControlResponse::getBody() const
-{
-  return m_body;
-}
-
-inline void
-ControlResponse::setBody(const Block& body)
-{
-  m_body = body;
-  m_body.encode(); // will do nothing if already encoded
-  m_wire.reset();
-}
-
-std::ostream&
-operator<<(std::ostream& os, const ControlResponse& response);
+typedef ndn::mgmt::ControlResponse ControlResponse;
 
 } // namespace nfd
 } // namespace ndn
diff --git a/src/mgmt/control-parameters.hpp b/src/mgmt/control-parameters.hpp
new file mode 100644
index 0000000..3a17091
--- /dev/null
+++ b/src/mgmt/control-parameters.hpp
@@ -0,0 +1,49 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NDN_MGMT_CONTROL_PARAMETERS_HPP
+#define NDN_MGMT_CONTROL_PARAMETERS_HPP
+
+#include "../encoding/block.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+/** \brief base class for a struct that contains ControlCommand parameters
+ */
+class ControlParameters
+{
+public:
+  virtual void
+  wireDecode(const Block& wire) = 0;
+
+  virtual Block
+  wireEncode() const = 0;
+};
+
+} // namespace mgmt
+} // namespace ndn
+
+#endif // NDN_MGMT_CONTROL_PARAMETERS_HPP
diff --git a/src/mgmt/control-response.cpp b/src/mgmt/control-response.cpp
new file mode 100644
index 0000000..9c66f2a
--- /dev/null
+++ b/src/mgmt/control-response.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "control-response.hpp"
+#include "../encoding/block-helpers.hpp"
+#include "../encoding/tlv-nfd.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+// BOOST_CONCEPT_ASSERT((boost::EqualityComparable<ControlResponse>));
+BOOST_CONCEPT_ASSERT((WireEncodable<ControlResponse>));
+BOOST_CONCEPT_ASSERT((WireDecodable<ControlResponse>));
+static_assert(std::is_base_of<tlv::Error, ControlResponse::Error>::value,
+              "ControlResponse::Error must inherit from tlv::Error");
+
+ControlResponse::ControlResponse()
+  : m_code(200)
+{
+}
+
+ControlResponse::ControlResponse(uint32_t code, const std::string& text)
+  : m_code(code)
+  , m_text(text)
+{
+}
+
+ControlResponse::ControlResponse(const Block& block)
+{
+  wireDecode(block);
+}
+
+const Block&
+ControlResponse::wireEncode() const
+{
+  if (m_wire.hasWire())
+    return m_wire;
+
+  m_wire = Block(tlv::nfd::ControlResponse);
+  m_wire.push_back(nonNegativeIntegerBlock(tlv::nfd::StatusCode, m_code));
+
+  m_wire.push_back(dataBlock(tlv::nfd::StatusText, m_text.c_str(), m_text.size()));
+
+  if (m_body.hasWire()) {
+    m_wire.push_back(m_body);
+  }
+
+  m_wire.encode();
+  return m_wire;
+}
+
+void
+ControlResponse::wireDecode(const Block& wire)
+{
+  m_wire = wire;
+  m_wire.parse();
+
+  if (m_wire.type() != tlv::nfd::ControlResponse)
+    throw Error("Requested decoding of ControlResponse, but Block is of different type");
+
+  Block::element_const_iterator val = m_wire.elements_begin();
+  if (val == m_wire.elements_end() || val->type() != tlv::nfd::StatusCode) {
+    throw Error("Incorrect ControlResponse format (StatusCode missing or not the first item)");
+  }
+
+  m_code = readNonNegativeInteger(*val);
+  ++val;
+
+  if (val == m_wire.elements_end() || val->type() != tlv::nfd::StatusText) {
+    throw Error("Incorrect ControlResponse format (StatusText missing or not the second item)");
+  }
+  m_text.assign(reinterpret_cast<const char*>(val->value()), val->value_size());
+  ++val;
+
+  if (val != m_wire.elements_end())
+    m_body = *val;
+  else
+    m_body = Block();
+}
+
+std::ostream&
+operator<<(std::ostream& os, const ControlResponse& response)
+{
+  os << response.getCode() << " " << response.getText();
+  return os;
+}
+
+} // namespace mgmt
+} // namespace ndn
diff --git a/src/mgmt/control-response.hpp b/src/mgmt/control-response.hpp
new file mode 100644
index 0000000..a107940
--- /dev/null
+++ b/src/mgmt/control-response.hpp
@@ -0,0 +1,137 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NDN_MGMT_CONTROL_RESPONSE_HPP
+#define NDN_MGMT_CONTROL_RESPONSE_HPP
+
+#include "../encoding/block.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+/** \brief ControlCommand response
+ */
+class ControlResponse
+{
+public:
+  class Error : public tlv::Error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : tlv::Error(what)
+    {
+    }
+  };
+
+  ControlResponse();
+
+  ControlResponse(uint32_t code, const std::string& text);
+
+  explicit
+  ControlResponse(const Block& block);
+
+  uint32_t
+  getCode() const;
+
+  ControlResponse&
+  setCode(uint32_t code);
+
+  const std::string&
+  getText() const;
+
+  ControlResponse&
+  setText(const std::string& text);
+
+  const Block&
+  getBody() const;
+
+  ControlResponse&
+  setBody(const Block& body);
+
+  const Block&
+  wireEncode() const;
+
+  void
+  wireDecode(const Block& block);
+
+protected:
+  uint32_t m_code;
+  std::string m_text;
+  Block m_body;
+
+  mutable Block m_wire;
+};
+
+inline uint32_t
+ControlResponse::getCode() const
+{
+  return m_code;
+}
+
+inline ControlResponse&
+ControlResponse::setCode(uint32_t code)
+{
+  m_code = code;
+  m_wire.reset();
+  return *this;
+}
+
+inline const std::string&
+ControlResponse::getText() const
+{
+  return m_text;
+}
+
+inline ControlResponse&
+ControlResponse::setText(const std::string& text)
+{
+  m_text = text;
+  m_wire.reset();
+  return *this;
+}
+
+inline const Block&
+ControlResponse::getBody() const
+{
+  return m_body;
+}
+
+inline ControlResponse&
+ControlResponse::setBody(const Block& body)
+{
+  m_body = body;
+  m_body.encode(); // will do nothing if already encoded
+  m_wire.reset();
+  return *this;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const ControlResponse& response);
+
+} // namespace mgmt
+} // namespace ndn
+
+#endif // NDN_MGMT_CONTRO_RESPONSE_HPP
diff --git a/src/mgmt/dispatcher.cpp b/src/mgmt/dispatcher.cpp
new file mode 100644
index 0000000..b43334b
--- /dev/null
+++ b/src/mgmt/dispatcher.cpp
@@ -0,0 +1,310 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dispatcher.hpp"
+
+#include <algorithm>
+
+// #define NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING
+
+namespace ndn {
+namespace mgmt {
+
+Authorization
+makeAcceptAllAuthorization()
+{
+  return [] (const Name& prefix,
+             const Interest& interest,
+             const ControlParameters* params,
+             AcceptContinuation accept,
+             RejectContinuation reject) {
+    accept("");
+  };
+}
+
+Dispatcher::Dispatcher(Face& face, security::KeyChain& keyChain,
+                       const security::SigningInfo& signingInfo)
+  : m_face(face)
+  , m_keyChain(keyChain)
+  , m_signingInfo(signingInfo)
+{
+}
+
+Dispatcher::~Dispatcher()
+{
+  std::vector<Name> topPrefixNames;
+
+  std::transform(m_topLevelPrefixes.begin(),
+                 m_topLevelPrefixes.end(),
+                 std::back_inserter(topPrefixNames),
+                 [] (const std::unordered_map<Name, TopPrefixEntry>::value_type& entry) {
+                   return entry.second.topPrefix;
+                 });
+
+  for (auto&& name : topPrefixNames) {
+    removeTopPrefix(name);
+  }
+}
+
+void
+Dispatcher::addTopPrefix(const Name& prefix,
+                         bool wantRegister,
+                         const security::SigningInfo& signingInfo)
+{
+  bool hasOverlap = std::any_of(m_topLevelPrefixes.begin(),
+                                m_topLevelPrefixes.end(),
+                                [&] (const std::unordered_map<Name, TopPrefixEntry>::value_type& x) {
+                                  return x.first.isPrefixOf(prefix) || prefix.isPrefixOf(x.first);
+                                });
+  if (hasOverlap) {
+    BOOST_THROW_EXCEPTION(std::out_of_range("Top-level Prefixes overlapped"));
+  }
+
+  TopPrefixEntry& topPrefixEntry = m_topLevelPrefixes[prefix];;
+  topPrefixEntry.topPrefix = prefix;
+  topPrefixEntry.wantRegister = wantRegister;
+
+  if (wantRegister) {
+    RegisterPrefixFailureCallback failure = [] (const Name& name, const std::string& reason) {
+      BOOST_THROW_EXCEPTION(std::runtime_error(reason));
+    };
+    topPrefixEntry.registerPrefixId =
+      m_face.registerPrefix(prefix, bind([]{}), failure, signingInfo);
+  }
+
+  for (auto&& entry : m_handlers) {
+    Name fullPrefix = prefix;
+    fullPrefix.append(entry.first);
+
+    const InterestFilterId* interestFilterId =
+      m_face.setInterestFilter(fullPrefix, std::bind(entry.second, prefix, _2));
+
+    topPrefixEntry.interestFilters.push_back(interestFilterId);
+  }
+}
+
+void
+Dispatcher::removeTopPrefix(const Name& prefix)
+{
+  auto it = m_topLevelPrefixes.find(prefix);
+  if (it == m_topLevelPrefixes.end()) {
+    return;
+  }
+
+  const TopPrefixEntry& topPrefixEntry = it->second;
+  if (topPrefixEntry.wantRegister) {
+    m_face.unregisterPrefix(topPrefixEntry.registerPrefixId, bind([]{}), bind([]{}));
+  }
+
+  for (auto&& filter : topPrefixEntry.interestFilters) {
+    m_face.unsetInterestFilter(filter);
+  }
+
+  m_topLevelPrefixes.erase(it);
+}
+
+bool
+Dispatcher::isOverlappedWithOthers(const PartialName& relPrefix)
+{
+  bool hasOverlapWithHandlers =
+    std::any_of(m_handlers.begin(), m_handlers.end(),
+                [&] (const HandlerMap::value_type& entry) {
+                  return entry.first.isPrefixOf(relPrefix) || relPrefix.isPrefixOf(entry.first);
+                });
+  bool hasOverlapWithStreams =
+    std::any_of(m_streams.begin(), m_streams.end(),
+                [&] (const std::unordered_map<PartialName, uint64_t>::value_type& entry) {
+                  return entry.first.isPrefixOf(relPrefix) || relPrefix.isPrefixOf(entry.first);
+                });
+
+  return hasOverlapWithHandlers || hasOverlapWithStreams;
+}
+
+void
+Dispatcher::afterAuthorizationRejected(RejectReply act, const Interest& interest)
+{
+  if (act == RejectReply::STATUS403) {
+    sendControlResponse(ControlResponse(403, "authorization rejected"), interest);
+  }
+}
+
+void
+Dispatcher::sendData(const Name& dataName, const Block& content,
+                     const MetaInfo& metaInfo)
+{
+  shared_ptr<Data> data = make_shared<Data>(dataName);
+  data->setContent(content).setMetaInfo(metaInfo);
+
+  m_keyChain.sign(*data, m_signingInfo);
+
+  try {
+    m_face.put(*data);
+  }
+  catch (Face::Error& e) {
+#ifdef NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING
+    std::clog << e.what() << std::endl;
+#endif // NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING.
+  }
+}
+
+void
+Dispatcher::processControlCommandInterest(const Name& prefix,
+                                          const Name& relPrefix,
+                                          const Interest& interest,
+                                          const ControlParametersParser& parser,
+                                          const Authorization& authorization,
+                                          const AuthorizationAcceptedCallback& accepted,
+                                          const AuthorizationRejectedCallback& rejected)
+{
+  // /<prefix>/<relPrefix>/<parameters>
+  size_t parametersLoc = prefix.size() + relPrefix.size();
+  const name::Component& pc = interest.getName().get(parametersLoc);
+
+  shared_ptr<ControlParameters> parameters;
+  try {
+    parameters = parser(pc);
+  }
+  catch (tlv::Error& e) {
+    return;
+  }
+
+  AcceptContinuation accept = bind(accepted, _1, prefix, interest, parameters.get());
+  RejectContinuation reject = bind(rejected, _1, interest);
+  authorization(prefix, interest, parameters.get(), accept, reject);
+}
+
+void
+Dispatcher::processAuthorizedControlCommandInterest(const std::string& requester,
+                                                    const Name& prefix,
+                                                    const Interest& interest,
+                                                    const ControlParameters* parameters,
+                                                    const ValidateParameters& validateParams,
+                                                    const ControlCommandHandler& handler)
+{
+  if (validateParams(*parameters)) {
+    handler(prefix, interest, *parameters,
+            bind(&Dispatcher::sendControlResponse, this, _1, interest, false));
+  }
+  else {
+    sendControlResponse(ControlResponse(400, "failed in validating parameters"), interest);
+  }
+}
+
+void
+Dispatcher::sendControlResponse(const ControlResponse& resp, const Interest& interest,
+                                bool isNack/*= false*/)
+{
+  MetaInfo info;
+  if (isNack) {
+    info.setType(tlv::ContentType_Nack);
+  }
+
+  sendData(interest.getName(), resp.wireEncode(), info);
+}
+
+void
+Dispatcher::addStatusDataset(const PartialName& relPrefix,
+                             Authorization authorization,
+                             StatusDatasetHandler handler)
+{
+  if (!m_topLevelPrefixes.empty()) {
+    BOOST_THROW_EXCEPTION(std::domain_error("one or more top-level prefix has been added"));
+  }
+
+  if (isOverlappedWithOthers(relPrefix)) {
+    BOOST_THROW_EXCEPTION(std::out_of_range("relPrefix overlapped"));
+  }
+
+  AuthorizationAcceptedCallback accepted =
+    bind(&Dispatcher::processAuthorizedStatusDatasetInterest, this,
+         _1, _2, _3, handler);
+  AuthorizationRejectedCallback rejected =
+    bind(&Dispatcher::afterAuthorizationRejected, this, _1, _2);
+  m_handlers[relPrefix] = bind(&Dispatcher::processStatusDatasetInterest, this,
+                               _1, _2, authorization, accepted, rejected);
+}
+
+void
+Dispatcher::processStatusDatasetInterest(const Name& prefix,
+                                         const Interest& interest,
+                                         const Authorization& authorization,
+                                         const AuthorizationAcceptedCallback& accepted,
+                                         const AuthorizationRejectedCallback& rejected)
+{
+  const Name& interestName = interest.getName();
+  bool endsWithVersionOrSegment = interestName.size() >= 1 &&
+                                  (interestName[-1].isVersion() || interestName[-1].isSegment());
+  if (endsWithVersionOrSegment) {
+    return;
+  }
+
+  AcceptContinuation accept = bind(accepted, _1, prefix, interest, nullptr);
+  RejectContinuation reject = bind(rejected, _1, interest);
+  authorization(prefix, interest, nullptr, accept, reject);
+}
+
+void
+Dispatcher::processAuthorizedStatusDatasetInterest(const std::string& requester,
+                                                   const Name& prefix,
+                                                   const Interest& interest,
+                                                   const StatusDatasetHandler& handler)
+{
+  StatusDatasetContext context(interest, bind(&Dispatcher::sendData, this, _1, _2, _3));
+  handler(prefix, interest, context);
+}
+
+PostNotification
+Dispatcher::addNotificationStream(const PartialName& relPrefix)
+{
+  if (!m_topLevelPrefixes.empty()) {
+    throw std::domain_error("one or more top-level prefix has been added");
+  }
+
+  if (isOverlappedWithOthers(relPrefix)) {
+    throw std::out_of_range("relPrefix overlaps with another relPrefix");
+  }
+
+  m_streams[relPrefix] = 0;
+  return bind(&Dispatcher::postNotification, this, _1, relPrefix);
+}
+
+void
+Dispatcher::postNotification(const Block& notification, const PartialName& relPrefix)
+{
+  if (m_topLevelPrefixes.empty() || m_topLevelPrefixes.size() > 1) {
+#ifdef NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING
+    std::clog << "no top-level prefix or too many top-level prefixes" << std::endl;
+#endif // NDN_CXX_MGMT_DISPATCHER_ENABLE_LOGGING.
+    return;
+  }
+
+  Name streamName(m_topLevelPrefixes.begin()->second.topPrefix);
+  streamName.append(relPrefix);
+  streamName.appendSequenceNumber(m_streams[streamName]++);
+  sendData(streamName, notification, MetaInfo());
+}
+
+} // namespace mgmt
+} // namespace ndn
diff --git a/src/mgmt/dispatcher.hpp b/src/mgmt/dispatcher.hpp
new file mode 100644
index 0000000..922722f
--- /dev/null
+++ b/src/mgmt/dispatcher.hpp
@@ -0,0 +1,451 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NDN_MGMT_DISPATCHER_HPP
+#define NDN_MGMT_DISPATCHER_HPP
+
+#include "../face.hpp"
+#include "../security/key-chain.hpp"
+#include "../encoding/block.hpp"
+#include "control-response.hpp"
+#include "control-parameters.hpp"
+#include "status-dataset-context.hpp"
+
+#include <unordered_map>
+
+namespace ndn {
+namespace mgmt {
+
+// ---- AUTHORIZATION ----
+
+/** \brief a function to be called if authorization is successful
+ *  \param requester a string that indicates the requester, whose semantics is determined by
+ *                   the Authorization function; this value is intended for logging only,
+ *                   and should not affect how the request is processed
+ */
+typedef std::function<void(const std::string& requester)> AcceptContinuation;
+
+/** \brief indicate how to reply in case authorization is rejected
+ */
+enum class RejectReply {
+  /** \brief do not reply
+   */
+  SILENT,
+  /** \brief reply with a ControlResponse where StatusCode is 403
+   */
+  STATUS403
+};
+
+/** \brief a function to be called if authorization is rejected
+ */
+typedef std::function<void(RejectReply act)> RejectContinuation;
+
+/** \brief a function that performs authorization
+ *  \param prefix top-level prefix, e.g., "/localhost/nfd";
+ *                This argument can be inspected to allow Interests only under a subset of
+ *                top-level prefixes (e.g., allow "/localhost/nfd" only),
+ *                or to use different trust model regarding to the prefix.
+ *  \param interest incoming Interest
+ *  \param params parsed ControlParameters for ControlCommand, otherwise nullptr;
+ *                This is guaranteed to be not-null and have correct type for the command,
+ *                but may not be valid (e.g., can have missing fields).
+ *
+ *  Either accept or reject must be called after authorization completes.
+ */
+typedef std::function<void(const Name& prefix, const Interest& interest,
+                           const ControlParameters* params,
+                           AcceptContinuation accept,
+                           RejectContinuation reject)> Authorization;
+
+/** \return an Authorization that accepts all Interests, with empty string as requester
+ */
+Authorization
+makeAcceptAllAuthorization();
+
+// ---- CONTROL COMMAND ----
+
+/** \brief a function to validate input ControlParameters
+ *  \param params parsed ControlParameters;
+ *                This is guaranteed to have correct type for the command.
+ */
+typedef std::function<bool(const ControlParameters& params)> ValidateParameters;
+
+/** \brief a function to be called after ControlCommandHandler completes
+ *  \param resp the response to be sent to requester
+ */
+typedef std::function<void(const ControlResponse& resp)> CommandContinuation;
+
+/** \brief a function to handle an authorized ControlCommand
+ *  \param prefix top-level prefix, e.g., "/localhost/nfd";
+ *  \param interest incoming Interest
+ *  \param params parsed ControlParameters;
+ *                This is guaranteed to have correct type for the command,
+ *                and is valid (e.g., has all required fields).
+ */
+typedef std::function<void(const Name& prefix, const Interest& interest,
+                           const ControlParameters& params,
+                           CommandContinuation done)> ControlCommandHandler;
+
+
+/** \brief a function to handle a StatusDataset request
+ *  \param prefix top-level prefix, e.g., "/localhost/nfd";
+ *  \param interest incoming Interest; its Name doesn't contain version and segment components
+ *
+ *  This function can generate zero or more blocks and pass them to \p append,
+ *  and must call \p end upon completion.
+ */
+typedef std::function<void(const Name& prefix, const Interest& interest,
+                           StatusDatasetContext& context)> StatusDatasetHandler;
+
+//---- NOTIFICATION STREAM ----
+
+/** \brief a function to post a notification
+ */
+typedef std::function<void(const Block& notification)> PostNotification;
+
+// ---- DISPATCHER ----
+
+/** \brief represents a dispatcher on server side of NFD Management protocol
+ */
+class Dispatcher : noncopyable
+{
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+public:
+  /** \brief constructor
+   *  \param face the Face on which the dispatcher operates
+   *  \param keyChain a KeyChain to sign Data
+   *  \param signingInfo signing parameters to sign Data with \p keyChain
+   */
+  Dispatcher(Face& face, security::KeyChain& keyChain,
+             const security::SigningInfo& signingInfo = security::SigningInfo());
+
+  virtual
+  ~Dispatcher();
+
+  /** \brief add a top-level prefix
+   *  \param prefix a top-level prefix, e.g., "/localhost/nfd"
+   *  \param wantRegister whether prefix registration should be performed through the Face
+   *  \param signingInfo signing parameters to sign the prefix registration command
+   *  \throw std::out_of_range \p prefix overlaps with an existing top-level prefix
+   *
+   *  Procedure for adding a top-level prefix:
+   *  1. if the new top-level prefix overlaps with an existing top-level prefix
+   *     (one top-level prefix is a prefix of another top-level prefix), throw std::domain_error
+   *  2. if wantRegister is true, invoke face.registerPrefix for the top-level prefix;
+   *     the returned RegisteredPrefixId shall be recorded internally, indexed by the top-level
+   *     prefix
+   *  3. foreach relPrefix from ControlCommands and StatusDatasets,
+   *     join the top-level prefix with the relPrefix to obtain the full prefix,
+   *     and invoke non-registering overload of face.setInterestFilter,
+   *     with the InterestHandler set to an appropriate private method to handle incoming Interests
+   *     for the ControlCommand or StatusDataset;
+   *     the returned InterestFilterId shall be recorded internally, indexed by the top-level
+   *     prefix
+   */
+  void
+  addTopPrefix(const Name& prefix, bool wantRegister = true,
+               const security::SigningInfo& signingInfo = security::SigningInfo());
+
+  /** \brief remove a top-level prefix
+   *  \param prefix a top-level prefix, e.g., "/localhost/nfd"
+   *
+   *  Procedure for removing a top-level prefix:
+   *  1. if the top-level prefix has not been added, abort these steps
+   *  2. if the top-level prefix has been added with wantRegister,
+   *     invoke face.unregisterPrefix with the RegisteredPrefixId
+   *  3. foreach InterestFilterId recorded during addTopPrefix,
+   *     invoke face.unsetInterestFilter with the InterestFilterId
+   */
+  void
+  removeTopPrefix(const Name& prefix);
+
+public: // ControlCommand
+  /** \brief register a ControlCommand
+   *  \tparam CP subclass of ControlParameters used by this command
+   *  \param relPrefix a prefix for this command, e.g., "faces/create";
+   *                   relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be
+   *                   non-overlapping
+   *                   (no relPrefix is a prefix of another relPrefix)
+   *  \pre no top-level prefix has been added
+   *  \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
+   *  \throw std::domain_error one or more top-level prefix has been added
+   *
+   *  Procedure for processing a ControlCommand:
+   *  1. extract the NameComponent containing ControlParameters (the component after relPrefix),
+   *     and parse ControlParameters into type CP; if parsing fails, abort these steps
+   *  2. perform authorization; if authorization is rejected,
+   *     perform the RejectReply action, and abort these steps
+   *  3. validate ControlParameters; if validation fails,
+   *     make ControlResponse with StatusCode 400, and go to step 5
+   *  4. invoke handler, wait until CommandContinuation is called
+   *  5. encode the ControlResponse into one Data packet
+   *  6. sign the Data packet
+   *  7. if the Data packet is too large, abort these steps and log an error
+   *  8. send the signed Data packet
+   */
+  template<typename CP>
+  void
+  addControlCommand(const PartialName& relPrefix,
+                    Authorization authorization,
+                    ValidateParameters validateParams,
+                    ControlCommandHandler handler);
+
+public: // StatusDataset
+  /** \brief register a StatusDataset or a prefix under which StatusDatasets can be requested
+   *  \param relPrefix a prefix for this dataset, e.g., "faces/list";
+   *                   relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be
+   *                   non-overlapping
+   *                   (no relPrefix is a prefix of another relPrefix)
+   *  \param authorization should set identity to Name() if the dataset is public
+   *  \pre no top-level prefix has been added
+   *  \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
+   *  \throw std::domain_error one or more top-level prefix has been added
+   *
+   * The payload of the returned status dataset data packet is at most half of the maximum
+   * data packet size.
+   *
+   *  Procedure for processing a StatusDataset request:
+   *  1. if the request Interest contains version or segment components, abort these steps;
+   *     note: the request may contain more components after relPrefix, e.g., a query condition
+   *  2. perform authorization; if authorization is rejected,
+   *     perform the RejectReply action, and abort these steps
+   *  3. invoke handler, store blocks passed to StatusDatasetAppend calls in a buffer,
+   *     wait until StatusDatasetEnd is called
+   *  4. allocate a version
+   *  5. segment the buffer into one or more segments under the allocated version,
+   *     such that the Data packets will not become too large after signing
+   *  6. set FinalBlockId on at least the last segment
+   *  7. sign the Data packets
+   *  8. send the signed Data packets
+   *
+   *  As an optimization, a Data packet may be sent as soon as enough octets have been collected
+   *  through StatusDatasetAppend calls.
+   */
+  void
+  addStatusDataset(const PartialName& relPrefix,
+                   Authorization authorization,
+                   StatusDatasetHandler handler);
+
+public: // NotificationStream
+  /** \brief register a NotificationStream
+   *  \param relPrefix a prefix for this notification stream, e.g., "faces/events";
+   *                   relPrefixes in ControlCommands, StatusDatasets, NotificationStreams must be
+   *                   non-overlapping
+   *                   (no relPrefix is a prefix of another relPrefix)
+   *  \return a function into which notifications can be posted
+   *  \pre no top-level prefix has been added
+   *  \throw std::out_of_range \p relPrefix overlaps with an existing relPrefix
+   *  \throw std::domain_error one or more top-level prefix has been added
+   *
+   *  Procedure for posting a notification:
+   *  1. if no top-level prefix has been added, or more than one top-level prefixes have been
+   *     added,
+   *     abort these steps and log an error
+   *  2. assign the next sequence number to the notification
+   *  3. place the notification block into one Data packet under the sole top-level prefix
+   *  4. sign the Data packet
+   *  5. if the Data packet is too large, abort these steps and log an error
+   *  6. send the signed Data packet
+   */
+  PostNotification
+  addNotificationStream(const PartialName& relPrefix);
+
+private:
+  typedef std::function<void(const Name& prefix,
+                             const Interest& interest)> InterestHandler;
+
+  typedef std::function<void(const std::string& requester,
+                             const Name& prefix,
+                             const Interest& interest,
+                             const ControlParameters*)> AuthorizationAcceptedCallback;
+
+  typedef std::function<void(RejectReply act,
+                             const Interest& interest)> AuthorizationRejectedCallback;
+
+  /**
+   * @brief the parser of extracting control parameters from name component.
+   *
+   * @param component name component that may encode control parameters.
+   * @return a shared pointer to the extracted control parameters.
+   * @throw tlv::Error if the NameComponent cannot be parsed as ControlParameters
+   */
+  typedef std::function<shared_ptr<ControlParameters>(const name::Component& component)>
+  ControlParametersParser;
+
+  bool
+  isOverlappedWithOthers(const PartialName& relPrefix);
+
+  /**
+   * @brief process unauthorized request
+   *
+   * @param act action to reply
+   * @param interest the incoming Interest
+   */
+  void
+  afterAuthorizationRejected(RejectReply act, const Interest& interest);
+
+  void
+  sendData(const Name& dataName, const Block& content,
+           const MetaInfo& metaInfo);
+
+  /**
+   * @brief process the control-command Interest before authorization.
+   *
+   * @param prefix the top-level prefix
+   * @param relPrefix the relative prefix
+   * @param interest the incoming Interest
+   * @param parser to extract control parameters from the \p interest
+   * @param authorization to process validation on this command
+   * @param accepted the callback for successful authorization
+   * @param rejected the callback for failed authorization
+   */
+  void
+  processControlCommandInterest(const Name& prefix,
+                                const Name& relPrefix,
+                                const Interest& interest,
+                                const ControlParametersParser& parser,
+                                const Authorization& authorization,
+                                const AuthorizationAcceptedCallback& accepted,
+                                const AuthorizationRejectedCallback& rejected);
+
+  /**
+   * @brief process the authorized control-command.
+   *
+   * @param requester the requester
+   * @param prefix the top-level prefix
+   * @param interest the incoming Interest
+   * @param parameters control parameters of this command
+   * @param validate to validate control parameters
+   * @param handler to process this command
+   */
+  void
+  processAuthorizedControlCommandInterest(const std::string& requester,
+                                          const Name& prefix,
+                                          const Interest& interest,
+                                          const ControlParameters* parameters,
+                                          const ValidateParameters& validate,
+                                          const ControlCommandHandler& handler);
+
+  void
+  sendControlResponse(const ControlResponse& resp, const Interest& interest, bool isNack = false);
+
+  /**
+   * @brief process the status-dataset Interest before authorization.
+   *
+   * @param prefix the top-level prefix
+   * @param interest the incoming Interest
+   * @param authorization to process verification
+   * @param accepted callback for successful authorization
+   * @param rejected callback for failed authorization
+   */
+  void
+  processStatusDatasetInterest(const Name& prefix,
+                               const Interest& interest,
+                               const Authorization& authorization,
+                               const AuthorizationAcceptedCallback& accepted,
+                               const AuthorizationRejectedCallback& rejected);
+
+  /**
+   * @brief process the authorized status-dataset request
+   *
+   * @param requester the requester
+   * @param prefix the top-level prefix
+   * @param interest the incoming Interest
+   * @param handler to process this request
+   */
+  void
+  processAuthorizedStatusDatasetInterest(const std::string& requester,
+                                         const Name& prefix,
+                                         const Interest& interest,
+                                         const StatusDatasetHandler& handler);
+
+  void
+  postNotification(const Block& notification, const PartialName& relPrefix);
+
+private:
+  struct TopPrefixEntry
+  {
+    Name topPrefix;
+    bool wantRegister;
+    const ndn::RegisteredPrefixId* registerPrefixId;
+    std::vector<const ndn::InterestFilterId*> interestFilters;
+  };
+  std::unordered_map<Name, TopPrefixEntry> m_topLevelPrefixes;
+
+  Face& m_face;
+  security::KeyChain& m_keyChain;
+  security::SigningInfo m_signingInfo;
+
+  typedef std::unordered_map<PartialName, InterestHandler> HandlerMap;
+  typedef HandlerMap::iterator HandlerMapIt;
+  HandlerMap m_handlers;
+
+  // NotificationStream name => next sequence number
+  std::unordered_map<Name, uint64_t> m_streams;
+};
+
+template<typename CP>
+void
+Dispatcher::addControlCommand(const PartialName& relPrefix,
+                              Authorization authorization,
+                              ValidateParameters validateParams,
+                              ControlCommandHandler handler)
+{
+  if (!m_topLevelPrefixes.empty()) {
+    throw std::domain_error("one or more top-level prefix has been added");
+  }
+
+  if (isOverlappedWithOthers(relPrefix)) {
+    throw std::out_of_range("relPrefix overlaps with another relPrefix");
+  }
+
+  ControlParametersParser parser =
+    [] (const name::Component& component) -> shared_ptr<ControlParameters> {
+    return make_shared<CP>(component.blockFromValue());
+  };
+
+  AuthorizationAcceptedCallback accepted =
+    bind(&Dispatcher::processAuthorizedControlCommandInterest, this,
+         _1, _2, _3, _4, validateParams, handler);
+
+  AuthorizationRejectedCallback rejected =
+    bind(&Dispatcher::afterAuthorizationRejected, this, _1, _2);
+
+  m_handlers[relPrefix] = bind(&Dispatcher::processControlCommandInterest, this,
+                               _1, relPrefix, _2, parser, authorization, accepted, rejected);
+}
+
+} // namespace mgmt
+} // namespace ndn
+#endif // NDN_MGMT_DISPATCHER_HPP
diff --git a/src/mgmt/status-dataset-context.cpp b/src/mgmt/status-dataset-context.cpp
new file mode 100644
index 0000000..1d386ef
--- /dev/null
+++ b/src/mgmt/status-dataset-context.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "status-dataset-context.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+const time::milliseconds DEFAULT_STATUS_DATASET_FRESHNESS_PERIOD = time::milliseconds(1000);
+
+const Name&
+StatusDatasetContext::getPrefix() const
+{
+  return m_prefix;
+}
+
+StatusDatasetContext&
+StatusDatasetContext::setPrefix(const Name& prefix)
+{
+  if (!m_interest.getName().isPrefixOf(prefix)) {
+    BOOST_THROW_EXCEPTION(std::invalid_argument("prefix does not start with Interest Name"));
+  }
+
+  if (m_state != State::INITIAL) {
+    BOOST_THROW_EXCEPTION(std::domain_error("state is not in INITIAL"));
+  }
+
+  m_prefix = prefix;
+
+  if (!m_prefix[-1].isVersion()) {
+    m_prefix.appendVersion();
+  }
+
+  return *this;
+}
+
+const time::milliseconds&
+StatusDatasetContext::getExpiry() const
+{
+  return m_expiry;
+}
+
+StatusDatasetContext&
+StatusDatasetContext::setExpiry(const time::milliseconds& expiry)
+{
+  m_expiry = expiry;
+  return *this;
+}
+
+void
+StatusDatasetContext::append(const Block& block)
+{
+  if (m_state == State::FINALIZED) {
+    BOOST_THROW_EXCEPTION(std::domain_error("state is in FINALIZED"));
+  }
+
+  m_state = State::RESPONDED;
+
+  size_t nBytesLeft = block.size();
+
+  while (nBytesLeft > 0) {
+    size_t nBytesAppend = std::min(nBytesLeft,
+                                   (ndn::MAX_NDN_PACKET_SIZE >> 1) - m_buffer->size());
+    m_buffer->appendByteArray(block.wire() + (block.size() - nBytesLeft), nBytesAppend);
+    nBytesLeft -= nBytesAppend;
+
+    if (nBytesLeft > 0) {
+      const Block& content = makeBinaryBlock(tlv::Content, m_buffer->buf(), m_buffer->size());
+      m_dataSender(Name(m_prefix).appendSegment(m_segmentNo++), content,
+                   MetaInfo().setFreshnessPeriod(m_expiry));
+
+      m_buffer = std::make_shared<EncodingBuffer>();
+    }
+  }
+}
+
+void
+StatusDatasetContext::end()
+{
+  if (m_state == State::FINALIZED) {
+    BOOST_THROW_EXCEPTION(std::domain_error("state is in FINALIZED"));
+  }
+
+  m_state = State::FINALIZED;
+
+  auto dataName = Name(m_prefix).appendSegment(m_segmentNo++);
+  m_dataSender(dataName, makeBinaryBlock(tlv::Content, m_buffer->buf(), m_buffer->size()),
+               MetaInfo().setFreshnessPeriod(m_expiry).setFinalBlockId(dataName[-1]));
+}
+
+void
+StatusDatasetContext::reject(const ControlResponse& resp /*= a ControlResponse with 400*/)
+{
+  if (m_state != State::INITIAL) {
+    BOOST_THROW_EXCEPTION(std::domain_error("state is in REPONSED or FINALIZED"));
+  }
+
+  m_state = State::FINALIZED;
+
+  m_dataSender(m_interest.getName(), resp.wireEncode(),
+               MetaInfo().setType(tlv::ContentType_Nack));
+}
+
+StatusDatasetContext::StatusDatasetContext(const Interest& interest,
+                                           const DataSender& dataSender)
+  : m_interest(interest)
+  , m_dataSender(dataSender)
+  , m_expiry(DEFAULT_STATUS_DATASET_FRESHNESS_PERIOD)
+  , m_buffer(make_shared<EncodingBuffer>())
+  , m_segmentNo(0)
+  , m_state(State::INITIAL)
+{
+  setPrefix(interest.getName());
+}
+
+} // namespace mgmt
+} // namespace ndn
diff --git a/src/mgmt/status-dataset-context.hpp b/src/mgmt/status-dataset-context.hpp
new file mode 100644
index 0000000..196a296
--- /dev/null
+++ b/src/mgmt/status-dataset-context.hpp
@@ -0,0 +1,125 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NDN_MGMT_STATUS_DATASET_CONTEXT_HPP
+#define NDN_MGMT_STATUS_DATASET_CONTEXT_HPP
+
+#include "../interest.hpp"
+#include "../data.hpp"
+#include "../util/time.hpp"
+#include "../encoding/encoding-buffer.hpp"
+#include "control-response.hpp"
+
+namespace ndn {
+namespace mgmt {
+
+class StatusDatasetContext
+{
+public:
+  /** \return prefix of Data packets, with version component but without segment component
+   */
+  const Name&
+  getPrefix() const;
+
+  /** \brief change prefix of Data packets
+   *  \param prefix the prefix; it must start with Interest Name, may contain version component,
+   *         but must not contain segment component
+   *  \throw std::invalid_argument prefix does not start with Interest Name
+   *  \throw std::domain_error append, end, or reject has been invoked
+   *
+   *  StatusDatasetHandler may change the prefix of Data packets with this method,
+   *  before sending any response.
+   *  The version component is optional, and will be generated from current timestamp when omitted.
+   */
+  StatusDatasetContext&
+  setPrefix(const Name& prefix);
+
+  /** \return expiration duration for this dataset response
+   */
+  const time::milliseconds&
+  getExpiry() const;
+
+  /** \brief set expiration duration
+   *
+   *  The response will be cached for the specified duration.
+   *  Incoming Interest that matches a cached response will be satisfied with that response,
+   *  without invoking StatusDatasetHandler again.
+   */
+  StatusDatasetContext&
+  setExpiry(const time::milliseconds& expiry);
+
+  /** \brief append a Block to the response
+   *  \throw std::domain_error end or reject has been invoked
+   */
+  void
+  append(const Block& block);
+
+  /** \brief end the response successfully after appending zero or more blocks
+   *  \throw std::domain_error reject has been invoked
+   */
+  void
+  end();
+
+  /** \brief declare the non-existence of a response
+   *  \throw std::domain_error append or end has been invoked
+   *
+   *  This should be invoked when the incoming Interest is malformed.
+   *  A producer-generated NACK will be returned to requester.
+   *
+   *  \param content Content of producer-generated NACK
+   */
+  void
+  reject(const ControlResponse& resp = ControlResponse().setCode(400));
+
+NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  typedef std::function<void(const Name& dataName, const Block& content,
+                             const MetaInfo& metaInfo)> DataSender;
+
+  StatusDatasetContext(const Interest& interest, const DataSender& dataSender);
+
+private:
+  friend class Dispatcher;
+
+  const Interest& m_interest;
+  DataSender m_dataSender;
+  Name m_prefix;
+  time::milliseconds m_expiry;
+
+NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  shared_ptr<EncodingBuffer> m_buffer;
+  uint64_t m_segmentNo;
+
+  enum class State {
+    INITIAL, ///< none of .append, .end, .reject has been invoked
+    RESPONDED, ///< .append has been invoked
+    FINALIZED ///< .end or .reject has been invoked
+  };
+  State m_state;
+};
+
+} // namespace mgmt
+} // namespace ndn
+
+#endif // NDN_MGMT_STATUS_DATASET_CONTEXT_HPP
diff --git a/tests/unit-tests/mgmt/dispatcher.t.cpp b/tests/unit-tests/mgmt/dispatcher.t.cpp
new file mode 100644
index 0000000..050b927
--- /dev/null
+++ b/tests/unit-tests/mgmt/dispatcher.t.cpp
@@ -0,0 +1,386 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mgmt/dispatcher.hpp"
+#include "management/nfd-control-parameters.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+#include "unit-tests/unit-test-time-fixture.hpp"
+#include "unit-tests/make-interest-data.hpp"
+
+namespace ndn {
+namespace mgmt {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(MgmtDispatcher)
+
+class DispatcherFixture : public UnitTestTimeFixture
+                        , public security::IdentityManagementFixture
+{
+public:
+  DispatcherFixture()
+    : face(util::makeDummyClientFace(io, {true, true}))
+    , dispatcher(*face, m_keyChain, security::SigningInfo())
+  {
+  }
+
+public:
+  shared_ptr<util::DummyClientFace> face;
+  mgmt::Dispatcher dispatcher;
+};
+
+class VoidParameters : public mgmt::ControlParameters
+{
+public:
+  explicit
+  VoidParameters(const Block& wire)
+  {
+    wireDecode(wire);
+  }
+
+  virtual Block
+  wireEncode() const NDN_CXX_DECL_FINAL
+  {
+    return Block(128);
+  }
+
+  virtual void
+  wireDecode(const Block& wire) NDN_CXX_DECL_FINAL
+  {
+    if (wire.type() != 128)
+      throw tlv::Error("Expecting TLV type 128");
+  }
+};
+
+static Authorization
+makeTestAuthorization()
+{
+  return [] (const Name& prefix,
+             const Interest& interest,
+             const ControlParameters* params,
+             AcceptContinuation accept,
+             RejectContinuation reject) {
+    if (interest.getName()[-1] == name::Component("valid")) {
+      accept("");
+    }
+    else {
+      if (interest.getName()[-1] == name::Component("silent")) {
+        reject(RejectReply::SILENT);
+      }
+      else {
+        reject(RejectReply::STATUS403);
+      }
+    }
+  };
+}
+
+BOOST_FIXTURE_TEST_CASE(BasicUsageSemantics, DispatcherFixture)
+{
+  BOOST_CHECK_NO_THROW(dispatcher
+                         .addControlCommand<VoidParameters>("test/1", makeAcceptAllAuthorization(),
+                                                            bind([] { return true; }),
+                                                            bind([]{})));
+  BOOST_CHECK_NO_THROW(dispatcher
+                         .addControlCommand<VoidParameters>("test/2", makeAcceptAllAuthorization(),
+                                                            bind([] { return true; }),
+                                                            bind([]{})));
+
+  BOOST_CHECK_THROW(dispatcher
+                      .addControlCommand<VoidParameters>("test", makeAcceptAllAuthorization(),
+                                                         bind([] { return true; }),
+                                                         bind([]{})),
+                    std::out_of_range);
+
+  BOOST_CHECK_NO_THROW(dispatcher.addStatusDataset("status/1",
+                                                   makeAcceptAllAuthorization(), bind([]{})));
+  BOOST_CHECK_NO_THROW(dispatcher.addStatusDataset("status/2",
+                                                   makeAcceptAllAuthorization(), bind([]{})));
+  BOOST_CHECK_THROW(dispatcher.addStatusDataset("status",
+                                                makeAcceptAllAuthorization(), bind([]{})),
+                    std::out_of_range);
+
+  BOOST_CHECK_NO_THROW(dispatcher.addNotificationStream("stream/1"));
+  BOOST_CHECK_NO_THROW(dispatcher.addNotificationStream("stream/2"));
+  BOOST_CHECK_THROW(dispatcher.addNotificationStream("stream"), std::out_of_range);
+
+
+  BOOST_CHECK_NO_THROW(dispatcher.addTopPrefix("/root/1"));
+  BOOST_CHECK_NO_THROW(dispatcher.addTopPrefix("/root/2"));
+  BOOST_CHECK_THROW(dispatcher.addTopPrefix("/root"), std::out_of_range);
+
+  BOOST_CHECK_THROW(dispatcher
+                      .addControlCommand<VoidParameters>("test/3", makeAcceptAllAuthorization(),
+                                                         bind([] { return true; }),
+                                                         bind([]{})),
+                    std::domain_error);
+
+  BOOST_CHECK_THROW(dispatcher.addStatusDataset("status/3",
+                                                makeAcceptAllAuthorization(), bind([]{})),
+                    std::domain_error);
+
+  BOOST_CHECK_THROW(dispatcher.addNotificationStream("stream/3"), std::domain_error);
+}
+
+BOOST_FIXTURE_TEST_CASE(AddRemoveTopPrefix, DispatcherFixture)
+{
+  std::map<std::string, size_t> nCallbackCalled;
+  dispatcher
+    .addControlCommand<VoidParameters>("test/1", makeAcceptAllAuthorization(),
+                                       bind([] { return true; }),
+                                       bind([&nCallbackCalled] { ++nCallbackCalled["test/1"]; }));
+
+  dispatcher
+    .addControlCommand<VoidParameters>("test/2", makeAcceptAllAuthorization(),
+                                       bind([] { return true; }),
+                                       bind([&nCallbackCalled] { ++nCallbackCalled["test/2"]; }));
+
+  face->receive(*util::makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 0);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 0);
+
+  dispatcher.addTopPrefix("/root/1");
+  advanceClocks(time::milliseconds(1));
+
+  face->receive(*util::makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 1);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 0);
+
+  face->receive(*util::makeInterest("/root/1/test/2/%80%00"));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 1);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 1);
+
+  face->receive(*util::makeInterest("/root/2/test/1/%80%00"));
+  face->receive(*util::makeInterest("/root/2/test/2/%80%00"));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 1);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 1);
+
+  dispatcher.addTopPrefix("/root/2");
+  advanceClocks(time::milliseconds(1));
+
+  face->receive(*util::makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 2);
+
+  face->receive(*util::makeInterest("/root/2/test/1/%80%00"));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 3);
+
+  dispatcher.removeTopPrefix("/root/1");
+  advanceClocks(time::milliseconds(1));
+
+  face->receive(*util::makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 3);
+
+  face->receive(*util::makeInterest("/root/2/test/1/%80%00"));
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 4);
+}
+
+BOOST_FIXTURE_TEST_CASE(ControlCommand, DispatcherFixture)
+{
+  size_t nCallbackCalled = 0;
+  dispatcher
+    .addControlCommand<VoidParameters>("test",
+                                       makeTestAuthorization(),
+                                       bind([] { return true; }),
+                                       bind([&nCallbackCalled] { ++nCallbackCalled; }));
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(time::milliseconds(1));
+  face->sentDatas.clear();
+
+  face->receive(*util::makeInterest("/root/test/%80%00")); // returns 403
+  face->receive(*util::makeInterest("/root/test/%80%00/invalid")); // returns 403
+  face->receive(*util::makeInterest("/root/test/%80%00/silent")); // silently ignored
+  face->receive(*util::makeInterest("/root/test/.../invalid")); // silently ignored (wrong format)
+  face->receive(*util::makeInterest("/root/test/.../valid"));  // silently ignored (wrong format)
+  advanceClocks(time::milliseconds(1), 20);
+  BOOST_CHECK_EQUAL(nCallbackCalled, 0);
+  BOOST_CHECK_EQUAL(face->sentDatas.size(), 2);
+
+  BOOST_CHECK(face->sentDatas[0].getContentType() == tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face->sentDatas[0].getContent().blockFromValue()).getCode(), 403);
+  BOOST_CHECK(face->sentDatas[1].getContentType() == tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face->sentDatas[1].getContent().blockFromValue()).getCode(), 403);
+
+  face->receive(*util::makeInterest("/root/test/%80%00/valid"));
+  advanceClocks(time::milliseconds(1), 10);
+  BOOST_CHECK_EQUAL(nCallbackCalled, 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(StatusDataset, DispatcherFixture)
+{
+  static Block smallBlock("\x81\x01\0x01", 3);
+  static Block largeBlock = [] () -> Block {
+    EncodingBuffer encoder;
+    for (size_t i = 0; i < 2500; ++i) {
+      encoder.prependByte(1);
+    }
+    encoder.prependVarNumber(2500);
+    encoder.prependVarNumber(129);
+    return encoder.block();
+  }();
+
+  dispatcher.addStatusDataset("test/small",
+                              makeTestAuthorization(),
+                              [] (const Name& prefix, const Interest& interest,
+                                  StatusDatasetContext context) {
+                                context.append(smallBlock);
+                                context.append(smallBlock);
+                                context.append(smallBlock);
+                                context.end();
+                              });
+
+  dispatcher.addStatusDataset("test/large",
+                              makeTestAuthorization(),
+                              [] (const Name& prefix, const Interest& interest,
+                                  StatusDatasetContext context) {
+                                context.append(largeBlock);
+                                context.append(largeBlock);
+                                context.append(largeBlock);
+                                context.end();
+                              });
+
+  dispatcher.addStatusDataset("test/reject",
+                              makeTestAuthorization(),
+                              [] (const Name& prefix, const Interest& interest,
+                                  StatusDatasetContext context) {
+                                context.reject();
+                              });
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(time::milliseconds(1));
+  face->sentDatas.clear();
+
+  face->receive(*util::makeInterest("/root/test/small/%80%00")); // returns 403
+  face->receive(*util::makeInterest("/root/test/small/%80%00/invalid")); // returns 403
+  face->receive(*util::makeInterest("/root/test/small/%80%00/silent")); // silently ignored
+  advanceClocks(time::milliseconds(1), 20);
+  BOOST_CHECK_EQUAL(face->sentDatas.size(), 2);
+
+  BOOST_CHECK(face->sentDatas[0].getContentType() == tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face->sentDatas[0].getContent().blockFromValue()).getCode(), 403);
+  BOOST_CHECK(face->sentDatas[1].getContentType() == tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face->sentDatas[1].getContent().blockFromValue()).getCode(), 403);
+
+  face->sentDatas.clear();
+  face->receive(*util::makeInterest("/root/test/small/valid"));
+  advanceClocks(time::milliseconds(1), 10);
+  BOOST_CHECK_EQUAL(face->sentDatas.size(), 1);
+
+  face->receive(*util::makeInterest(Name("/root/test/small/valid").appendVersion(10))); // should be ignored
+  face->receive(*util::makeInterest(Name("/root/test/small/valid").appendSegment(20))); // should be ignored
+  advanceClocks(time::milliseconds(1), 10);
+  BOOST_CHECK_EQUAL(face->sentDatas.size(), 1);
+
+  Block content = face->sentDatas[0].getContent();
+  BOOST_CHECK_NO_THROW(content.parse());
+
+  BOOST_CHECK_EQUAL(content.elements().size(), 3);
+  BOOST_CHECK(content.elements()[0] == smallBlock);
+  BOOST_CHECK(content.elements()[1] == smallBlock);
+  BOOST_CHECK(content.elements()[2] == smallBlock);
+
+  face->sentDatas.clear();
+  face->receive(*util::makeInterest("/root/test/large/valid"));
+  advanceClocks(time::milliseconds(1), 10);
+  BOOST_CHECK_EQUAL(face->sentDatas.size(), 2);
+
+  const auto& datas = face->sentDatas;
+  content = [&datas] () -> Block {
+    EncodingBuffer encoder;
+    size_t valueLength = encoder.prependByteArray(datas[1].getContent().value(),
+                                                  datas[1].getContent().value_size());
+    valueLength += encoder.prependByteArray(datas[0].getContent().value(),
+                                            datas[0].getContent().value_size());
+    encoder.prependVarNumber(valueLength);
+    encoder.prependVarNumber(tlv::Content);
+    return encoder.block();
+  }();
+
+  BOOST_CHECK_NO_THROW(content.parse());
+
+  BOOST_CHECK_EQUAL(content.elements().size(), 3);
+  BOOST_CHECK(content.elements()[0] == largeBlock);
+  BOOST_CHECK(content.elements()[1] == largeBlock);
+  BOOST_CHECK(content.elements()[2] == largeBlock);
+
+  face->sentDatas.clear();
+  face->receive(*util::makeInterest("/root/test/reject/%80%00/valid")); // returns nack
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(face->sentDatas.size(), 1);
+  BOOST_CHECK(face->sentDatas[0].getContentType() == tlv::ContentType_Nack);
+  BOOST_CHECK_EQUAL(ControlResponse(face->sentDatas[0].getContent().blockFromValue()).getCode(), 400);
+}
+
+BOOST_FIXTURE_TEST_CASE(NotificationStream, DispatcherFixture)
+{
+  static Block block("\x82\x01\x02", 3);
+
+  auto post = dispatcher.addNotificationStream("test");
+
+  post(block);
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(face->sentDatas.size(), 0);
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(time::milliseconds(1));
+  face->sentDatas.clear();
+
+  post(block);
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(face->sentDatas.size(), 1);
+
+  post(block);
+  post(block);
+  post(block);
+  advanceClocks(time::milliseconds(1), 10);
+
+  BOOST_CHECK_EQUAL(face->sentDatas.size(), 4);
+  BOOST_CHECK_EQUAL(face->sentDatas[0].getName(), "/root/test/%FE%00");
+  BOOST_CHECK_EQUAL(face->sentDatas[1].getName(), "/root/test/%FE%01");
+  BOOST_CHECK_EQUAL(face->sentDatas[2].getName(), "/root/test/%FE%02");
+  BOOST_CHECK_EQUAL(face->sentDatas[3].getName(), "/root/test/%FE%03");
+
+  BOOST_CHECK(face->sentDatas[0].getContent().blockFromValue() == block);
+  BOOST_CHECK(face->sentDatas[1].getContent().blockFromValue() == block);
+  BOOST_CHECK(face->sentDatas[2].getContent().blockFromValue() == block);
+  BOOST_CHECK(face->sentDatas[3].getContent().blockFromValue() == block);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace mgmt
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/status-dataset-context.t.cpp b/tests/unit-tests/mgmt/status-dataset-context.t.cpp
new file mode 100644
index 0000000..40c5bf6
--- /dev/null
+++ b/tests/unit-tests/mgmt/status-dataset-context.t.cpp
@@ -0,0 +1,256 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mgmt/status-dataset-context.hpp"
+#include "boost-test.hpp"
+#include "unit-tests/make-interest-data.hpp"
+
+namespace ndn {
+namespace mgmt {
+namespace tests {
+
+class StatusDatasetContextFixture
+{
+public:
+  StatusDatasetContextFixture()
+    : interest(util::makeInterest("/test/context/interest"))
+    , contentBlock(makeStringBlock(tlv::Content, "/test/data/content"))
+    , context(*interest, bind(&StatusDatasetContextFixture::sendData, this, _1, _2, _3))
+  {
+  }
+
+  void
+  sendData(const Name& dataName, const Block& content, const MetaInfo& info)
+  {
+    sentData.push_back(Data(dataName).setContent(content).setMetaInfo(info));
+  }
+
+  Block
+  concatenate()
+  {
+    EncodingBuffer encoder;
+    size_t valueLength = 0;
+    for (auto data : sentData) {
+       valueLength += encoder.appendByteArray(data.getContent().value(),
+                                              data.getContent().value_size());
+    }
+    encoder.prependVarNumber(valueLength);
+    encoder.prependVarNumber(tlv::Content);
+    return encoder.block();
+  }
+
+public:
+  std::vector<Data> sentData;
+  shared_ptr<Interest> interest;
+  Block contentBlock;
+  mgmt::StatusDatasetContext context;
+};
+
+BOOST_FIXTURE_TEST_SUITE(MgmtStatusDatasetContext, StatusDatasetContextFixture)
+
+BOOST_AUTO_TEST_CASE(GetPrefix)
+{
+  Name dataName = context.getPrefix();
+  BOOST_CHECK(dataName[-1].isVersion());
+  BOOST_CHECK(dataName.getPrefix(-1) == interest->getName());
+}
+
+BOOST_AUTO_TEST_SUITE(SetPrefix)
+
+BOOST_AUTO_TEST_CASE(Valid)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  BOOST_CHECK_NO_THROW(context.setPrefix(validPrefix));
+  BOOST_CHECK(context.getPrefix()[-1].isVersion());
+  BOOST_CHECK(context.getPrefix().getPrefix(-1) == validPrefix);
+}
+
+BOOST_AUTO_TEST_CASE(Invalid)
+{
+  Name invalidPrefix = Name(interest->getName()).getPrefix(-1).append("/invalid");
+  BOOST_CHECK_THROW(context.setPrefix(invalidPrefix), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(ValidWithAppendCalled)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  context.append(contentBlock);
+  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(ValidWithEndCalled)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  context.end();
+  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(ValidWithRejectCalled)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  context.reject();
+  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // SetPrefix
+
+BOOST_AUTO_TEST_CASE(Expiry)
+{
+  // getExpiry & setExpiry
+  auto period = time::milliseconds(9527);
+  BOOST_CHECK_EQUAL(context.getExpiry(), time::milliseconds(1000));
+  BOOST_CHECK_EQUAL(context.setExpiry(period).getExpiry(), period);
+}
+
+BOOST_AUTO_TEST_CASE(Respond)
+{
+  BOOST_CHECK_NO_THROW(context.append(contentBlock));
+  BOOST_CHECK(sentData.empty()); // does not call end yet
+
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_EQUAL(sentData.size(), 1);
+  BOOST_CHECK_EQUAL(sentData[0].getName()[-1].toSegment(), 0);
+  BOOST_CHECK_EQUAL(sentData[0].getName().getPrefix(-1), context.getPrefix());
+  BOOST_CHECK_EQUAL(sentData[0].getFinalBlockId().toSegment(), 0);
+  BOOST_CHECK(sentData[0].getContent().blockFromValue() == contentBlock);
+}
+
+BOOST_AUTO_TEST_CASE(RespondLarge)
+{
+  static Block largeBlock = [] () -> Block {
+    EncodingBuffer encoder;
+    size_t maxBlockSize = MAX_NDN_PACKET_SIZE >> 1;
+    for (size_t i = 0; i < maxBlockSize; ++i) {
+      encoder.prependByte(1);
+    }
+    encoder.prependVarNumber(maxBlockSize);
+    encoder.prependVarNumber(tlv::Content);
+    return encoder.block();
+  }();
+
+  BOOST_CHECK_NO_THROW(context.append(largeBlock));
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_EQUAL(sentData.size(), 2);
+  BOOST_CHECK_EQUAL(sentData[0].getName()[-1].toSegment(), 0);
+  BOOST_CHECK_EQUAL(sentData[0].getName().getPrefix(-1), context.getPrefix());
+  BOOST_CHECK_EQUAL(sentData[1].getName()[-1].toSegment(), 1);
+  BOOST_CHECK_EQUAL(sentData[1].getName().getPrefix(-1), context.getPrefix());
+  BOOST_CHECK_EQUAL(sentData[1].getFinalBlockId().toSegment(), 1);
+
+  auto contentLargeBlock = concatenate();
+  BOOST_CHECK_NO_THROW(contentLargeBlock.parse());
+  BOOST_CHECK_EQUAL(contentLargeBlock.elements().size(), 1);
+  BOOST_CHECK(contentLargeBlock.elements()[0] == largeBlock);
+}
+
+BOOST_AUTO_TEST_CASE(ResponseMultipleSmall)
+{
+  size_t nBlocks = 100;
+  for (size_t i = 0 ; i < nBlocks ; i ++) {
+    BOOST_CHECK_NO_THROW(context.append(contentBlock));
+  }
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_EQUAL(sentData.size(), 1);
+  BOOST_CHECK_EQUAL(sentData[0].getName()[-1].toSegment(), 0);
+  BOOST_CHECK_EQUAL(sentData[0].getName().getPrefix(-1), context.getPrefix());
+  BOOST_CHECK_EQUAL(sentData[0].getFinalBlockId().toSegment(), 0);
+
+  auto contentMultiBlocks = concatenate();
+  BOOST_CHECK_NO_THROW(contentMultiBlocks.parse());
+  BOOST_CHECK_EQUAL(contentMultiBlocks.elements().size(), nBlocks);
+  for (auto&& element : contentMultiBlocks.elements()) {
+    BOOST_CHECK(element == contentBlock);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(Reject)
+{
+  BOOST_CHECK_NO_THROW(context.reject());
+  BOOST_CHECK_EQUAL(sentData.size(), 1);
+  BOOST_CHECK(sentData[0].getContentType() == tlv::ContentType_Nack);
+  BOOST_CHECK_EQUAL(ControlResponse(sentData[0].getContent().blockFromValue()).getCode(), 400);
+}
+
+BOOST_AUTO_TEST_SUITE(AbnormalState)
+
+BOOST_AUTO_TEST_CASE(AppendReject)
+{
+  mgmt::StatusDatasetContext context(Interest("/abnormal-state"), bind([]{}));
+  BOOST_CHECK_NO_THROW(context.append(Block("\x82\x01\x02", 3)));
+  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(AppendEndReject)
+{
+  mgmt::StatusDatasetContext context(Interest("/abnormal-state"), bind([]{}));
+  BOOST_CHECK_NO_THROW(context.append(Block("\x82\x01\x02", 3)));
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(EndAppend)
+{
+  mgmt::StatusDatasetContext context (Interest("/abnormal-state"), bind([]{}));
+  BOOST_CHECK_NO_THROW(context.end());
+  // end, append -> error
+  BOOST_CHECK_THROW(context.append(Block("\x82\x01\x02", 3)), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(EndEnd)
+{
+  mgmt::StatusDatasetContext context(Interest("/abnormal-state"), bind([]{}));
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_THROW(context.end(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(EndReject)
+{
+  mgmt::StatusDatasetContext context(Interest("/abnormal-state"), bind([]{}));
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(RejectAppend)
+{
+  mgmt::StatusDatasetContext context(Interest("/abnormal-state"), bind([]{}));
+  BOOST_CHECK_NO_THROW(context.reject());
+  BOOST_CHECK_THROW(context.append(Block("\x82\x01\x02", 3)), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(RejectEnd)
+{
+  mgmt::StatusDatasetContext context(Interest("/abnormal-state"), bind([]{}));
+  BOOST_CHECK_NO_THROW(context.reject());
+  BOOST_CHECK_THROW(context.end(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // AbnormalState
+
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace mgmt
+} // namespace ndn