mgmt: send FACE_EVENT_UP and FACE_EVENT_DOWN notifications

refs #3794

Change-Id: I98cc9db2652454d8fd09975b619433046e9bc7ca
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index 0ce16c5..8a9bb1c 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -33,8 +33,6 @@
 
 #include <ndn-cxx/lp/tags.hpp>
 #include <ndn-cxx/mgmt/nfd/channel-status.hpp>
-#include <ndn-cxx/mgmt/nfd/face-status.hpp>
-#include <ndn-cxx/mgmt/nfd/face-event-notification.hpp>
 
 #ifdef HAVE_UNIX_SOCKETS
 #include "face/unix-stream-factory.hpp"
@@ -77,8 +75,13 @@
   registerStatusDatasetHandler("query", bind(&FaceManager::queryFaces, this, _1, _2, _3));
 
   m_postNotification = registerNotificationStream("events");
-  m_faceAddConn = m_faceTable.afterAdd.connect(bind(&FaceManager::notifyAddFace, this, _1));
-  m_faceRemoveConn = m_faceTable.beforeRemove.connect(bind(&FaceManager::notifyRemoveFace, this, _1));
+  m_faceAddConn = m_faceTable.afterAdd.connect([this] (const Face& face) {
+    connectFaceStateChangeSignal(face);
+    notifyFaceEvent(face, ndn::nfd::FACE_EVENT_CREATED);
+  });
+  m_faceRemoveConn = m_faceTable.beforeRemove.connect([this] (const Face& face) {
+    notifyFaceEvent(face, ndn::nfd::FACE_EVENT_DESTROYED);
+  });
 }
 
 void
@@ -493,13 +496,6 @@
         .setNInBytes(counters.nInBytes)
         .setNOutBytes(counters.nOutBytes);
 
-  // Set Flag bits
-  auto linkService = dynamic_cast<face::GenericLinkService*>(face.getLinkService());
-  if (linkService != nullptr) {
-    auto linkServiceOptions = linkService->getOptions();
-    status.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, linkServiceOptions.allowLocalFields);
-  }
-
   return status;
 }
 
@@ -513,26 +509,43 @@
         .setFaceScope(face.getScope())
         .setFacePersistency(face.getPersistency())
         .setLinkType(face.getLinkType());
+
+  // Set Flag bits
+  auto linkService = dynamic_cast<face::GenericLinkService*>(face.getLinkService());
+  if (linkService != nullptr) {
+    auto linkServiceOptions = linkService->getOptions();
+    traits.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, linkServiceOptions.allowLocalFields);
+  }
 }
 
 void
-FaceManager::notifyAddFace(const Face& face)
+FaceManager::notifyFaceEvent(const Face& face, ndn::nfd::FaceEventKind kind)
 {
   ndn::nfd::FaceEventNotification notification;
-  notification.setKind(ndn::nfd::FACE_EVENT_CREATED);
+  notification.setKind(kind);
   collectFaceProperties(face, notification);
 
   m_postNotification(notification.wireEncode());
 }
 
 void
-FaceManager::notifyRemoveFace(const Face& face)
+FaceManager::connectFaceStateChangeSignal(const Face& face)
 {
-  ndn::nfd::FaceEventNotification notification;
-  notification.setKind(ndn::nfd::FACE_EVENT_DESTROYED);
-  collectFaceProperties(face, notification);
+  FaceId faceId = face.getId();
+  m_faceStateChangeConn[faceId] = face.afterStateChange.connect(
+    [this, faceId] (face::FaceState oldState, face::FaceState newState) {
+      const Face& face = *m_faceTable.get(faceId);
 
-  m_postNotification(notification.wireEncode());
+      if (newState == face::FaceState::UP) {
+        notifyFaceEvent(face, ndn::nfd::FACE_EVENT_UP);
+      }
+      else if (newState == face::FaceState::DOWN) {
+        notifyFaceEvent(face, ndn::nfd::FACE_EVENT_DOWN);
+      }
+      else if (newState == face::FaceState::CLOSED) {
+        m_faceStateChangeConn.erase(faceId);
+      }
+    });
 }
 
 void
diff --git a/daemon/mgmt/face-manager.hpp b/daemon/mgmt/face-manager.hpp
index 3a20dbd..c4a2c48 100644
--- a/daemon/mgmt/face-manager.hpp
+++ b/daemon/mgmt/face-manager.hpp
@@ -29,6 +29,7 @@
 #include "nfd-manager-base.hpp"
 #include <ndn-cxx/mgmt/nfd/face-status.hpp>
 #include <ndn-cxx/mgmt/nfd/face-query-filter.hpp>
+#include <ndn-cxx/mgmt/nfd/face-event-notification.hpp>
 #include "face/face.hpp"
 
 namespace nfd {
@@ -138,10 +139,10 @@
 
 private: // NotificationStream
   void
-  notifyAddFace(const Face& face);
+  notifyFaceEvent(const Face& face, ndn::nfd::FaceEventKind kind);
 
   void
-  notifyRemoveFace(const Face& face);
+  connectFaceStateChangeSignal(const Face& face);
 
 private: // configuration
   void
@@ -168,6 +169,7 @@
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   std::map<std::string /*protocol*/, shared_ptr<ProtocolFactory>> m_factories;
   FaceTable& m_faceTable;
+  std::map<FaceId, signal::ScopedConnection> m_faceStateChangeConn;
 
 private:
   ndn::mgmt::PostNotification m_postNotification;
diff --git a/tests/daemon/mgmt/face-manager.t.cpp b/tests/daemon/mgmt/face-manager.t.cpp
index 325b344..8a4907c 100644
--- a/tests/daemon/mgmt/face-manager.t.cpp
+++ b/tests/daemon/mgmt/face-manager.t.cpp
@@ -30,6 +30,7 @@
 
 #include "nfd-manager-common-fixture.hpp"
 #include "../face/dummy-face.hpp"
+#include "../face/dummy-transport.hpp"
 
 #include <ndn-cxx/encoding/tlv.hpp>
 #include <ndn-cxx/mgmt/nfd/channel-status.hpp>
@@ -284,7 +285,7 @@
 
   std::map<std::string, shared_ptr<TestChannel>> addedChannels;
   size_t nEntries = 404;
-  for (size_t i = 0 ; i < nEntries ; i ++) {
+  for (size_t i = 0; i < nEntries; i++) {
     auto channel = factory->addChannel("test" + boost::lexical_cast<std::string>(i) + "://");
     addedChannels[channel->getUri().toString()] = channel;
   }
@@ -309,49 +310,113 @@
 
 BOOST_AUTO_TEST_SUITE(Notifications)
 
-BOOST_AUTO_TEST_CASE(FaceEventNotification)
+BOOST_AUTO_TEST_CASE(FaceEventCreated)
 {
-  auto addedFace = addFace(); // trigger FACE_EVENT_CREATED notification
-  BOOST_CHECK_NE(addedFace->getId(), -1);
-  int64_t faceId = addedFace->getId();
+  auto face = addFace(); // trigger FACE_EVENT_CREATED notification
+  BOOST_CHECK_NE(face->getId(), face::INVALID_FACEID);
+  FaceId faceId = face->getId();
+
+  BOOST_CHECK_EQUAL(m_manager.m_faceStateChangeConn.count(faceId), 1);
 
   // check notification
-  {
-    Block payload;
-    ndn::nfd::FaceEventNotification notification;
-    BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
-    BOOST_CHECK_NO_THROW(payload = m_responses[0].getContent().blockFromValue());
-    BOOST_CHECK_EQUAL(payload.type(), ndn::tlv::nfd::FaceEventNotification);
-    BOOST_CHECK_NO_THROW(notification.wireDecode(payload));
-    BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_CREATED);
-    BOOST_CHECK_EQUAL(notification.getFaceId(), faceId);
-    BOOST_CHECK_EQUAL(notification.getRemoteUri(), addedFace->getRemoteUri().toString());
-    BOOST_CHECK_EQUAL(notification.getLocalUri(), addedFace->getLocalUri().toString());
-    BOOST_CHECK_EQUAL(notification.getFaceScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
-    BOOST_CHECK_EQUAL(notification.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
-    BOOST_CHECK_EQUAL(notification.getLinkType(), ndn::nfd::LinkType::LINK_TYPE_POINT_TO_POINT);
-  }
+  Block payload;
+  ndn::nfd::FaceEventNotification notification;
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_NO_THROW(payload = m_responses.back().getContent().blockFromValue());
+  BOOST_CHECK_EQUAL(payload.type(), ndn::tlv::nfd::FaceEventNotification);
+  BOOST_CHECK_NO_THROW(notification.wireDecode(payload));
+  BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_CREATED);
+  BOOST_CHECK_EQUAL(notification.getFaceId(), faceId);
+  BOOST_CHECK_EQUAL(notification.getRemoteUri(), face->getRemoteUri().toString());
+  BOOST_CHECK_EQUAL(notification.getLocalUri(), face->getLocalUri().toString());
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), ndn::nfd::LinkType::LINK_TYPE_POINT_TO_POINT);
+  BOOST_CHECK_EQUAL(notification.getFlags(), 0x0);
+}
 
-  addedFace->close(); // trigger FaceDestroy FACE_EVENT_DESTROYED
+BOOST_AUTO_TEST_CASE(FaceEventDownUp)
+{
+  auto face = addFace();
+  BOOST_CHECK_NE(face->getId(), face::INVALID_FACEID);
+  FaceId faceId = face->getId();
+
+  // trigger FACE_EVENT_DOWN notification
+  dynamic_cast<face::tests::DummyTransport*>(face->getTransport())->setState(face::FaceState::DOWN);
   advanceClocks(time::milliseconds(1), 10);
+  BOOST_CHECK_EQUAL(face->getState(), face::FaceState::DOWN);
 
   // check notification
   {
     Block payload;
     ndn::nfd::FaceEventNotification notification;
     BOOST_REQUIRE_EQUAL(m_responses.size(), 2);
-    BOOST_CHECK_NO_THROW(payload = m_responses[1].getContent().blockFromValue());
+    BOOST_CHECK_NO_THROW(payload = m_responses.back().getContent().blockFromValue());
     BOOST_CHECK_EQUAL(payload.type(), ndn::tlv::nfd::FaceEventNotification);
     BOOST_CHECK_NO_THROW(notification.wireDecode(payload));
-    BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_DESTROYED);
+    BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_DOWN);
     BOOST_CHECK_EQUAL(notification.getFaceId(), faceId);
-    BOOST_CHECK_EQUAL(notification.getRemoteUri(), addedFace->getRemoteUri().toString());
-    BOOST_CHECK_EQUAL(notification.getLocalUri(), addedFace->getLocalUri().toString());
+    BOOST_CHECK_EQUAL(notification.getRemoteUri(), face->getRemoteUri().toString());
+    BOOST_CHECK_EQUAL(notification.getLocalUri(), face->getLocalUri().toString());
     BOOST_CHECK_EQUAL(notification.getFaceScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
     BOOST_CHECK_EQUAL(notification.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
     BOOST_CHECK_EQUAL(notification.getLinkType(), ndn::nfd::LinkType::LINK_TYPE_POINT_TO_POINT);
+    BOOST_CHECK_EQUAL(notification.getFlags(), 0x0);
   }
-  BOOST_CHECK_EQUAL(addedFace->getId(), face::INVALID_FACEID);
+
+  // trigger FACE_EVENT_UP notification
+  dynamic_cast<face::tests::DummyTransport*>(face->getTransport())->setState(face::FaceState::UP);
+  advanceClocks(time::milliseconds(1), 10);
+  BOOST_CHECK_EQUAL(face->getState(), face::FaceState::UP);
+
+  // check notification
+  {
+    Block payload;
+    ndn::nfd::FaceEventNotification notification;
+    BOOST_REQUIRE_EQUAL(m_responses.size(), 3);
+    BOOST_CHECK_NO_THROW(payload = m_responses.back().getContent().blockFromValue());
+    BOOST_CHECK_EQUAL(payload.type(), ndn::tlv::nfd::FaceEventNotification);
+    BOOST_CHECK_NO_THROW(notification.wireDecode(payload));
+    BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_UP);
+    BOOST_CHECK_EQUAL(notification.getFaceId(), faceId);
+    BOOST_CHECK_EQUAL(notification.getRemoteUri(), face->getRemoteUri().toString());
+    BOOST_CHECK_EQUAL(notification.getLocalUri(), face->getLocalUri().toString());
+    BOOST_CHECK_EQUAL(notification.getFaceScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+    BOOST_CHECK_EQUAL(notification.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+    BOOST_CHECK_EQUAL(notification.getLinkType(), ndn::nfd::LinkType::LINK_TYPE_POINT_TO_POINT);
+    BOOST_CHECK_EQUAL(notification.getFlags(), 0x0);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(FaceEventDestroyed)
+{
+  auto face = addFace();
+  BOOST_CHECK_NE(face->getId(), face::INVALID_FACEID);
+  FaceId faceId = face->getId();
+
+  BOOST_CHECK_EQUAL(m_manager.m_faceStateChangeConn.count(faceId), 1);
+
+  face->close(); // trigger FaceDestroy FACE_EVENT_DESTROYED
+  advanceClocks(time::milliseconds(1), 10);
+
+  // check notification
+  Block payload;
+  ndn::nfd::FaceEventNotification notification;
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 2);
+  BOOST_CHECK_NO_THROW(payload = m_responses.back().getContent().blockFromValue());
+  BOOST_CHECK_EQUAL(payload.type(), ndn::tlv::nfd::FaceEventNotification);
+  BOOST_CHECK_NO_THROW(notification.wireDecode(payload));
+  BOOST_CHECK_EQUAL(notification.getKind(), ndn::nfd::FACE_EVENT_DESTROYED);
+  BOOST_CHECK_EQUAL(notification.getFaceId(), faceId);
+  BOOST_CHECK_EQUAL(notification.getRemoteUri(), face->getRemoteUri().toString());
+  BOOST_CHECK_EQUAL(notification.getLocalUri(), face->getLocalUri().toString());
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), ndn::nfd::LinkType::LINK_TYPE_POINT_TO_POINT);
+  BOOST_CHECK_EQUAL(notification.getFlags(), 0x0);
+
+  BOOST_CHECK_EQUAL(face->getId(), face::INVALID_FACEID);
+  BOOST_CHECK_EQUAL(m_manager.m_faceStateChangeConn.count(faceId), 0);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // Notifications