core: use markers in StatusDataset and NotificationStream

This commit also refactors FaceMonitor as generic NotificationSubscriber,
and refactors AutoregServer to use FaceMonitor.

refs #1837 #1838

Change-Id: I8b40dfae118853d1224c8290cf92e7cc0daa116f
diff --git a/core/event-emitter.hpp b/core/event-emitter.hpp
index 35925f9..e63e111 100644
--- a/core/event-emitter.hpp
+++ b/core/event-emitter.hpp
@@ -1,11 +1,12 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014  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
+ * Copyright (c) 2014,  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.
@@ -20,7 +21,7 @@
  *
  * 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 NFD_CORE_EVENT_EMITTER_HPP
 #define NFD_CORE_EVENT_EMITTER_HPP
@@ -29,25 +30,27 @@
 
 namespace nfd {
 
-struct empty {};
+struct empty
+{
+};
 
 /** \class EventEmitter
  *  \brief provides a lightweight event system
  *
  *  To declare an event:
- *    EventEmitter<TArgs> m_eventName;
+ *    EventEmitter<TArgs> onEventName;
  *  To subscribe to an event:
- *    eventSource->m_eventName += eventHandler;
+ *    eventSource->onEventName += eventHandler;
  *    Multiple functions can subscribe to the same event.
  *  To trigger an event:
- *    m_eventName(args);
+ *    onEventName(args);
  *  To clear event subscriptions:
- *    m_eventName.clear();
+ *    onEventName.clear();
  */
 
 // four arguments
 template<typename T1 = empty, typename T2 = empty,
-    typename T3 = empty, typename T4 = empty>
+         typename T3 = empty, typename T4 = empty>
 class EventEmitter : noncopyable
 {
 public:
@@ -199,6 +202,8 @@
   std::vector<Handler>::iterator it;
   for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
     (*it)();
+    if (m_handlers.empty()) // .clear has been called
+      return;
   }
 }
 
@@ -232,6 +237,8 @@
   typename std::vector<Handler>::iterator it;
   for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
     (*it)(a1);
+    if (m_handlers.empty()) // .clear has been called
+      return;
   }
 }
 
@@ -266,6 +273,8 @@
   typename std::vector<Handler>::iterator it;
   for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
     (*it)(a1, a2);
+    if (m_handlers.empty()) // .clear has been called
+      return;
   }
 }
 
@@ -300,6 +309,8 @@
   typename std::vector<Handler>::iterator it;
   for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
     (*it)(a1, a2, a3);
+    if (m_handlers.empty()) // .clear has been called
+      return;
   }
 }
 
@@ -334,6 +345,8 @@
   typename std::vector<Handler>::iterator it;
   for (it = m_handlers.begin(); it != m_handlers.end(); ++it) {
     (*it)(a1, a2, a3, a4);
+    if (m_handlers.empty()) // .clear has been called
+      return;
   }
 }
 
diff --git a/core/face-monitor.cpp b/core/face-monitor.cpp
deleted file mode 100644
index 914e3df..0000000
--- a/core/face-monitor.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014  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 "face-monitor.hpp"
-#include "core/logger.hpp"
-
-#include <ndn-cxx/face.hpp>
-
-namespace nfd {
-
-NFD_LOG_INIT("FaceMonitor");
-
-FaceMonitor::FaceMonitor(ndn::Face& face)
-  : m_face(face)
-  , m_isStopped(true)
-{
-}
-
-FaceMonitor::~FaceMonitor()
-{
-}
-
-void
-FaceMonitor::removeAllSubscribers()
-{
-  m_notificationCallbacks.clear();
-  m_timeoutCallbacks.clear();
-  stopNotifications();
-}
-
-void
-FaceMonitor::stopNotifications()
-{
-  if (static_cast<bool>(m_lastInterestId))
-    m_face.removePendingInterest(m_lastInterestId);
-  m_isStopped = true;
-}
-
-void
-FaceMonitor::startNotifications()
-{
-  if (m_isStopped == false)
-    return; // Notifications cycle has been started.
-  m_isStopped = false;
-
-  Interest interest("/localhost/nfd/faces/events");
-  interest
-    .setMustBeFresh(true)
-    .setChildSelector(1)
-    .setInterestLifetime(time::seconds(60))
-  ;
-
-  NFD_LOG_DEBUG("startNotification: Interest Sent: " << interest);
-
-  m_lastInterestId = m_face.expressInterest(interest,
-                                            bind(&FaceMonitor::onNotification, this, _2),
-                                            bind(&FaceMonitor::onTimeout, this));
-}
-
-void
-FaceMonitor::addSubscriber(const NotificationCallback& notificationCallback)
-{
-  addSubscriber(notificationCallback, TimeoutCallback());
-}
-
-void
-FaceMonitor::addSubscriber(const NotificationCallback& notificationCallback,
-                           const TimeoutCallback&  timeoutCallback)
-{
-  if (static_cast<bool>(notificationCallback))
-    m_notificationCallbacks.push_back(notificationCallback);
-
-  if (static_cast<bool>(timeoutCallback))
-    m_timeoutCallbacks.push_back(timeoutCallback);
-}
-
-void
-FaceMonitor::onTimeout()
-{
-  if (m_isStopped)
-    return;
-
-  std::vector<TimeoutCallback>::iterator it;
-  for (it = m_timeoutCallbacks.begin();
-       it != m_timeoutCallbacks.end(); ++it) {
-    (*it)();
-    //One of the registered callbacks has cleared the vector,
-    //return now as the iterator has been invalidated and
-    //the vector is empty.
-    if (m_timeoutCallbacks.empty()) {
-       return;
-    }
-  }
-
-  Interest newInterest("/localhost/nfd/faces/events");
-  newInterest
-    .setMustBeFresh(true)
-    .setChildSelector(1)
-    .setInterestLifetime(time::seconds(60))
-    ;
-
-  NFD_LOG_DEBUG("In onTimeout, sending interest: " << newInterest);
-
-  m_lastInterestId = m_face.expressInterest(newInterest,
-                                            bind(&FaceMonitor::onNotification, this, _2),
-                                            bind(&FaceMonitor::onTimeout, this));
-}
-
-void
-FaceMonitor::onNotification(const Data& data)
-{
-  if (m_isStopped)
-    return;
-
-  m_lastSequence = data.getName().get(-1).toSegment();
-  ndn::nfd::FaceEventNotification notification(data.getContent().blockFromValue());
-
-  std::vector<NotificationCallback>::iterator it;
-  for (it = m_notificationCallbacks.begin();
-       it != m_notificationCallbacks.end(); ++it) {
-    (*it)(notification);
-    if (m_notificationCallbacks.empty()) {
-      //One of the registered callbacks has cleared the vector.
-      //return back, as no one is interested in notifications anymore.
-      return;
-    }
-  }
-
-  //Setting up next notification
-  Name nextNotification("/localhost/nfd/faces/events");
-  nextNotification.appendSegment(m_lastSequence + 1);
-
-  Interest interest(nextNotification);
-  interest.setInterestLifetime(time::seconds(60));
-
-  NFD_LOG_DEBUG("onNotification: Interest sent: " <<  interest);
-
-  m_lastInterestId = m_face.expressInterest(interest,
-                                            bind(&FaceMonitor::onNotification, this, _2),
-                                            bind(&FaceMonitor::onTimeout, this));
-}
-
-} // namespace nfd
diff --git a/core/face-monitor.hpp b/core/face-monitor.hpp
index 459b181..521fc23 100644
--- a/core/face-monitor.hpp
+++ b/core/face-monitor.hpp
@@ -1,12 +1,12 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014  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
+ * Copyright (c) 2014,  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.
@@ -21,83 +21,26 @@
  *
  * 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 NFD_CORE_FACE_MONITOR_HPP
 #define NFD_CORE_FACE_MONITOR_HPP
 
