util: Add BroadcastLink to DummyClientFace

Change-Id: I2bfe3156016a098b91ff375f2bb83b5e4351317c
Refs: #3913
diff --git a/src/util/dummy-client-face.cpp b/src/util/dummy-client-face.cpp
index 989b455..9f9383a 100644
--- a/src/util/dummy-client-face.cpp
+++ b/src/util/dummy-client-face.cpp
@@ -85,6 +85,16 @@
   Signal<Transport, Block> onSendBlock;
 };
 
+struct DummyClientFace::BroadcastLink
+{
+  std::vector<DummyClientFace*> faces;
+};
+
+DummyClientFace::AlreadyLinkedError::AlreadyLinkedError()
+  : Error("Face has already been linked to another face")
+{
+}
+
 DummyClientFace::DummyClientFace(const Options& options/* = DummyClientFace::DEFAULT_OPTIONS*/)
   : Face(make_shared<DummyClientFace::Transport>())
   , m_internalKeyChain(new KeyChain)
@@ -118,6 +128,11 @@
   this->construct(options);
 }
 
+DummyClientFace::~DummyClientFace()
+{
+  unlink();
+}
+
 void
 DummyClientFace::construct(const Options& options)
 {
@@ -159,6 +174,40 @@
     this->enableRegistrationReply();
 
   m_processEventsOverride = options.processEventsOverride;
+
+  enableBroadcastLink();
+}
+
+void
+DummyClientFace::enableBroadcastLink()
+{
+  this->onSendInterest.connect([this] (const Interest& interest) {
+      if (m_bcastLink != nullptr) {
+        for (auto otherFace : m_bcastLink->faces) {
+          if (otherFace != this) {
+            otherFace->receive(interest);
+          }
+        }
+      }
+    });
+  this->onSendData.connect([this] (const Data& data) {
+      if (m_bcastLink != nullptr) {
+        for (auto otherFace : m_bcastLink->faces) {
+          if (otherFace != this) {
+            otherFace->receive(data);
+          }
+        }
+      }
+    });
+  this->onSendNack.connect([this] (const lp::Nack& nack) {
+      if (m_bcastLink != nullptr) {
+        for (auto otherFace : m_bcastLink->faces) {
+          if (otherFace != this) {
+            otherFace->receive(nack);
+          }
+        }
+      }
+    });
 }
 
 void
@@ -241,6 +290,48 @@
 }
 
 void
+DummyClientFace::linkTo(DummyClientFace& other)
+{
+  if (m_bcastLink != nullptr && other.m_bcastLink != nullptr) {
+    if (m_bcastLink != other.m_bcastLink) {
+      // already on different links
+      BOOST_THROW_EXCEPTION(AlreadyLinkedError());
+    }
+  }
+  else if (m_bcastLink == nullptr && other.m_bcastLink != nullptr) {
+    m_bcastLink = other.m_bcastLink;
+    m_bcastLink->faces.push_back(this);
+  }
+  else if (m_bcastLink != nullptr && other.m_bcastLink == nullptr) {
+    other.m_bcastLink = m_bcastLink;
+    m_bcastLink->faces.push_back(&other);
+  }
+  else {
+    m_bcastLink = other.m_bcastLink = make_shared<BroadcastLink>();
+    m_bcastLink->faces.push_back(this);
+    m_bcastLink->faces.push_back(&other);
+  }
+}
+
+void
+DummyClientFace::unlink()
+{
+  if (m_bcastLink == nullptr) {
+    return;
+  }
+
+  auto it = std::find(m_bcastLink->faces.begin(), m_bcastLink->faces.end(), this);
+  BOOST_ASSERT(it != m_bcastLink->faces.end());
+  m_bcastLink->faces.erase(it);
+
+  if (m_bcastLink->faces.size() == 1) {
+    m_bcastLink->faces[0]->m_bcastLink = nullptr;
+    m_bcastLink->faces.clear();
+  }
+  m_bcastLink = nullptr;
+}
+
+void
 DummyClientFace::doProcessEvents(time::milliseconds timeout, bool keepThread)
 {
   if (m_processEventsOverride != nullptr) {
diff --git a/src/util/dummy-client-face.hpp b/src/util/dummy-client-face.hpp
index 781a797..7d40f65 100644
--- a/src/util/dummy-client-face.hpp
+++ b/src/util/dummy-client-face.hpp
@@ -72,6 +72,12 @@
     std::function<void(time::milliseconds)> processEventsOverride;
   };
 
+  class AlreadyLinkedError : public Error
+  {
+  public:
+    AlreadyLinkedError();
+  };
+
   /** \brief Create a dummy face with internal IO service
    */
   explicit
@@ -92,6 +98,8 @@
   DummyClientFace(boost::asio::io_service& ioService, KeyChain& keyChain,
                   const Options& options = Options());
 
+  ~DummyClientFace();
+
   /** \brief cause the Face to receive an interest
    */
   void
@@ -107,6 +115,16 @@
   void
   receive(const lp::Nack& nack);
 
+  /** \brief link another DummyClientFace through a broadcast media
+   */
+  void
+  linkTo(DummyClientFace& other);
+
+  /** \brief unlink the broadcast media if previously linked
+   */
+  void
+  unlink();
+
 private:
   class Transport;
 
@@ -114,6 +132,9 @@
   construct(const Options& options);
 
   void
+  enableBroadcastLink();
+
+  void
   enablePacketLogging();
 
   void
@@ -165,7 +186,9 @@
    */
   Signal<DummyClientFace, lp::Nack> onSendNack;
 
-private:
+NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  struct BroadcastLink;
+  shared_ptr<BroadcastLink> m_bcastLink;
   std::unique_ptr<KeyChain> m_internalKeyChain;
   KeyChain& m_keyChain;
   std::function<void(time::milliseconds)> m_processEventsOverride;