mgmt: add face event notification and notification stream

refs: #1244

Change-Id: I14bd327be53bcb607b457d6cb4bd66bd28a2feaa
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index 237a914..e4769ff 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -12,6 +12,8 @@
 #include "face/tcp-factory.hpp"
 #include "face/udp-factory.hpp"
 
+#include <ndn-cpp-dev/management/nfd-face-event-notification.hpp>
+
 #ifdef HAVE_UNIX_SOCKETS
 #include "face/unix-stream-factory.hpp"
 #endif // HAVE_UNIX_SOCKETS
@@ -54,18 +56,24 @@
                              Name::Component("list"),
                              &FaceManager::listFaces
                              ),
+
+    UnsignedVerbAndProcessor(
+                             Name::Component("events"),
+                             &FaceManager::ignoreUnsignedVerb
+                             ),
   };
 
 const Name FaceManager::LIST_COMMAND_PREFIX("/localhost/nfd/faces/list");
 const size_t FaceManager::LIST_COMMAND_NCOMPS = LIST_COMMAND_PREFIX.size();
 
-
+const Name FaceManager::EVENTS_COMMAND_PREFIX("/localhost/nfd/faces/events");
 
 FaceManager::FaceManager(FaceTable& faceTable,
                          shared_ptr<InternalFace> face)
   : ManagerBase(face, FACE_MANAGER_PRIVILEGE)
   , m_faceTable(faceTable)
   , m_statusPublisher(m_faceTable, m_face, LIST_COMMAND_PREFIX)
+  , m_notificationStream(m_face, EVENTS_COMMAND_PREFIX)
   , m_signedVerbDispatch(SIGNED_COMMAND_VERBS,
                    SIGNED_COMMAND_VERBS +
                    (sizeof(SIGNED_COMMAND_VERBS) / sizeof(SignedVerbAndProcessor)))
@@ -76,6 +84,9 @@
 {
   face->setInterestFilter("/localhost/nfd/faces",
                           bind(&FaceManager::onFaceRequest, this, _2));
+
+  m_faceTable.onAdd    += bind(&FaceManager::onAddFace, this, _1);
+  m_faceTable.onRemove += bind(&FaceManager::onRemoveFace, this, _1);
 }
 
 FaceManager::~FaceManager()
@@ -632,15 +643,35 @@
                          ndn::nfd::FaceManagementOptions& options)
 {
   shared_ptr<Face> target = m_faceTable.get(options.getFaceId());
-  if (target)
+  if (static_cast<bool>(target))
     {
-      // don't call m_faceTable.remove(target): it's called by target->close() via onFail
       target->close();
     }
   sendResponse(requestName, 200, "Success");
 }
 
 void
+FaceManager::onAddFace(shared_ptr<Face> face)
+{
+  ndn::nfd::FaceEventNotification faceCreated(ndn::nfd::FACE_EVENT_CREATED,
+                                              face->getId(),
+                                              face->getUri().toString());
+
+  m_notificationStream.postNotification(faceCreated);
+}
+
+void
+FaceManager::onRemoveFace(shared_ptr<Face> face)
+{
+  ndn::nfd::FaceEventNotification faceDestroyed(ndn::nfd::FACE_EVENT_DESTROYED,
+                                                face->getId(),
+                                                face->getUri().toString());
+
+  m_notificationStream.postNotification(faceDestroyed);
+}
+
+
+void
 FaceManager::listFaces(const Interest& request)
 {
   const Name& command = request.getName();
diff --git a/daemon/mgmt/face-manager.hpp b/daemon/mgmt/face-manager.hpp
index 8efb509..6787084 100644
--- a/daemon/mgmt/face-manager.hpp
+++ b/daemon/mgmt/face-manager.hpp
@@ -13,6 +13,7 @@
 #include "mgmt/manager-base.hpp"
 #include "mgmt/config-file.hpp"
 #include "mgmt/face-status-publisher.hpp"
+#include "mgmt/notification-stream.hpp"
 #include "fw/face-table.hpp"
 
 #include <ndn-cpp-dev/management/nfd-face-management-options.hpp>
@@ -69,6 +70,9 @@
   destroyFace(const Name& requestName,
               ndn::nfd::FaceManagementOptions& options);
 
+  void
+  ignoreUnsignedVerb(const Interest& request);
+
   bool
   extractOptions(const Interest& request,
                  ndn::nfd::FaceManagementOptions& extractedOptions);
@@ -81,6 +85,12 @@
   void
   onConnectFailed(const Name& requestName, const std::string& reason);
 
+  void
+  onAddFace(shared_ptr<Face> face);
+
+  void
+  onRemoveFace(shared_ptr<Face> face);
+
 private:
   void
   onConfig(const ConfigSection& configSection, bool isDryRun, const std::string& filename);
@@ -115,6 +125,7 @@
   FactoryMap m_factories;
   FaceTable& m_faceTable;
   FaceStatusPublisher m_statusPublisher;
+  NotificationStream m_notificationStream;
 
   typedef function<void(FaceManager*,
                         const Name&,
@@ -147,6 +158,8 @@
 
   static const Name LIST_COMMAND_PREFIX;
   static const size_t LIST_COMMAND_NCOMPS;
+
+  static const Name EVENTS_COMMAND_PREFIX;
 };
 
 inline bool
@@ -170,6 +183,12 @@
 
 }
 
+inline void
+FaceManager::ignoreUnsignedVerb(const Interest& request)
+{
+  // do nothing
+}
+
 } // namespace nfd
 
 #endif // NFD_MGMT_FACE_MANAGER_HPP
diff --git a/daemon/mgmt/notification-stream.hpp b/daemon/mgmt/notification-stream.hpp
new file mode 100644
index 0000000..2741035
--- /dev/null
+++ b/daemon/mgmt/notification-stream.hpp
@@ -0,0 +1,60 @@
+/* -*- 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_NOTIFICATION_STREAM_HPP
+#define NFD_MGMT_NOTIFICATION_STREAM_HPP
+
+#include "mgmt/app-face.hpp"
+
+namespace nfd {
+
+class NotificationStream
+{
+public:
+  NotificationStream(shared_ptr<AppFace> face, const Name& prefix);
+
+  ~NotificationStream();
+
+  template <typename T> void
+  postNotification(const T& notification);
+
+private:
+  shared_ptr<AppFace> m_face;
+  const Name m_prefix;
+  uint64_t m_sequenceNo;
+};
+
+inline
+NotificationStream::NotificationStream(shared_ptr<AppFace> face, const Name& prefix)
+  : m_face(face)
+  , m_prefix(prefix)
+  , m_sequenceNo(0)
+{
+}
+
+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());
+
+  m_face->sign(*data);
+  m_face->put(*data);
+
+  ++m_sequenceNo;
+}
+
+inline
+NotificationStream::~NotificationStream()
+{
+}
+
+} // namespace nfd
+
+
+#endif // NFD_MGMT_NOTIFICATION_STREAM_HPP
diff --git a/tests/mgmt/face-manager.cpp b/tests/mgmt/face-manager.cpp
index 310a425..cca3671 100644
--- a/tests/mgmt/face-manager.cpp
+++ b/tests/mgmt/face-manager.cpp
@@ -1,12 +1,13 @@
-// /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-// /**
-//  * Copyright (C) 2014 Named Data Networking Project
-//  * See COPYING for copyright and distribution information.
-//  */
+/* -*- 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/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"
@@ -18,6 +19,7 @@
 #include "face-status-publisher-common.hpp"
 
 #include <ndn-cpp-dev/encoding/tlv.hpp>
+#include <ndn-cpp-dev/management/nfd-face-event-notification.hpp>
 
 namespace nfd {
 namespace tests {
@@ -62,7 +64,6 @@
   TestFaceTable(Forwarder& forwarder)
     : FaceTable(forwarder),
       m_addFired(false),
-      m_removeFired(false),
       m_getFired(false),
       m_dummy(make_shared<FaceManagerTestFace>())
   {
@@ -81,12 +82,6 @@
     m_addFired = true;
   }
 
-//  virtual void
-//  remove(shared_ptr<Face> face)
-//  {
-//    m_removeFired = true;
-//  }
-
   virtual shared_ptr<Face>
   get(FaceId id) const
   {
@@ -100,12 +95,6 @@
     return m_addFired;
   }
 
-//  bool
-//  didRemoveFire() const
-//  {
-//    return m_removeFired;
-//  }
-
   bool
   didGetFire() const
   {
@@ -116,7 +105,6 @@
   reset()
   {
     m_addFired = false;
-    m_removeFired = false;
     m_getFired = false;
   }
 
@@ -128,7 +116,6 @@
 
 private:
   bool m_addFired;
-  bool m_removeFired;
   mutable bool m_getFired;
   shared_ptr<FaceManagerTestFace> m_dummy;
 };
@@ -292,12 +279,6 @@
     return m_faceTable.didAddFire();
   }
 
-//  bool
-//  didFaceTableRemoveFire() const
-//  {
-//    return m_faceTable.didRemoveFire();
-//  }
-
   bool
   didFaceTableGetFire() const
   {
@@ -880,13 +861,33 @@
   BOOST_CHECK(didDestroyFaceFire());
 }
 
-class FaceFixture : public TestFaceTableFixture,
-                          public TestFaceManagerCommon,
-                          public FaceManager
+class FaceTableFixture
+{
+public:
+  FaceTableFixture()
+    : m_faceTable(m_forwarder)
+  {
+  }
+
+  virtual
+  ~FaceTableFixture()
+  {
+  }
+
+protected:
+  Forwarder m_forwarder;
+  FaceTable m_faceTable;
+};
+
+class FaceFixture : public FaceTableFixture,
+                    public TestFaceManagerCommon,
+                    public FaceManager
 {
 public:
   FaceFixture()
-    : FaceManager(TestFaceTableFixture::m_faceTable, TestFaceManagerCommon::m_face)
+    : FaceManager(FaceTableFixture::m_faceTable,
+                  TestFaceManagerCommon::m_face)
+    , m_receivedNotification(false)
   {
 
   }
@@ -896,6 +897,76 @@
   {
 
   }
+
+  void
+  callbackDispatch(const Data& response,
+                   const Name& expectedName,
+                   uint32_t expectedCode,
+                   const std::string& expectedText,
+                   const Block& expectedBody,
+                   const ndn::nfd::FaceEventNotification& expectedFaceEvent)
+  {
+    Block payload = response.getContent().blockFromValue();
+    if (payload.type() == ndn::tlv::nfd::ControlResponse)
+      {
+        validateControlResponse(response, expectedName, expectedCode,
+                                expectedText, expectedBody);
+      }
+    else if (payload.type() == ndn::tlv::nfd::FaceEventNotification)
+      {
+        validateFaceEvent(payload, expectedFaceEvent);
+      }
+    else
+      {
+        BOOST_FAIL("Received unknown message type: #" << payload.type());
+      }
+  }
+
+  void
+  callbackDispatch(const Data& response,
+                   const Name& expectedName,
+                   uint32_t expectedCode,
+                   const std::string& expectedText,
+                   const ndn::nfd::FaceEventNotification& expectedFaceEvent)
+  {
+    Block payload = response.getContent().blockFromValue();
+    if (payload.type() == ndn::tlv::nfd::ControlResponse)
+      {
+        validateControlResponse(response, expectedName,
+                                expectedCode, expectedText);
+      }
+    else if (payload.type() == ndn::tlv::nfd::FaceEventNotification)
+      {
+        validateFaceEvent(payload, expectedFaceEvent);
+      }
+    else
+      {
+        BOOST_FAIL("Received unknown message type: #" << payload.type());
+      }
+  }
+
+  void
+  validateFaceEvent(const Block& wire,
+                    const ndn::nfd::FaceEventNotification& expectedFaceEvent)
+  {
+
+    m_receivedNotification = true;
+
+    ndn::nfd::FaceEventNotification notification(wire);
+
+    BOOST_CHECK_EQUAL(notification.getFaceId(), expectedFaceEvent.getFaceId());
+    BOOST_CHECK_EQUAL(notification.getUri(), expectedFaceEvent.getUri());
+    BOOST_CHECK_EQUAL(notification.getEventKind(), expectedFaceEvent.getEventKind());
+  }
+
+  bool
+  didReceiveNotication() const
+  {
+    return m_receivedNotification;
+  }
+
+protected:
+  bool m_receivedNotification;
 };
 
 BOOST_FIXTURE_TEST_CASE(CreateFaceBadUri, AuthorizedCommandFixture<FaceFixture>)
@@ -962,18 +1033,25 @@
 
   ndn::nfd::FaceManagementOptions resultOptions;
   resultOptions.setUri("tcp://127.0.0.1");
-  resultOptions.setFaceId(-1);
+  resultOptions.setFaceId(1);
+
+  shared_ptr<DummyFace> dummy(make_shared<DummyFace>());
+
+  ndn::nfd::FaceEventNotification expectedFaceEvent(ndn::nfd::FACE_EVENT_CREATED,
+                                                    1,
+                                                    dummy->getUri().toString());
 
   Block encodedResultOptions(resultOptions.wireEncode());
 
   getFace()->onReceiveData +=
-    bind(&FaceFixture::validateControlResponse, this, _1,
-         command->getName(), 200, "Success", encodedResultOptions);
+    bind(&FaceFixture::callbackDispatch, this, _1,
+                                        command->getName(), 200, "Success",
+                                        encodedResultOptions, expectedFaceEvent);
 
-  onCreated(command->getName(), options, make_shared<DummyFace>());
+  onCreated(command->getName(), options, dummy);
 
   BOOST_REQUIRE(didCallbackFire());
-  BOOST_CHECK(TestFaceTableFixture::m_faceTable.didAddFire());
+  BOOST_REQUIRE(didReceiveNotication());
 }
 
 BOOST_FIXTURE_TEST_CASE(OnConnectFailed, AuthorizedCommandFixture<FaceFixture>)
@@ -997,14 +1075,18 @@
   onConnectFailed(command->getName(), "unit-test-reason");
 
   BOOST_REQUIRE(didCallbackFire());
-  BOOST_CHECK_EQUAL(TestFaceTableFixture::m_faceTable.didAddFire(), false);
+  BOOST_CHECK_EQUAL(didReceiveNotication(), false);
 }
 
 
 BOOST_FIXTURE_TEST_CASE(DestroyFace, AuthorizedCommandFixture<FaceFixture>)
 {
+  shared_ptr<DummyFace> dummy(make_shared<DummyFace>());
+  FaceTableFixture::m_faceTable.add(dummy);
+
   ndn::nfd::FaceManagementOptions options;
   options.setUri("tcp://127.0.0.1");
+  options.setFaceId(dummy->getId());
 
   Block encodedOptions(options.wireEncode());
 
@@ -1015,15 +1097,18 @@
   shared_ptr<Interest> command(make_shared<Interest>(commandName));
   generateCommand(*command);
 
+  ndn::nfd::FaceEventNotification expectedFaceEvent(ndn::nfd::FACE_EVENT_DESTROYED,
+                                                    dummy->getId(),
+                                                    dummy->getUri().toString());
+
   getFace()->onReceiveData +=
-    bind(&FaceFixture::validateControlResponse, this, _1,
-         command->getName(), 200, "Success");
+    bind(&FaceFixture::callbackDispatch, this, _1,
+         command->getName(), 200, "Success", expectedFaceEvent);
 
   destroyFace(command->getName(), options);
 
   BOOST_REQUIRE(didCallbackFire());
-//  BOOST_CHECK(TestFaceTableFixture::m_faceTable.didRemoveFire());
-  BOOST_CHECK(TestFaceTableFixture::m_faceTable.getDummyFace()->didCloseFire());
+  BOOST_REQUIRE(didReceiveNotication());
 }
 
 class FaceListFixture : public FaceStatusPublisherFixture
@@ -1086,9 +1171,6 @@
   BOOST_REQUIRE(m_finished);
 }
 
-
-
-
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests
diff --git a/tests/mgmt/notification-stream.cpp b/tests/mgmt/notification-stream.cpp
new file mode 100644
index 0000000..1883a8e
--- /dev/null
+++ b/tests/mgmt/notification-stream.cpp
@@ -0,0 +1,123 @@
+/* -*- 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/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;
+};
+
+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");
+
+  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/wscript b/wscript
index af6d67a..c728fa8 100644
--- a/wscript
+++ b/wscript
@@ -69,7 +69,7 @@
     
     conf.load('coverage')
 
-    conf.define('DEFAULT_CONFIG_FILE', '%s/nfd/nfd.conf' % conf.env['SYSCONFDIR'])
+    conf.define('DEFAULT_CONFIG_FILE', '%s/ndn/nfd.conf' % conf.env['SYSCONFDIR'])
 
     conf.write_config_header('daemon/config.hpp')