-#include "common.hpp"
+#include "notification-subscriber.hpp"
 
 #include <ndn-cxx/management/nfd-face-event-notification.hpp>
 
-namespace ndn {
-class Face;
-class PendingInterestId;
-}
-
 namespace nfd {
 
 using ndn::nfd::FaceEventNotification;
 
-/**
- * \brief Helper class to subscribe to face notification events
- *
- * \todo Write test cases
- */
-class FaceMonitor : noncopyable
+class FaceMonitor : public NotificationSubscriber<FaceEventNotification>
 {
 public:
-  typedef function<void(const ndn::nfd::FaceEventNotification&)> NotificationCallback;
-  typedef function<void()> TimeoutCallback;
-
-  typedef std::vector<NotificationCallback> NotificationCallbacks;
-  typedef std::vector<TimeoutCallback> TimeoutCallbacks;
-
-  explicit
-  FaceMonitor(ndn::Face& face);
-
-  ~FaceMonitor();
-
-  /** \brief Stops all notifications. This method  doesn't remove registered callbacks.
-   */
-  void
-  stopNotifications();
-
-  /** \brief Resumes notifications for added subscribers.
-   */
-  void
-  startNotifications();
-
-  /** \brief Removes all notification subscribers.
-   */
-  void
-  removeAllSubscribers();
-
-  /** \brief Adds a notification subscriber. This method doesn't return on timeouts.
-   */
-  void
-  addSubscriber(const NotificationCallback& notificationCallback);
-
-  /** \brief Adds a notification subscriber.
-   */
-  void
-  addSubscriber(const NotificationCallback& notificationCallback,
-                const TimeoutCallback&  timeoutCallback);
-
-private:
-  void
-  onTimeout();
-
-  void
-  onNotification(const Data& data);
-
-private:
-  ndn::Face& m_face;
-  uint64_t m_lastSequence;
-  bool m_isStopped;
-  NotificationCallbacks m_notificationCallbacks;
-  TimeoutCallbacks m_timeoutCallbacks;
-  const ndn::PendingInterestId* m_lastInterestId;
+  FaceMonitor(ndn::Face& face)
+    : NotificationSubscriber<FaceEventNotification>(face, "ndn:/localhost/nfd/faces/events")
+  {
+  }
 };
 
 } // namespace nfd
diff --git a/core/notification-stream.hpp b/core/notification-stream.hpp
new file mode 100644
index 0000000..9a89b4c
--- /dev/null
+++ b/core/notification-stream.hpp
@@ -0,0 +1,81 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014,  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 NFD_CORE_NOTIFICATION_STREAM_HPP
+#define NFD_CORE_NOTIFICATION_STREAM_HPP
+
+#include "common.hpp"
+
+#include <ndn-cxx/encoding/encoding-buffer.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+
+namespace nfd {
+
+/** \brief provides a publisher of Notification Stream
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/Notification
+ */
+template <class FaceBase>
+class NotificationStream : noncopyable
+{
+public:
+  NotificationStream(FaceBase& face, const Name& prefix, ndn::KeyChain& keyChain)
+    : m_face(face)
+    , m_prefix(prefix)
+    , m_keyChain(keyChain)
+    , m_sequenceNo(0)
+  {
+  }
+
+  virtual
+  ~NotificationStream()
+  {
+  }
+
+  template<typename T> void
+  postNotification(const T& notification)
+  {
+    Name dataName = m_prefix;
+    dataName.appendSequenceNumber(m_sequenceNo);
+
+    shared_ptr<Data> data = make_shared<Data>(dataName);
+    data->setContent(notification.wireEncode());
+    data->setFreshnessPeriod(time::seconds(1));
+
+    m_keyChain.sign(*data);
+    m_face.put(*data);
+
+    ++m_sequenceNo;
+  }
+
+private:
+  FaceBase& m_face;
+  const Name m_prefix;
+  ndn::KeyChain& m_keyChain;
+  uint64_t m_sequenceNo;
+};
+
+} // namespace nfd
+
+#endif // NFD_CORE_NOTIFICATION_STREAM_HPP
diff --git a/core/notification-subscriber.hpp b/core/notification-subscriber.hpp
new file mode 100644
index 0000000..c359c82
--- /dev/null
+++ b/core/notification-subscriber.hpp
@@ -0,0 +1,214 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014,  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 NFD_CORE_NOTIFICATION_SUBSCRIBER_HPP
+#define NFD_CORE_NOTIFICATION_SUBSCRIBER_HPP
+
+#include "event-emitter.hpp"
+
+#include <ndn-cxx/face.hpp>
+
+namespace nfd {
+
+/** \brief provides a subscriber of Notification Stream
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/Notification
+ *  \tparam T type of Notification item, appears in payload of Data packets
+ */
+template<typename T>
+class NotificationSubscriber : noncopyable
+{
+public:
+  /** \brief construct a NotificationSubscriber
+   *  \note The subscriber is not started after construction.
+   *        User should add one or more handlers to onNotification, and invoke .start().
+   */
+  NotificationSubscriber(ndn::Face& face, const Name& prefix)
+    : m_face(face)
+    , m_prefix(prefix)
+    , m_isRunning(false)
+    , m_lastSequenceNo(std::numeric_limits<uint64_t>::max())
+    , m_lastInterestId(0)
+  {
+  }
+
+  virtual
+  ~NotificationSubscriber()
+  {
+  }
+
+  /** \return InterestLifetime of Interests to retrieve notifications
+   *  \details This must be greater than FreshnessPeriod of Notification Data packets,
+   *           to ensure correct operation of this subscriber implementation.
+   */
+  static time::milliseconds
+  getInterestLifetime()
+  {
+    return time::milliseconds(60000);
+  }
+
+  bool
+  isRunning() const
+  {
+    return m_isRunning;
+  }
+
+  /** \brief start or resume receiving notifications
+   *  \note onNotification must have at least one listener,
+   *        otherwise this operation has no effect.
+   */
+  void
+  start()
+  {
+    if (m_isRunning) // already running
+      return;
+    m_isRunning = true;
+
+    this->sendInitialInterest();
+  }
+
+  /** \brief stop receiving notifications
+   */
+  void
+  stop()
+  {
+    if (!m_isRunning) // not running
+      return;
+    m_isRunning = false;
+
+    if (m_lastInterestId != 0)
+      m_face.removePendingInterest(m_lastInterestId);
+    m_lastInterestId = 0;
+  }
+
+public: // subscriptions
+  /** \brief fires when a Notification is received
+   *  \note Removing all handlers will cause the subscriber to stop.
+   */
+  EventEmitter<T> onNotification;
+
+  /** \brief fires when no Notification is received within .getInterestLifetime period
+   */
+  EventEmitter<> onTimeout;
+
+  /** \brief fires when a Data packet in the Notification Stream cannot be decoded as T
+   */
+  EventEmitter<Data> onDecodeError;
+
+private:
+  void
+  sendInitialInterest()
+  {
+    if (this->shouldStop())
+      return;
+
+    shared_ptr<Interest> interest = make_shared<Interest>(m_prefix);
+    interest->setMustBeFresh(true);
+    interest->setChildSelector(1);
+    interest->setInterestLifetime(getInterestLifetime());
+
+    m_lastInterestId = m_face.expressInterest(*interest,
+                         bind(&NotificationSubscriber<T>::afterReceiveData, this, _2),
+                         bind(&NotificationSubscriber<T>::afterTimeout, this));
+  }
+
+  void
+  sendNextInterest()
+  {
+    if (this->shouldStop())
+      return;
+
+    BOOST_ASSERT(m_lastSequenceNo !=
+                 std::numeric_limits<uint64_t>::max());// overflow or missing initial reply
+
+    Name nextName = m_prefix;
+    nextName.appendSequenceNumber(m_lastSequenceNo + 1);
+
+    shared_ptr<Interest> interest = make_shared<Interest>(nextName);
+    interest->setInterestLifetime(getInterestLifetime());
+
+    m_lastInterestId = m_face.expressInterest(*interest,
+                         bind(&NotificationSubscriber<T>::afterReceiveData, this, _2),
+                         bind(&NotificationSubscriber<T>::afterTimeout, this));
+  }
+
+  /** \brief Check if the subscriber is or should be stopped.
+   *  \return true if the subscriber is stopped.
+   */
+  bool
+  shouldStop()
+  {
+    if (!m_isRunning)
+      return true;
+    if (onNotification.isEmpty()) {
+      this->stop();
+      return true;
+    }
+    return false;
+  }
+
+  void
+  afterReceiveData(const Data& data)
+  {
+    if (this->shouldStop())
+      return;
+
+    T notification;
+    try {
+      m_lastSequenceNo = data.getName().get(-1).toSequenceNumber();
+      notification.wireDecode(data.getContent().blockFromValue());
+    }
+    catch (tlv::Error&) {
+      this->onDecodeError(data);
+      this->sendInitialInterest();
+      return;
+    }
+
+    this->onNotification(notification);
+
+    this->sendNextInterest();
+  }
+
+  void
+  afterTimeout()
+  {
+    if (this->shouldStop())
+      return;
+
+    this->onTimeout();
+
+    this->sendInitialInterest();
+  }
+
+private:
+  ndn::Face& m_face;
+  Name m_prefix;
+  bool m_isRunning;
+  uint64_t m_lastSequenceNo;
+  const ndn::PendingInterestId* m_lastInterestId;
+};
+
+} // namespace nfd
+
+#endif // NFD_CORE_NOTIFICATION_SUBSCRIBER_HPP
diff --git a/core/segment-publisher.hpp b/core/segment-publisher.hpp
index b1aa4a9..ccb3426 100644
--- a/core/segment-publisher.hpp
+++ b/core/segment-publisher.hpp
@@ -33,6 +33,9 @@
 
 namespace nfd {
 
+/** \brief provides a publisher of Status Dataset or other segmented octet stream
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/StatusDataset
+ */
 template <class FaceBase>
 class SegmentPublisher : noncopyable
 {
@@ -59,48 +62,44 @@
   void
   publish()
   {
-    Name segmentPrefix(m_prefix);
-    segmentPrefix.appendVersion();
-
     ndn::EncodingBuffer buffer;
-
     generate(buffer);
 
     const uint8_t* rawBuffer = buffer.buf();
     const uint8_t* segmentBegin = rawBuffer;
     const uint8_t* end = rawBuffer + buffer.size();
 
+    Name segmentPrefix(m_prefix);
+    segmentPrefix.appendVersion();
+
     uint64_t segmentNo = 0;
-    do
-      {
-        const uint8_t* segmentEnd = segmentBegin + getMaxSegmentSize();
-        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)
-          {
-            data->setFinalBlockId(segmentName[-1]);
-          }
-
-        publishSegment(data);
-        segmentNo++;
+    do {
+      const uint8_t* segmentEnd = segmentBegin + getMaxSegmentSize();
+      if (segmentEnd > end) {
+        segmentEnd = end;
       }
-    while (segmentBegin < 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) {
+        data->setFinalBlockId(segmentName[-1]);
+      }
+
+      publishSegment(data);
+      ++segmentNo;
+    } while (segmentBegin < end);
   }
 
 protected:
-
+  /** \brief In a derived class, write the octets into outBuffer.
+   */
   virtual size_t
-  generate(ndn::EncodingBuffer& outBuffer) =0;
+  generate(ndn::EncodingBuffer& outBuffer) = 0;
 
 private:
   void
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index c8bc267..b31f841 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -119,7 +119,7 @@
   , m_faceTable(faceTable)
   , m_faceStatusPublisher(m_faceTable, *m_face, FACES_LIST_DATASET_PREFIX, keyChain)
   , m_channelStatusPublisher(m_factories, *m_face, CHANNELS_LIST_DATASET_PREFIX, keyChain)
-  , m_notificationStream(m_face, FACE_EVENTS_PREFIX, keyChain)
+  , m_notificationStream(*m_face, FACE_EVENTS_PREFIX, keyChain)
   , m_signedVerbDispatch(SIGNED_COMMAND_VERBS,
                          SIGNED_COMMAND_VERBS +
                          (sizeof(SIGNED_COMMAND_VERBS) / sizeof(SignedVerbAndProcessor)))
diff --git a/daemon/mgmt/face-manager.hpp b/daemon/mgmt/face-manager.hpp
index ec5decf..6a3994e 100644
--- a/daemon/mgmt/face-manager.hpp
+++ b/daemon/mgmt/face-manager.hpp
@@ -27,11 +27,11 @@
 #define NFD_DAEMON_MGMT_FACE_MANAGER_HPP
 
 #include "common.hpp"
+#include "core/notification-stream.hpp"
 #include "face/local-face.hpp"
 #include "mgmt/manager-base.hpp"
 #include "mgmt/face-status-publisher.hpp"
 #include "mgmt/channel-status-publisher.hpp"
-#include "mgmt/notification-stream.hpp"
 
 #include <ndn-cxx/management/nfd-control-parameters.hpp>
 #include <ndn-cxx/management/nfd-control-response.hpp>
@@ -174,7 +174,7 @@
   FaceTable& m_faceTable;
   FaceStatusPublisher m_faceStatusPublisher;
   ChannelStatusPublisher m_channelStatusPublisher;
-  NotificationStream m_notificationStream;
+  NotificationStream<AppFace> m_notificationStream;
 
   typedef function<void(FaceManager*,
                         const Interest&,
diff --git a/daemon/mgmt/notification-stream.hpp b/daemon/mgmt/notification-stream.hpp
deleted file mode 100644
index 06c3b5a..0000000
--- a/daemon/mgmt/notification-stream.hpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014,  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 NFD_DAEMON_MGMT_NOTIFICATION_STREAM_HPP
-#define NFD_DAEMON_MGMT_NOTIFICATION_STREAM_HPP
-
-#include "mgmt/app-face.hpp"
-
-namespace nfd {
-
-class NotificationStream
-{
-public:
-  NotificationStream(shared_ptr<AppFace> face, const Name& prefix, ndn::KeyChain& keyChain);
-
-  ~NotificationStream();
-
-  template <typename T> void
-  postNotification(const T& notification);
-
-private:
-  shared_ptr<AppFace> m_face;
-  const Name m_prefix;
-  uint64_t m_sequenceNo;
-  ndn::KeyChain& m_keyChain;
-};
-
-inline
-NotificationStream::NotificationStream(shared_ptr<AppFace> face,
-                                       const Name& prefix,
-                                       ndn::KeyChain& keyChain)
-  : m_face(face)
-  , m_prefix(prefix)
-  , m_sequenceNo(0)
-  , m_keyChain(keyChain)
-{
-}
-
-template <typename T>
-inline void
-NotificationStream::postNotification(const T& notification)
-{
-  Name dataName(m_prefix);
-  dataName.appendSegment(m_sequenceNo);
-  shared_ptr<Data> data(make_shared<Data>(dataName));
-  data->setContent(notification.wireEncode());
-  data->setFreshnessPeriod(time::seconds(1));
-
-  m_keyChain.sign(*data);
-  m_face->put(*data);
-
-  ++m_sequenceNo;
-}
-
-inline
-NotificationStream::~NotificationStream()
-{
-}
-
-} // namespace nfd
-
-
-#endif // NFD_DAEMON_MGMT_NOTIFICATION_STREAM_HPP
diff --git a/rib/rib-manager.cpp b/rib/rib-manager.cpp
index 318198a..d4be75d 100644
--- a/rib/rib-manager.cpp
+++ b/rib/rib-manager.cpp
@@ -118,8 +118,8 @@
   }
 
   NFD_LOG_INFO("Start monitoring face create/destroy events");
-  m_faceMonitor.addSubscriber(bind(&RibManager::onNotification, this, _1));
-  m_faceMonitor.startNotifications();
+  m_faceMonitor.onNotification += bind(&RibManager::onNotification, this, _1);
+  m_faceMonitor.start();
 
   fetchActiveFaces();
 }
diff --git a/tests/core/notification-stream.cpp b/tests/core/notification-stream.cpp
new file mode 100644
index 0000000..df3fc5b
--- /dev/null
+++ b/tests/core/notification-stream.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014,  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 "core/notification-stream.hpp"
+#include "simple-notification.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/dummy-client-face.hpp"
+
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(CoreNotificationStream, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Post)
+{
+  shared_ptr<DummyClientFace> face = makeDummyClientFace();
+  ndn::KeyChain keyChain;
+  NotificationStream<DummyClientFace> notificationStream(*face,
+    "/localhost/nfd/NotificationStreamTest", keyChain);
+
+  SimpleNotification event1("msg1");
+  notificationStream.postNotification(event1);
+  face->processEvents();
+  BOOST_REQUIRE_EQUAL(face->m_sentDatas.size(), 1);
+  BOOST_CHECK_EQUAL(face->m_sentDatas[0].getName(),
+                    "/localhost/nfd/NotificationStreamTest/%FE%00");
+  SimpleNotification decoded1;
+  BOOST_CHECK_NO_THROW(decoded1.wireDecode(face->m_sentDatas[0].getContent().blockFromValue()));
+  BOOST_CHECK_EQUAL(decoded1.getMessage(), "msg1");
+
+  SimpleNotification event2("msg2");
+  notificationStream.postNotification(event2);
+  face->processEvents();
+  BOOST_REQUIRE_EQUAL(face->m_sentDatas.size(), 2);
+  BOOST_CHECK_EQUAL(face->m_sentDatas[1].getName(),
+                    "/localhost/nfd/NotificationStreamTest/%FE%01");
+  SimpleNotification decoded2;
+  BOOST_CHECK_NO_THROW(decoded2.wireDecode(face->m_sentDatas[1].getContent().blockFromValue()));
+  BOOST_CHECK_EQUAL(decoded2.getMessage(), "msg2");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/core/notification-subscriber.cpp b/tests/core/notification-subscriber.cpp
new file mode 100644
index 0000000..12834f6
--- /dev/null
+++ b/tests/core/notification-subscriber.cpp
@@ -0,0 +1,223 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014,  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 "core/notification-subscriber.hpp"
+#include "core/notification-stream.hpp"
+#include "simple-notification.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/dummy-client-face.hpp"
+
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(CoreNotificationSubscriber, BaseFixture)
+
+class EndToEndFixture : public BaseFixture
+{
+public:
+  EndToEndFixture()
+    : streamPrefix("ndn:/NotificationSubscriberTest")
+    , publisherFace(makeDummyClientFace())
+    , notificationStream(*publisherFace, streamPrefix, publisherKeyChain)
+    , subscriberFace(makeDummyClientFace())
+    , subscriber(*subscriberFace, streamPrefix)
+  {
+  }
+
+  /** \brief post one notification and deliver to subscriber
+   */
+  void
+  deliverNotification(const std::string& msg)
+  {
+    publisherFace->m_sentDatas.clear();
+    SimpleNotification notification(msg);
+    notificationStream.postNotification(notification);
+    publisherFace->processEvents();
+    BOOST_REQUIRE_EQUAL(publisherFace->m_sentDatas.size(), 1);
+
+    lastDeliveredSeqNo = publisherFace->m_sentDatas[0].getName().at(-1).toSequenceNumber();
+
+    lastNotification.setMessage("");
+    subscriberFace->receive(publisherFace->m_sentDatas[0]);
+  }
+
+  void
+  afterNotification(const SimpleNotification& notification)
+  {
+    lastNotification = notification;
+  }
+
+  void
+  clearNotificationHandlers()
+  {
+    subscriber.onNotification.clear();
+  }
+
+  void
+  afterTimeout()
+  {
+    hasTimeout = true;
+  }
+
+  void
+  afterDecodeError(const Data& data)
+  {
+    lastDecodeErrorData = data;
+  }
+
+  /** \return true if subscriberFace has an initial request as sole sent Interest
+   */
+  bool
+  hasInitialRequest() const
+  {
+    if (subscriberFace->m_sentInterests.size() != 1)
+      return 0;
+
+    const Interest& interest = subscriberFace->m_sentInterests[0];
+    return interest.getName() == streamPrefix &&
+           interest.getChildSelector() == 1 &&
+           interest.getMustBeFresh() &&
+           interest.getInterestLifetime() ==
+             NotificationSubscriber<SimpleNotification>::getInterestLifetime();
+  }
+
+  /** \return sequence number of the continuation request sent from subscriberFace
+   *          or 0 if there's no such request as sole sent Interest
+   */
+  uint64_t
+  getRequestSeqNo() const
+  {
+    if (subscriberFace->m_sentInterests.size() != 1)
+      return 0;
+
+    const Interest& interest = subscriberFace->m_sentInterests[0];
+    const Name& name = interest.getName();
+    if (streamPrefix.isPrefixOf(name) &&
+        name.size() == streamPrefix.size() + 1 &&
+        interest.getInterestLifetime() ==
+          NotificationSubscriber<SimpleNotification>::getInterestLifetime())
+      return name[-1].toSequenceNumber();
+    else
+      return 0;
+  }
+
+protected:
+  Name streamPrefix;
+  shared_ptr<DummyClientFace> publisherFace;
+  ndn::KeyChain publisherKeyChain;
+  NotificationStream<DummyClientFace> notificationStream;
+  shared_ptr<DummyClientFace> subscriberFace;
+  NotificationSubscriber<SimpleNotification> subscriber;
+
+  uint64_t lastDeliveredSeqNo;
+
+  SimpleNotification lastNotification;
+  bool hasTimeout;
+  Data lastDecodeErrorData;
+};
+
+BOOST_FIXTURE_TEST_CASE(EndToEnd, EndToEndFixture)
+{
+  BOOST_REQUIRE_EQUAL(subscriber.isRunning(), false);
+
+  // has no effect because onNotification has no handler
+  subscriber.start();
+  BOOST_REQUIRE_EQUAL(subscriber.isRunning(), false);
+
+  subscriber.onNotification += bind(&EndToEndFixture::afterNotification, this, _1);
+  subscriber.onTimeout += bind(&EndToEndFixture::afterTimeout, this);
+  subscriber.onDecodeError += bind(&EndToEndFixture::afterDecodeError, this, _1);
+
+  // not received when subscriber is not running
+  this->deliverNotification("n1");
+  subscriberFace->processEvents(time::milliseconds(10));
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK_EQUAL(subscriberFace->m_sentInterests.size(), 0);
+
+  subscriberFace->m_sentInterests.clear();
+  subscriber.start();
+  subscriberFace->processEvents(time::milliseconds(10));
+  BOOST_REQUIRE_EQUAL(subscriber.isRunning(), true);
+  BOOST_CHECK(this->hasInitialRequest());
+
+  // respond to initial request
+  subscriberFace->m_sentInterests.clear();
+  this->deliverNotification("n2");
+  subscriberFace->processEvents(time::milliseconds(10));
+  BOOST_CHECK_EQUAL(lastNotification.getMessage(), "n2");
+  BOOST_CHECK_EQUAL(this->getRequestSeqNo(), lastDeliveredSeqNo + 1);
+
+  // respond to continuation request
+  subscriberFace->m_sentInterests.clear();
+  this->deliverNotification("n3");
+  subscriberFace->processEvents(time::milliseconds(10));
+  BOOST_CHECK_EQUAL(lastNotification.getMessage(), "n3");
+  BOOST_CHECK_EQUAL(this->getRequestSeqNo(), lastDeliveredSeqNo + 1);
+
+  // timeout
+  subscriberFace->m_sentInterests.clear();
+  lastNotification.setMessage("");
+  subscriberFace->processEvents(
+    NotificationSubscriber<SimpleNotification>::getInterestLifetime() +
+    time::milliseconds(1000));
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK_EQUAL(hasTimeout, true);
+  BOOST_CHECK(this->hasInitialRequest());
+
+  // decode error on sequence number
+  Name wrongName = streamPrefix;
+  wrongName.append("%07%07");
+  Data wrongData(wrongName);
+  publisherKeyChain.sign(wrongData);
+  subscriberFace->receive(wrongData);
+  subscriberFace->m_sentInterests.clear();
+  lastNotification.setMessage("");
+  subscriberFace->processEvents(time::milliseconds(10));
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK_EQUAL(lastDecodeErrorData.getName(), wrongName);
+  BOOST_CHECK(this->hasInitialRequest());
+
+  // decode error in payload
+  subscriberFace->m_sentInterests.clear();
+  lastNotification.setMessage("");
+  this->deliverNotification("\x07n4");
+  subscriberFace->processEvents(time::milliseconds(10));
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK(this->hasInitialRequest());
+
+  // stop if handlers are cleared
+  subscriber.onNotification += bind(&EndToEndFixture::clearNotificationHandlers, this);
+  subscriberFace->m_sentInterests.clear();
+  this->deliverNotification("n5");
+  subscriberFace->processEvents(time::milliseconds(10));
+  BOOST_CHECK_EQUAL(subscriberFace->m_sentInterests.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/core/segment-publisher.cpp b/tests/core/segment-publisher.cpp
index f859408..8817669 100644
--- a/tests/core/segment-publisher.cpp
+++ b/tests/core/segment-publisher.cpp
@@ -26,7 +26,7 @@
 #include "core/segment-publisher.hpp"
 
 #include "tests/test-common.hpp"
-#include "tests/dummy-face.hpp"
+#include "tests/dummy-client-face.hpp"
 
 #include <ndn-cxx/encoding/tlv.hpp>
 
@@ -38,10 +38,10 @@
 NFD_LOG_INIT("SegmentPublisherTest");
 
 template<int64_t N=10000>
-class TestSegmentPublisher : public SegmentPublisher<DummyFace>
+class TestSegmentPublisher : public SegmentPublisher<DummyClientFace>
 {
 public:
-  TestSegmentPublisher(DummyFace& face,
+  TestSegmentPublisher(DummyClientFace& face,
                        const Name& prefix,
                        ndn::KeyChain& keyChain)
     : SegmentPublisher(face, prefix, keyChain)
@@ -90,7 +90,7 @@
 {
 public:
   SegmentPublisherFixture()
-    : m_face(makeDummyFace())
+    : m_face(makeDummyClientFace())
     , m_publisher(*m_face, "/localhost/nfd/SegmentPublisherFixture", m_keyChain)
   {
   }
@@ -134,7 +134,7 @@
   }
 
 protected:
-  shared_ptr<DummyFace> m_face;
+  shared_ptr<DummyClientFace> m_face;
   TestSegmentPublisher<N> m_publisher;
   ndn::EncodingBuffer m_buffer;
   ndn::KeyChain m_keyChain;
diff --git a/tests/core/simple-notification.hpp b/tests/core/simple-notification.hpp
new file mode 100644
index 0000000..3c8fdef
--- /dev/null
+++ b/tests/core/simple-notification.hpp
@@ -0,0 +1,94 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014,  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 NFD_TESTS_CORE_SIMPLE_NOTIFICATION_HPP
+#define NFD_TESTS_CORE_SIMPLE_NOTIFICATION_HPP
+
+#include "common.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+class SimpleNotification
+{
+public:
+  SimpleNotification()
+  {
+  }
+
+  SimpleNotification(const std::string& message)
+    : m_message(message)
+  {
+  }
+
+  ~SimpleNotification()
+  {
+  }
+
+  Block
+  wireEncode() const
+  {
+    ndn::EncodingBuffer buffer;
+    prependByteArrayBlock(buffer,
+                          0x8888,
+                          reinterpret_cast<const uint8_t*>(m_message.c_str()),
+                          m_message.size());
+    return buffer.block();
+  }
+
+  void
+  wireDecode(const Block& block)
+  {
+    m_message.assign(reinterpret_cast<const char*>(block.value()),
+                     block.value_size());
+
+    // error for testing
+    if (!m_message.empty() && m_message[0] == '\x07')
+      throw tlv::Error("0x07 error");
+  }
+
+public:
+  const std::string&
+  getMessage() const
+  {
+    return m_message;
+  }
+
+  void
+  setMessage(const std::string& message)
+  {
+    m_message = message;
+  }
+
+private:
+  std::string m_message;
+};
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_CORE_SIMPLE_NOTIFICATION_HPP
diff --git a/tests/daemon/mgmt/notification-stream.cpp b/tests/daemon/mgmt/notification-stream.cpp
deleted file mode 100644
index f64e50b..0000000
--- a/tests/daemon/mgmt/notification-stream.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014,  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/notification-stream.hpp"
-#include "mgmt/internal-face.hpp"
-
-#include "tests/test-common.hpp"
-
-
-namespace nfd {
-namespace tests {
-
-NFD_LOG_INIT("NotificationStreamTest");
-
-
-
-class NotificationStreamFixture : public BaseFixture
-{
-public:
-  NotificationStreamFixture()
-    : m_callbackFired(false)
-    , m_prefix("/localhost/nfd/NotificationStreamTest")
-    , m_message("TestNotificationMessage")
-    , m_sequenceNo(0)
-  {
-  }
-
-  virtual
-  ~NotificationStreamFixture()
-  {
-  }
-
-  void
-  validateCallback(const Data& data)
-  {
-    Name expectedName(m_prefix);
-    expectedName.appendSegment(m_sequenceNo);
-    BOOST_REQUIRE_EQUAL(data.getName(), expectedName);
-
-    ndn::Block payload = data.getContent();
-    std::string message;
-
-    message.append(reinterpret_cast<const char*>(payload.value()), payload.value_size());
-
-    BOOST_REQUIRE_EQUAL(message, m_message);
-
-    m_callbackFired = true;
-    ++m_sequenceNo;
-  }
-
-  void
-  resetCallbackFired()
-  {
-    m_callbackFired = false;
-  }
-
-protected:
-  bool m_callbackFired;
-  const std::string m_prefix;
-  const std::string m_message;
-  uint64_t m_sequenceNo;
-  ndn::KeyChain m_keyChain;
-};
-
-BOOST_FIXTURE_TEST_SUITE(MgmtNotificationStream, NotificationStreamFixture)
-
-class TestNotification
-{
-public:
-  TestNotification(const std::string& message)
-    : m_message(message)
-  {
-  }
-
-  ~TestNotification()
-  {
-  }
-
-  Block
-  wireEncode() const
-  {
-    ndn::EncodingBuffer buffer;
-
-    prependByteArrayBlock(buffer,
-                          ndn::Tlv::Content,
-                          reinterpret_cast<const uint8_t*>(m_message.c_str()),
-                          m_message.size());
-    return buffer.block();
-  }
-
-private:
-  const std::string m_message;
-};
-
-BOOST_AUTO_TEST_CASE(TestPostEvent)
-{
-  shared_ptr<InternalFace> face(make_shared<InternalFace>());
-  NotificationStream notificationStream(face, "/localhost/nfd/NotificationStreamTest", m_keyChain);
-
-  face->onReceiveData += bind(&NotificationStreamFixture::validateCallback, this, _1);
-
-  TestNotification event1(m_message);
-  notificationStream.postNotification(event1);
-
-  BOOST_REQUIRE(m_callbackFired);
-
-  resetCallbackFired();
-
-  TestNotification event2(m_message);
-  notificationStream.postNotification(event2);
-
-  BOOST_REQUIRE(m_callbackFired);
-}
-
-
-BOOST_AUTO_TEST_SUITE_END()
-
-
-} // namespace tests
-} // namespace nfd
diff --git a/tests/dummy-face.hpp b/tests/dummy-client-face.hpp
similarity index 73%
rename from tests/dummy-face.hpp
rename to tests/dummy-client-face.hpp
index 84da29f..2e8781d 100644
--- a/tests/dummy-face.hpp
+++ b/tests/dummy-client-face.hpp
@@ -23,8 +23,8 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef NFD_TESTS_DUMMY_FACE_HPP
-#define NFD_TESTS_DUMMY_FACE_HPP
+#ifndef NFD_TESTS_DUMMY_CLIENT_FACE_HPP
+#define NFD_TESTS_DUMMY_CLIENT_FACE_HPP
 
 #include <ndn-cxx/face.hpp>
 #include <ndn-cxx/transport/transport.hpp>
@@ -32,13 +32,14 @@
 namespace nfd {
 namespace tests {
 
-class DummyTransport : public ndn::Transport
+class DummyClientTransport : public ndn::Transport
 {
 public:
   void
   receive(const Block& block)
   {
-    m_receiveCallback(block);
+    if (static_cast<bool>(m_receiveCallback))
+      m_receiveCallback(block);
   }
 
   virtual void
@@ -79,13 +80,13 @@
 };
 
 
-/** \brief a Face for unit testing
+/** \brief a client-side face for unit testing
  */
-class DummyFace : public ndn::Face
+class DummyClientFace : public ndn::Face
 {
 public:
   explicit
-  DummyFace(shared_ptr<DummyTransport> transport)
+  DummyClientFace(shared_ptr<DummyClientTransport> transport)
     : Face(transport)
     , m_transport(transport)
   {
@@ -103,20 +104,28 @@
   }
 
 public:
+  /** \brief sent Interests
+   *  \note After .expressInterest, .processEvents must be called before
+   *        the Interest would show up here.
+   */
   std::vector<Interest> m_sentInterests;
+  /** \brief sent Datas
+   *  \note After .put, .processEvents must be called before
+   *        the Interest would show up here.
+   */
   std::vector<Data>     m_sentDatas;
 
 private:
-  shared_ptr<DummyTransport> m_transport;
+  shared_ptr<DummyClientTransport> m_transport;
 };
 
-inline shared_ptr<DummyFace>
-makeDummyFace()
+inline shared_ptr<DummyClientFace>
+makeDummyClientFace()
 {
-  return make_shared<DummyFace>(make_shared<DummyTransport>());
+  return make_shared<DummyClientFace>(make_shared<DummyClientTransport>());
 }
 
 } // namespace tests
 } // namespace nfd
 
-#endif // NFD_TESTS_DUMMY_FACE_HPP
+#endif // NFD_TESTS_DUMMY_CLIENT_FACE_HPP
diff --git a/tests/rib/rib-manager.cpp b/tests/rib/rib-manager.cpp
index 8a30684..593517f 100644
--- a/tests/rib/rib-manager.cpp
+++ b/tests/rib/rib-manager.cpp
@@ -26,7 +26,7 @@
 #include "rib/rib-manager.hpp"
 
 #include "tests/test-common.hpp"
-#include "tests/dummy-face.hpp"
+#include "tests/dummy-client-face.hpp"
 #include "rib/rib-status-publisher-common.hpp"
 
 namespace nfd {
@@ -41,7 +41,7 @@
     , ADD_NEXTHOP_VERB("add-nexthop")
     , REMOVE_NEXTHOP_VERB("remove-nexthop")
   {
-    face = nfd::tests::makeDummyFace();
+    face = nfd::tests::makeDummyClientFace();
 
     manager = make_shared<RibManager>(ndn::ref(*face));
     manager->registerWithNfd();
@@ -81,7 +81,7 @@
 
 public:
   shared_ptr<RibManager> manager;
-  shared_ptr<nfd::tests::DummyFace> face;
+  shared_ptr<nfd::tests::DummyClientFace> face;
 
   const Name COMMAND_PREFIX;
   const Name::Component ADD_NEXTHOP_VERB;
diff --git a/tests/rib/rib-status-publisher.cpp b/tests/rib/rib-status-publisher.cpp
index ac68c63..5253bba 100644
--- a/tests/rib/rib-status-publisher.cpp
+++ b/tests/rib/rib-status-publisher.cpp
@@ -26,7 +26,7 @@
 #include "rib/rib-status-publisher.hpp"
 
 #include "rib-status-publisher-common.hpp"
-#include "tests/dummy-face.hpp"
+#include "tests/dummy-client-face.hpp"
 
 namespace nfd {
 namespace rib {
@@ -47,7 +47,7 @@
   rib.insert(name, entry);
 
   ndn::KeyChain keyChain;
-  shared_ptr<nfd::tests::DummyFace> face = nfd::tests::makeDummyFace();
+  shared_ptr<nfd::tests::DummyClientFace> face = nfd::tests::makeDummyClientFace();
   RibStatusPublisher publisher(rib, *face, "/localhost/nfd/rib/list", keyChain);
 
   publisher.publish();
diff --git a/tools/nfd-autoreg.cpp b/tools/nfd-autoreg.cpp
index 21dbc83..b0c46c9 100644
--- a/tools/nfd-autoreg.cpp
+++ b/tools/nfd-autoreg.cpp
@@ -26,7 +26,6 @@
 #include <ndn-cxx/face.hpp>
 #include <ndn-cxx/name.hpp>
 
-#include <ndn-cxx/management/nfd-face-event-notification.hpp>
 #include <ndn-cxx/management/nfd-controller.hpp>
 
 #include <boost/program_options/options_description.hpp>
@@ -35,6 +34,7 @@
 
 #include "version.hpp"
 #include "core/face-uri.hpp"
+#include "core/face-monitor.hpp"
 #include "network.hpp"
 
 namespace po = boost::program_options;
@@ -49,7 +49,7 @@
 public:
   AutoregServer()
     : m_controller(m_face)
-    // , m_lastNotification(<undefined>)
+    , m_faceMonitor(m_face)
     , m_cost(255)
   {
   }
@@ -130,13 +130,8 @@
   }
 
   void
-  onNotification(const Data& data)
+  onNotification(const FaceEventNotification& notification)
   {
-    m_lastNotification = data.getName().get(-1).toSegment();
-
-    // process
-    FaceEventNotification notification(data.getContent().blockFromValue());
-
     if (notification.getKind() == FACE_EVENT_CREATED &&
         !notification.isLocal() &&
         notification.isOnDemand())
@@ -147,33 +142,6 @@
       {
         std::cerr << "IGNORED: " << notification << std::endl;
       }
-
-    Name nextNotification("/localhost/nfd/faces/events");
-    nextNotification
-      .appendSegment(m_lastNotification + 1);
-
-    // no need to set freshness or child selectors
-    m_face.expressInterest(nextNotification,
-                           bind(&AutoregServer::onNotification, this, _2),
-                           bind(&AutoregServer::onTimeout, this, _1));
-  }
-
-  void
-  onTimeout(const Interest& timedOutInterest)
-  {
-    // re-express the timed out interest, but reset Nonce, since it has to change
-
-    // To be robust against missing notification, use ChildSelector and MustBeFresh
-    Interest interest("/localhost/nfd/faces/events");
-    interest
-      .setMustBeFresh(true)
-      .setChildSelector(1)
-      .setInterestLifetime(time::seconds(60))
-      ;
-
-    m_face.expressInterest(interest,
-                           bind(&AutoregServer::onNotification, this, _2),
-                           bind(&AutoregServer::onTimeout, this, _1));
   }
 
   void
@@ -225,16 +193,8 @@
         std::cout << "  " << *network << std::endl;
       }
 
-    Interest interest("/localhost/nfd/faces/events");
-    interest
-      .setMustBeFresh(true)
-      .setChildSelector(1)
-      .setInterestLifetime(time::seconds(60))
-      ;
-
-    m_face.expressInterest(interest,
-                           bind(&AutoregServer::onNotification, this, _2),
-                           bind(&AutoregServer::onTimeout, this, _1));
+    m_faceMonitor.onNotification += bind(&AutoregServer::onNotification, this, _1);
+    m_faceMonitor.start();
 
     boost::asio::signal_set signalSet(m_face.getIoService(), SIGINT, SIGTERM);
     signalSet.async_wait(bind(&AutoregServer::signalHandler, this));
@@ -314,7 +274,7 @@
 private:
   Face m_face;
   Controller m_controller;
-  uint64_t m_lastNotification;
+  FaceMonitor m_faceMonitor;
   std::vector<ndn::Name> m_autoregPrefixes;
   uint64_t m_cost;
   std::vector<Network> m_whiteList;