mgmt: add face/list support and general purpose data segementer

refs: #1245

Change-Id: I3769941022b7ed6e2a8d39622032e4e16909f645
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index 701bddd..bf31a10 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -9,7 +9,6 @@
 #include "core/face-uri.hpp"
 #include "core/network-interface.hpp"
 #include "fw/face-table.hpp"
-
 #include "face/tcp-factory.hpp"
 #include "face/udp-factory.hpp"
 
@@ -25,7 +24,7 @@
 
 NFD_LOG_INIT("FaceManager");
 
-const Name FaceManager::COMMAND_PREFIX = "/localhost/nfd/faces";
+const Name FaceManager::COMMAND_PREFIX("/localhost/nfd/faces");
 
 const size_t FaceManager::COMMAND_UNSIGNED_NCOMPS =
   FaceManager::COMMAND_PREFIX.size() +
@@ -36,28 +35,44 @@
   FaceManager::COMMAND_UNSIGNED_NCOMPS +
   4; // (timestamp, nonce, signed info tlv, signature tlv)
 
-const FaceManager::VerbAndProcessor FaceManager::COMMAND_VERBS[] =
+const FaceManager::SignedVerbAndProcessor FaceManager::SIGNED_COMMAND_VERBS[] =
   {
-    VerbAndProcessor(
+    SignedVerbAndProcessor(
                      Name::Component("create"),
                      &FaceManager::createFace
                      ),
 
-    VerbAndProcessor(
+    SignedVerbAndProcessor(
                      Name::Component("destroy"),
                      &FaceManager::destroyFace
                      ),
   };
 
+const FaceManager::UnsignedVerbAndProcessor FaceManager::UNSIGNED_COMMAND_VERBS[] =
+  {
+    UnsignedVerbAndProcessor(
+                             Name::Component("list"),
+                             &FaceManager::listFaces
+                             ),
+  };
+
+const Name FaceManager::LIST_COMMAND_PREFIX("/localhost/nfd/faces/list");
+const size_t FaceManager::LIST_COMMAND_NCOMPS = LIST_COMMAND_PREFIX.size();
+
 
 
 FaceManager::FaceManager(FaceTable& faceTable,
                          shared_ptr<InternalFace> face)
   : ManagerBase(face, FACE_MANAGER_PRIVILEGE)
   , m_faceTable(faceTable)
-  , m_verbDispatch(COMMAND_VERBS,
-                   COMMAND_VERBS +
-                   (sizeof(COMMAND_VERBS) / sizeof(VerbAndProcessor)))
+  , m_statusPublisher(m_faceTable, m_face, LIST_COMMAND_PREFIX)
+  , m_signedVerbDispatch(SIGNED_COMMAND_VERBS,
+                   SIGNED_COMMAND_VERBS +
+                   (sizeof(SIGNED_COMMAND_VERBS) / sizeof(SignedVerbAndProcessor)))
+  , m_unsignedVerbDispatch(UNSIGNED_COMMAND_VERBS,
+                   UNSIGNED_COMMAND_VERBS +
+                   (sizeof(UNSIGNED_COMMAND_VERBS) / sizeof(UnsignedVerbAndProcessor)))
+
 {
   face->setInterestFilter("/localhost/nfd/faces",
                           bind(&FaceManager::onFaceRequest, this, _2));
@@ -487,26 +502,32 @@
 {
   const Name& command = request.getName();
   const size_t commandNComps = command.size();
+  const Name::Component& verb = command.get(COMMAND_PREFIX.size());
 
-  if (COMMAND_UNSIGNED_NCOMPS <= commandNComps &&
+  UnsignedVerbDispatchTable::const_iterator unsignedVerbProcessor = m_unsignedVerbDispatch.find(verb);
+  if (unsignedVerbProcessor != m_unsignedVerbDispatch.end())
+    {
+      NFD_LOG_INFO("command result: processing verb: " << verb);
+      (unsignedVerbProcessor->second)(this, boost::cref(request));
+    }
+  else if (COMMAND_UNSIGNED_NCOMPS <= commandNComps &&
       commandNComps < COMMAND_SIGNED_NCOMPS)
     {
       NFD_LOG_INFO("command result: unsigned verb: " << command);
       sendResponse(command, 401, "Signature required");
-
-      return;
     }
   else if (commandNComps < COMMAND_SIGNED_NCOMPS ||
            !COMMAND_PREFIX.isPrefixOf(command))
     {
       NFD_LOG_INFO("command result: malformed");
       sendResponse(command, 400, "Malformed command");
-      return;
     }
-
-  validate(request,
-           bind(&FaceManager::onValidatedFaceRequest, this, _1),
-           bind(&ManagerBase::onCommandValidationFailed, this, _1, _2));
+  else
+    {
+      validate(request,
+               bind(&FaceManager::onValidatedFaceRequest, this, _1),
+               bind(&ManagerBase::onCommandValidationFailed, this, _1, _2));
+    }
 }
 
 void
@@ -515,8 +536,8 @@
   const Name& command = request->getName();
   const Name::Component& verb = command.get(COMMAND_PREFIX.size());
 
-  VerbDispatchTable::const_iterator verbProcessor = m_verbDispatch.find (verb);
-  if (verbProcessor != m_verbDispatch.end())
+  SignedVerbDispatchTable::const_iterator signedVerbProcessor = m_signedVerbDispatch.find(verb);
+  if (signedVerbProcessor != m_signedVerbDispatch.end())
     {
       ndn::nfd::FaceManagementOptions options;
       if (!extractOptions(*request, options))
@@ -526,7 +547,7 @@
         }
 
       NFD_LOG_INFO("command result: processing verb: " << verb);
-      (verbProcessor->second)(this, command, options);
+      (signedVerbProcessor->second)(this, command, options);
     }
   else
     {
@@ -617,6 +638,21 @@
   sendResponse(requestName, 200, "Success");
 }
 
+void
+FaceManager::listFaces(const Interest& request)
+{
+  const Name& command = request.getName();
+  const size_t commandNComps = command.size();
 
+  if (commandNComps < LIST_COMMAND_NCOMPS ||
+      !LIST_COMMAND_PREFIX.isPrefixOf(command))
+    {
+      NFD_LOG_INFO("command result: malformed");
+      sendResponse(command, 400, "Malformed command");
+      return;
+    }
+
+  m_statusPublisher.publish();
+}
 
 } // namespace nfd
diff --git a/daemon/mgmt/face-manager.hpp b/daemon/mgmt/face-manager.hpp
index 55e4df4..4139152 100644
--- a/daemon/mgmt/face-manager.hpp
+++ b/daemon/mgmt/face-manager.hpp
@@ -12,6 +12,8 @@
 #include "mgmt/app-face.hpp"
 #include "mgmt/manager-base.hpp"
 #include "mgmt/config-file.hpp"
+#include "mgmt/face-status-publisher.hpp"
+#include "fw/face-table.hpp"
 
 #include <ndn-cpp-dev/management/nfd-face-management-options.hpp>
 #include <ndn-cpp-dev/management/nfd-control-response.hpp>
@@ -19,15 +21,16 @@
 namespace nfd {
 
 const std::string FACE_MANAGER_PRIVILEGE = "faces";
-class FaceTable;
+
 class ProtocolFactory;
 class NetworkInterfaceInfo;
 
 class FaceManager : public ManagerBase
 {
 public:
-  struct Error : public ManagerBase::Error
+  class Error : public ManagerBase::Error
   {
+  public:
     Error(const std::string& what) : ManagerBase::Error(what) {}
   };
 
@@ -49,6 +52,10 @@
   void
   onFaceRequest(const Interest& request);
 
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  void
+  listFaces(const Interest& request);
+
 PROTECTED_WITH_TESTS_ELSE_PRIVATE:
 
   void
@@ -107,28 +114,39 @@
   typedef std::map< std::string/*protocol*/, shared_ptr<ProtocolFactory> > FactoryMap;
   FactoryMap m_factories;
   FaceTable& m_faceTable;
+  FaceStatusPublisher m_statusPublisher;
 
   typedef function<void(FaceManager*,
                         const Name&,
-                        ndn::nfd::FaceManagementOptions&)> VerbProcessor;
+                        ndn::nfd::FaceManagementOptions&)> SignedVerbProcessor;
 
-  typedef std::map<Name::Component, VerbProcessor> VerbDispatchTable;
+  typedef std::map<Name::Component, SignedVerbProcessor> SignedVerbDispatchTable;
+  typedef std::pair<Name::Component, SignedVerbProcessor> SignedVerbAndProcessor;
 
-  typedef std::pair<Name::Component, VerbProcessor> VerbAndProcessor;
+  typedef function<void(FaceManager*, const Interest&)> UnsignedVerbProcessor;
 
-  const VerbDispatchTable m_verbDispatch;
+  typedef std::map<Name::Component, UnsignedVerbProcessor> UnsignedVerbDispatchTable;
+  typedef std::pair<Name::Component, UnsignedVerbProcessor> UnsignedVerbAndProcessor;
 
-  static const Name COMMAND_PREFIX; // /localhost/nfd/fib
 
-  // number of components in an invalid, but not malformed, unsigned command.
-  // (/localhost/nfd/fib + verb + options) = 5
+  const SignedVerbDispatchTable m_signedVerbDispatch;
+  const UnsignedVerbDispatchTable m_unsignedVerbDispatch;
+
+  static const Name COMMAND_PREFIX; // /localhost/nfd/faces
+
+  // number of components in an invalid signed command (i.e. should be signed, but isn't)
+  // (/localhost/nfd/faces + verb + options) = 5
   static const size_t COMMAND_UNSIGNED_NCOMPS;
 
-  // number of components in a valid signed Interest.
+  // number of components in a valid signed command.
   // (see UNSIGNED_NCOMPS), 9 with signed Interest support.
   static const size_t COMMAND_SIGNED_NCOMPS;
 
-  static const VerbAndProcessor COMMAND_VERBS[];
+  static const SignedVerbAndProcessor SIGNED_COMMAND_VERBS[];
+  static const UnsignedVerbAndProcessor UNSIGNED_COMMAND_VERBS[];
+
+  static const Name LIST_COMMAND_PREFIX;
+  static const size_t LIST_COMMAND_NCOMPS;
 };
 
 inline bool
diff --git a/daemon/mgmt/face-status-publisher.cpp b/daemon/mgmt/face-status-publisher.cpp
new file mode 100644
index 0000000..2cf2603
--- /dev/null
+++ b/daemon/mgmt/face-status-publisher.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+#include "face-status-publisher.hpp"
+
+namespace nfd {
+
+NFD_LOG_INIT("FaceStatusPublisher");
+
+
+FaceStatusPublisher::FaceStatusPublisher(const FaceTable& faceTable,
+                                         shared_ptr<AppFace> face,
+                                         const Name& prefix)
+  : SegmentPublisher(face, prefix)
+  , m_faceTable(faceTable)
+{
+
+}
+
+
+FaceStatusPublisher::~FaceStatusPublisher()
+{
+
+}
+
+size_t
+FaceStatusPublisher::generate(ndn::EncodingBuffer& outBuffer)
+{
+  size_t totalLength = 0;
+
+  for (FaceTable::const_iterator i = m_faceTable.begin();
+       i != m_faceTable.end();
+       ++i)
+    {
+      const shared_ptr<Face>& face = *i;
+      const FaceCounters& counters = face->getCounters();
+      const std::string uri = face->getUri().toString();
+
+      size_t statusLength = 0;
+
+      statusLength += prependNonNegativeIntegerBlock(outBuffer,
+                                                     ndn::tlv::nfd::TotalOutgoingDataCounter,
+                                                     counters.getOutData());
+
+      statusLength += prependNonNegativeIntegerBlock(outBuffer,
+                                                     ndn::tlv::nfd::TotalOutgoingInterestCounter,
+                                                     counters.getOutInterest());
+
+      statusLength += prependNonNegativeIntegerBlock(outBuffer,
+                                                     ndn::tlv::nfd::TotalIncomingDataCounter,
+                                                     counters.getInData());
+
+      statusLength += prependNonNegativeIntegerBlock(outBuffer,
+                                                     ndn::tlv::nfd::TotalIncomingInterestCounter,
+                                                     counters.getInInterest());
+
+      statusLength += prependByteArrayBlock(outBuffer,
+                                            ndn::tlv::nfd::Uri,
+                                            reinterpret_cast<const uint8_t*>(uri.c_str()),
+                                            uri.size());
+
+      statusLength += prependNonNegativeIntegerBlock(outBuffer,
+                                                     ndn::tlv::nfd::FaceId,
+                                                     face->getId());
+
+      statusLength += outBuffer.prependVarNumber(statusLength);
+      statusLength += outBuffer.prependVarNumber(ndn::tlv::nfd::FaceStatus);
+
+      totalLength += statusLength;
+    }
+  return totalLength;
+}
+
+} // namespace nfd
diff --git a/daemon/mgmt/face-status-publisher.hpp b/daemon/mgmt/face-status-publisher.hpp
new file mode 100644
index 0000000..2006462
--- /dev/null
+++ b/daemon/mgmt/face-status-publisher.hpp
@@ -0,0 +1,36 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_MGMT_FACE_STATUS_PUBLISHER_HPP
+#define NFD_MGMT_FACE_STATUS_PUBLISHER_HPP
+
+#include "fw/face-table.hpp"
+#include "mgmt/segment-publisher.hpp"
+
+namespace nfd {
+
+class FaceStatusPublisher : public SegmentPublisher
+{
+public:
+  FaceStatusPublisher(const FaceTable& faceTable,
+                      shared_ptr<AppFace> face,
+                      const Name& prefix);
+
+  virtual
+  ~FaceStatusPublisher();
+
+protected:
+
+  virtual size_t
+  generate(ndn::EncodingBuffer& outBuffer);
+
+private:
+  const FaceTable& m_faceTable;
+};
+
+} // namespace nfd
+
+#endif // NFD_MGMT_FACE_STATUS_PUBLISHER_HPP
diff --git a/daemon/mgmt/segment-publisher.cpp b/daemon/mgmt/segment-publisher.cpp
new file mode 100644
index 0000000..bd7d971
--- /dev/null
+++ b/daemon/mgmt/segment-publisher.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "segment-publisher.hpp"
+
+#include "common.hpp"
+#include "face/face.hpp"
+
+#include <ndn-cpp-dev/util/time.hpp>
+
+namespace nfd {
+
+NFD_LOG_INIT("SegmentPublisher");
+
+SegmentPublisher::SegmentPublisher(shared_ptr<AppFace> face,
+                                   const Name& prefix)
+  : m_face(face)
+  , m_prefix(prefix)
+{
+
+}
+
+
+SegmentPublisher::~SegmentPublisher()
+{
+
+}
+
+void
+SegmentPublisher::publish()
+{
+  Name segmentPrefix(m_prefix);
+  segmentPrefix.appendSegment(ndn::ndn_getNowMilliseconds());
+
+  static const size_t  MAX_SEGMENT_SIZE = MAX_NDN_PACKET_SIZE >> 1;
+
+  ndn::EncodingBuffer buffer;
+
+  generate(buffer);
+
+  const uint8_t* rawBuffer = buffer.buf();
+  const uint8_t* segmentBegin = rawBuffer;
+  const uint8_t* end = rawBuffer + buffer.size();
+
+  uint64_t segmentNo = 0;
+  while (segmentBegin < end)
+    {
+      const uint8_t* segmentEnd = segmentBegin + MAX_SEGMENT_SIZE;
+      if (segmentEnd > end)
+        {
+          segmentEnd = end;
+        }
+
+      Name segmentName(segmentPrefix);
+      segmentName.appendSegment(segmentNo);
+
+      shared_ptr<Data> data(make_shared<Data>(segmentName));
+      data->setContent(segmentBegin, segmentEnd - segmentBegin);
+
+      segmentBegin = segmentEnd;
+      if (segmentBegin >= end)
+        {
+          NFD_LOG_DEBUG("final block is " << segmentNo);
+          data->setFinalBlockId(segmentName[-1]);
+        }
+
+      NFD_LOG_DEBUG("publishing segment #" << segmentNo);
+      publishSegment(data);
+      segmentNo++;
+    }
+}
+
+void
+SegmentPublisher::publishSegment(shared_ptr<Data>& data)
+{
+  m_face->sign(*data);
+  m_face->put(*data);
+}
+
+} // namespace nfd
diff --git a/daemon/mgmt/segment-publisher.hpp b/daemon/mgmt/segment-publisher.hpp
new file mode 100644
index 0000000..4b5dc4f
--- /dev/null
+++ b/daemon/mgmt/segment-publisher.hpp
@@ -0,0 +1,47 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_MGMT_SEGMENT_PUBLISHER_HPP
+#define NFD_MGMT_SEGMENT_PUBLISHER_HPP
+
+#include "common.hpp"
+#include "mgmt/app-face.hpp"
+
+#include <ndn-cpp-dev/encoding/encoding-buffer.hpp>
+
+namespace nfd {
+
+class AppFace;
+
+class SegmentPublisher : noncopyable
+{
+public:
+  SegmentPublisher(shared_ptr<AppFace> face,
+                   const Name& prefix);
+
+  virtual
+  ~SegmentPublisher();
+
+  void
+  publish();
+
+protected:
+
+  virtual size_t
+  generate(ndn::EncodingBuffer& outBuffer) =0;
+
+private:
+  void
+  publishSegment(shared_ptr<Data>& data);
+
+private:
+  shared_ptr<AppFace> m_face;
+  const Name m_prefix;
+};
+
+} // namespace nfd
+
+#endif // NFD_MGMT_SEGMENT_PUBLISHER_HPP
diff --git a/tests/mgmt/face-manager.cpp b/tests/mgmt/face-manager.cpp
index 191de04..80e14d7 100644
--- a/tests/mgmt/face-manager.cpp
+++ b/tests/mgmt/face-manager.cpp
@@ -6,6 +6,7 @@
 
 #include "mgmt/face-manager.hpp"
 #include "mgmt/internal-face.hpp"
+#include "mgmt/face-status-publisher.hpp"
 #include "face/face.hpp"
 #include "../face/dummy-face.hpp"
 #include "fw/face-table.hpp"
@@ -14,24 +15,27 @@
 #include "common.hpp"
 #include "tests/test-common.hpp"
 #include "validation-common.hpp"
+#include "face-status-publisher-common.hpp"
+
+#include <ndn-cpp-dev/encoding/tlv.hpp>
 
 namespace nfd {
 namespace tests {
 
 NFD_LOG_INIT("FaceManagerTest");
 
-class TestDummyFace : public DummyFace
+class FaceManagerTestFace : public DummyFace
 {
 public:
 
-  TestDummyFace()
+  FaceManagerTestFace()
     : m_closeFired(false)
   {
 
   }
 
   virtual
-  ~TestDummyFace()
+  ~FaceManagerTestFace()
   {
 
   }
@@ -60,7 +64,7 @@
       m_addFired(false),
       m_removeFired(false),
       m_getFired(false),
-      m_dummy(make_shared<TestDummyFace>())
+      m_dummy(make_shared<FaceManagerTestFace>())
   {
 
   }
@@ -116,7 +120,7 @@
     m_getFired = false;
   }
 
-  shared_ptr<TestDummyFace>&
+  shared_ptr<FaceManagerTestFace>&
   getDummyFace()
   {
     return m_dummy;
@@ -126,7 +130,7 @@
   bool m_addFired;
   bool m_removeFired;
   mutable bool m_getFired;
-  shared_ptr<TestDummyFace> m_dummy;
+  shared_ptr<FaceManagerTestFace> m_dummy;
 };
 
 
@@ -257,18 +261,18 @@
     m_manager.setConfigFile(m_config);
   }
 
-  void
-  parseConfig(const std::string configuration, bool isDryRun)
-  {
-    m_config.parse(configuration, isDryRun, "dummy-config");
-  }
-
   virtual
   ~FaceManagerFixture()
   {
 
   }
 
+  void
+  parseConfig(const std::string configuration, bool isDryRun)
+  {
+    m_config.parse(configuration, isDryRun, "dummy-config");
+  }
+
   FaceManager&
   getManager()
   {
@@ -1022,8 +1026,72 @@
   BOOST_CHECK(TestFaceTableFixture::m_faceTable.getDummyFace()->didCloseFire());
 }
 
+class FaceListFixture : public FaceStatusPublisherFixture
+{
+public:
+  FaceListFixture()
+    : m_manager(m_table, m_face)
+  {
+
+  }
+
+  virtual
+  ~FaceListFixture()
+  {
+
+  }
+
+protected:
+  FaceManager m_manager;
+};
+
+BOOST_FIXTURE_TEST_CASE(TestFaceList, FaceListFixture)
+
+{
+  Name commandName("/localhost/nfd/faces/list");
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+
+  // MAX_SEGMENT_SIZE == 4400, FaceStatus size with filler counters is 55
+  // 55 divides 4400 (== 80), so only use 79 FaceStatuses and then two smaller ones
+  // to force a FaceStatus to span Data packets
+  for (int i = 0; i < 79; i++)
+    {
+      shared_ptr<TestCountersFace> dummy(make_shared<TestCountersFace>());
+
+      uint64_t filler = std::numeric_limits<uint64_t>::max() - 1;
+      dummy->setCounters(filler, filler, filler, filler);
+
+      m_referenceFaces.push_front(dummy);
+
+      add(dummy);
+    }
+
+  for (int i = 0; i < 2; i++)
+    {
+      shared_ptr<TestCountersFace> dummy(make_shared<TestCountersFace>());
+      uint64_t filler = std::numeric_limits<uint32_t>::max() - 1;
+      dummy->setCounters(filler, filler, filler, filler);
+
+      m_referenceFaces.push_front(dummy);
+
+      add(dummy);
+    }
+
+  ndn::EncodingBuffer buffer;
+
+  m_face->onReceiveData +=
+    bind(&FaceStatusPublisherFixture::decodeFaceStatusBlock, this, _1);
+
+  m_manager.listFaces(*command);
+  BOOST_REQUIRE(m_finished);
+}
+
+
+
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests
 } // namespace nfd
 
+
diff --git a/tests/mgmt/face-status-publisher-common.hpp b/tests/mgmt/face-status-publisher-common.hpp
new file mode 100644
index 0000000..e6ff706
--- /dev/null
+++ b/tests/mgmt/face-status-publisher-common.hpp
@@ -0,0 +1,219 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_TESTS_MGMT_FACE_STATUS_PUBLISHER_COMMON_HPP
+#define NFD_TESTS_MGMT_FACE_STATUS_PUBLISHER_COMMON_HPP
+
+#include "mgmt/face-status-publisher.hpp"
+#include "mgmt/app-face.hpp"
+#include "mgmt/internal-face.hpp"
+#include "fw/forwarder.hpp"
+#include "../face/dummy-face.hpp"
+
+#include "tests/test-common.hpp"
+
+#include <ndn-cpp-dev/encoding/tlv.hpp>
+
+namespace nfd {
+namespace tests {
+
+class TestCountersFace : public DummyFace
+{
+public:
+
+  TestCountersFace()
+  {
+  }
+
+  virtual
+  ~TestCountersFace()
+  {
+  }
+
+  void
+  setCounters(FaceCounter inInterest,
+              FaceCounter inData,
+              FaceCounter outInterest,
+              FaceCounter outData)
+  {
+    FaceCounters& counters = getMutableCounters();
+    counters.getInInterest() = inInterest;
+    counters.getInData() = inData;
+    counters.getOutInterest() = outInterest;
+    counters.getOutData() = outData;
+  }
+
+
+};
+
+static inline uint64_t
+readNonNegativeIntegerType(const Block& block,
+                           uint32_t type)
+{
+  if (block.type() == type)
+    {
+      return readNonNegativeInteger(block);
+    }
+  std::stringstream error;
+  error << "expected type " << type << " got " << block.type();
+  throw ndn::Tlv::Error(error.str());
+}
+
+static inline uint64_t
+checkedReadNonNegativeIntegerType(Block::element_const_iterator& i,
+                                  Block::element_const_iterator end,
+                                  uint32_t type)
+{
+  if (i != end)
+    {
+      const Block& block = *i;
+      ++i;
+      return readNonNegativeIntegerType(block, type);
+    }
+  throw ndn::Tlv::Error("Unexpected end of FaceStatus");
+}
+
+class FaceStatusPublisherFixture : public BaseFixture
+{
+public:
+
+  FaceStatusPublisherFixture()
+    : m_table(m_forwarder)
+    , m_face(make_shared<InternalFace>())
+    , m_publisher(m_table, m_face, "/localhost/nfd/FaceStatusPublisherFixture")
+    , m_finished(false)
+  {
+
+  }
+
+  virtual
+  ~FaceStatusPublisherFixture()
+  {
+
+  }
+
+  void
+  add(shared_ptr<Face> face)
+  {
+    m_table.add(face);
+  }
+
+  void
+  validateFaceStatus(const Block& status, const shared_ptr<Face>& reference)
+  {
+    const FaceCounters& counters = reference->getCounters();
+
+    FaceId faceId = INVALID_FACEID;
+    std::string uri;
+    FaceCounter inInterest = 0;
+    FaceCounter inData = 0;
+    FaceCounter outInterest = 0;
+    FaceCounter outData = 0;
+
+    status.parse();
+
+    for (Block::element_const_iterator i = status.elements_begin();
+         i != status.elements_end();
+         ++i)
+      {
+        // parse a full set of FaceStatus sub-blocks
+        faceId =
+          checkedReadNonNegativeIntegerType(i,
+                                            status.elements_end(),
+                                            ndn::tlv::nfd::FaceId);
+
+        BOOST_REQUIRE_EQUAL(faceId, reference->getId());
+
+        BOOST_REQUIRE(i->type() == ndn::tlv::nfd::Uri);
+
+        uri.append(reinterpret_cast<const char*>(i->value()), i->value_size());
+        ++i;
+
+        BOOST_REQUIRE(i != status.elements_end());
+
+        BOOST_REQUIRE_EQUAL(uri, reference->getUri().toString());
+
+        inInterest =
+          checkedReadNonNegativeIntegerType(i,
+                                            status.elements_end(),
+                                            ndn::tlv::nfd::TotalIncomingInterestCounter);
+
+        BOOST_REQUIRE_EQUAL(inInterest, counters.getInInterest());
+
+        inData =
+          checkedReadNonNegativeIntegerType(i,
+                                            status.elements_end(),
+                                            ndn::tlv::nfd::TotalIncomingDataCounter);
+
+        BOOST_REQUIRE_EQUAL(inData, counters.getInData());
+
+        outInterest =
+          checkedReadNonNegativeIntegerType(i,
+                                            status.elements_end(),
+                                            ndn::tlv::nfd::TotalOutgoingInterestCounter);
+        BOOST_REQUIRE_EQUAL(outInterest, counters.getOutInterest());
+
+        outData =
+          readNonNegativeIntegerType(*i,
+                                     ndn::tlv::nfd::TotalOutgoingDataCounter);
+
+        BOOST_REQUIRE_EQUAL(outData, counters.getOutData());
+      }
+  }
+
+  void
+  decodeFaceStatusBlock(const Data& data)
+  {
+    Block payload = data.getContent();
+
+    m_buffer.appendByteArray(payload.value(), payload.value_size());
+
+    uint64_t segmentNo = data.getName()[-1].toSegment();
+    if (data.getFinalBlockId() != data.getName()[-1])
+      {
+        return;
+      }
+
+    // wrap the Face Statuses in a single Content TLV for easy parsing
+    m_buffer.prependVarNumber(m_buffer.size());
+    m_buffer.prependVarNumber(ndn::Tlv::Content);
+
+    ndn::Block parser(m_buffer.buf(), m_buffer.size());
+    parser.parse();
+
+    BOOST_REQUIRE_EQUAL(parser.elements_size(), m_referenceFaces.size());
+
+    std::list<shared_ptr<Face> >::const_iterator iReference = m_referenceFaces.begin();
+    for (Block::element_const_iterator i = parser.elements_begin();
+         i != parser.elements_end();
+         ++i)
+      {
+        if (i->type() != ndn::tlv::nfd::FaceStatus)
+          {
+            BOOST_FAIL("expected face status, got type #" << i->type());
+          }
+        validateFaceStatus(*i, *iReference);
+        ++iReference;
+      }
+    m_finished = true;
+  }
+
+protected:
+  Forwarder m_forwarder;
+  FaceTable m_table;
+  shared_ptr<InternalFace> m_face;
+  FaceStatusPublisher m_publisher;
+  ndn::EncodingBuffer m_buffer;
+  std::list<shared_ptr<Face> > m_referenceFaces;
+
+protected:
+  bool m_finished;
+};
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_MGMT_FACE_STATUS_PUBLISHER_COMMON_HPP
diff --git a/tests/mgmt/face-status-publisher.cpp b/tests/mgmt/face-status-publisher.cpp
new file mode 100644
index 0000000..eb9946c
--- /dev/null
+++ b/tests/mgmt/face-status-publisher.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "face-status-publisher-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("FaceStatusPublisherTest");
+
+BOOST_FIXTURE_TEST_SUITE(MgmtFaceManager, FaceStatusPublisherFixture)
+
+BOOST_AUTO_TEST_CASE(TestFaceStatusPublisher)
+{
+  Name commandName("/localhost/nfd/faces/list");
+  shared_ptr<Interest> command(make_shared<Interest>(commandName));
+
+  // MAX_SEGMENT_SIZE == 4400, FaceStatus size with filler counters is 55
+  // 55 divides 4400 (== 80), so only use 79 FaceStatuses and then two smaller ones
+  // to force a FaceStatus to span Data packets
+  for (int i = 0; i < 79; i++)
+    {
+      shared_ptr<TestCountersFace> dummy(make_shared<TestCountersFace>());
+
+      uint64_t filler = std::numeric_limits<uint64_t>::max() - 1;
+      dummy->setCounters(filler, filler, filler, filler);
+
+      m_referenceFaces.push_front(dummy);
+
+      add(dummy);
+    }
+
+  for (int i = 0; i < 2; i++)
+    {
+      shared_ptr<TestCountersFace> dummy(make_shared<TestCountersFace>());
+      uint64_t filler = std::numeric_limits<uint32_t>::max() - 1;
+      dummy->setCounters(filler, filler, filler, filler);
+
+      m_referenceFaces.push_front(dummy);
+
+      add(dummy);
+    }
+
+  ndn::EncodingBuffer buffer;
+
+  m_face->onReceiveData +=
+    bind(&FaceStatusPublisherFixture::decodeFaceStatusBlock, this, _1);
+
+  m_publisher.publish();
+  BOOST_REQUIRE(m_finished);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/mgmt/segment-publisher.cpp b/tests/mgmt/segment-publisher.cpp
new file mode 100644
index 0000000..0d65ee6
--- /dev/null
+++ b/tests/mgmt/segment-publisher.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "mgmt/segment-publisher.hpp"
+#include "mgmt/internal-face.hpp"
+#include "mgmt/app-face.hpp"
+
+#include "tests/test-common.hpp"
+#include <ndn-cpp-dev/encoding/tlv.hpp>
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("SegmentPublisherTest");
+
+class TestSegmentPublisher : public SegmentPublisher
+{
+public:
+  TestSegmentPublisher(shared_ptr<AppFace> face,
+                       const Name& prefix,
+                       const uint64_t limit=10000)
+    : SegmentPublisher(face, prefix)
+    , m_limit((limit == 0)?(1):(limit))
+  {
+
+  }
+
+  virtual
+  ~TestSegmentPublisher()
+  {
+
+  }
+
+  uint16_t
+  getLimit() const
+  {
+    return m_limit;
+  }
+
+protected:
+
+  virtual size_t
+  generate(ndn::EncodingBuffer& outBuffer)
+  {
+    size_t totalLength = 0;
+    for (uint64_t i = 0; i < m_limit; i++)
+      {
+        totalLength += prependNonNegativeIntegerBlock(outBuffer, ndn::Tlv::Content, i);
+      }
+    return totalLength;
+  }
+
+protected:
+  const uint64_t m_limit;
+};
+
+class SegmentPublisherFixture : public BaseFixture
+{
+public:
+  SegmentPublisherFixture()
+    : m_face(make_shared<InternalFace>())
+    , m_publisher(m_face, "/localhost/nfd/SegmentPublisherFixture")
+    , m_finished(false)
+  {
+
+  }
+
+  void
+  validate(const Data& data)
+  {
+    Block payload = data.getContent();
+    NFD_LOG_DEBUG("payload size (w/o Content TLV): " << payload.value_size());
+
+    m_buffer.appendByteArray(payload.value(), payload.value_size());
+
+    uint64_t segmentNo = data.getName()[-1].toSegment();
+    if (data.getFinalBlockId() != data.getName()[-1])
+      {
+        return;
+      }
+
+    NFD_LOG_DEBUG("got final block: #" << segmentNo);
+
+    // wrap data in a single Content TLV for easy parsing
+    m_buffer.prependVarNumber(m_buffer.size());
+    m_buffer.prependVarNumber(ndn::Tlv::Content);
+
+    BOOST_TEST_CHECKPOINT("creating parser");
+    ndn::Block parser(m_buffer.buf(), m_buffer.size());
+    BOOST_TEST_CHECKPOINT("parsing aggregated response");
+    parser.parse();
+
+    BOOST_REQUIRE_EQUAL(parser.elements_size(), m_publisher.getLimit());
+
+    uint64_t expectedNo = m_publisher.getLimit() - 1;
+    for (Block::element_const_iterator i = parser.elements_begin();
+         i != parser.elements_end();
+         ++i)
+      {
+        uint64_t number = readNonNegativeInteger(*i);
+        BOOST_REQUIRE_EQUAL(number, expectedNo);
+        --expectedNo;
+      }
+    m_finished = true;
+  }
+
+protected:
+  shared_ptr<InternalFace> m_face;
+  TestSegmentPublisher m_publisher;
+  ndn::EncodingBuffer m_buffer;
+
+protected:
+  bool m_finished;
+};
+
+BOOST_FIXTURE_TEST_SUITE(MgmtSegmentPublisher, SegmentPublisherFixture)
+
+BOOST_AUTO_TEST_CASE(Generate)
+{
+  m_face->onReceiveData +=
+    bind(&SegmentPublisherFixture::validate, this, _1);
+
+  m_publisher.publish();
+  BOOST_REQUIRE(m_finished);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd