util: Extend DummyClientFace to process LocalControlHeader

Change-Id: I5ab397ae585fcdc2a5e231e8ad9a819bccfb3baa
Refs: #2510
diff --git a/src/detail/face-impl.hpp b/src/detail/face-impl.hpp
index 460d316..6844813 100644
--- a/src/detail/face-impl.hpp
+++ b/src/detail/face-impl.hpp
@@ -149,9 +149,9 @@
   {
     this->ensureConnected();
 
-    if (!data->getLocalControlHeader().empty(false, true))
+    if (!data->getLocalControlHeader().empty(false, false))
       {
-        m_face.m_transport->send(data->getLocalControlHeader().wireEncode(*data, false, true),
+        m_face.m_transport->send(data->getLocalControlHeader().wireEncode(*data, false, false),
                                  data->wireEncode());
       }
     else
diff --git a/src/face.cpp b/src/face.cpp
index 57f9605..744ee0d 100644
--- a/src/face.cpp
+++ b/src/face.cpp
@@ -483,8 +483,7 @@
 
   if (block.type() == tlv::Interest)
     {
-      shared_ptr<Interest> interest = make_shared<Interest>();
-      interest->wireDecode(block);
+      shared_ptr<Interest> interest = make_shared<Interest>(block);
       if (&block != &blockFromDaemon)
         interest->getLocalControlHeader().wireDecode(blockFromDaemon);
 
@@ -492,8 +491,7 @@
     }
   else if (block.type() == tlv::Data)
     {
-      shared_ptr<Data> data = make_shared<Data>();
-      data->wireDecode(block);
+      shared_ptr<Data> data = make_shared<Data>(block);
       if (&block != &blockFromDaemon)
         data->getLocalControlHeader().wireDecode(blockFromDaemon);
 
diff --git a/src/util/dummy-client-face.cpp b/src/util/dummy-client-face.cpp
index 852642d..f5e1172 100644
--- a/src/util/dummy-client-face.cpp
+++ b/src/util/dummy-client-face.cpp
@@ -63,7 +63,11 @@
   virtual void
   send(const Block& header, const Block& payload)
   {
-    this->send(payload);
+    EncodingBuffer encoder(header.size() + payload.size(), header.size() + payload.size());
+    encoder.appendByteArray(header.wire(), header.size());
+    encoder.appendByteArray(payload.wire(), payload.size());
+
+    this->send(encoder.block());
   }
 
   boost::asio::io_service&
@@ -94,13 +98,21 @@
 void
 DummyClientFace::construct(const Options& options)
 {
-  m_transport->onSendBlock.connect([this] (const Block& wire) {
-    if (wire.type() == tlv::Interest) {
-      shared_ptr<Interest> interest = make_shared<Interest>(wire);
+  m_transport->onSendBlock.connect([this] (const Block& blockFromDaemon) {
+    const Block& block = nfd::LocalControlHeader::getPayload(blockFromDaemon);
+
+    if (block.type() == tlv::Interest) {
+      shared_ptr<Interest> interest = make_shared<Interest>(block);
+      if (&block != &blockFromDaemon)
+        interest->getLocalControlHeader().wireDecode(blockFromDaemon);
+
       onSendInterest(*interest);
     }
-    else if (wire.type() == tlv::Data) {
-      shared_ptr<Data> data = make_shared<Data>(wire);
+    else if (block.type() == tlv::Data) {
+      shared_ptr<Data> data = make_shared<Data>(block);
+      if (&block != &blockFromDaemon)
+        data->getLocalControlHeader().wireDecode(blockFromDaemon);
+
       onSendData(*data);
     }
   });
@@ -156,7 +168,21 @@
 void
 DummyClientFace::receive(const Packet& packet)
 {
-  m_transport->receive(packet.wireEncode());
+  // do not restrict what injected control header can contain
+  if (!packet.getLocalControlHeader().empty(true, true)) {
+
+    Block header = packet.getLocalControlHeader().wireEncode(packet, true, true);
+    Block payload = packet.wireEncode();
+
+    EncodingBuffer encoder(header.size() + payload.size(), header.size() + payload.size());
+    encoder.appendByteArray(header.wire(), header.size());
+    encoder.appendByteArray(payload.wire(), payload.size());
+
+    m_transport->receive(encoder.block());
+  }
+  else {
+    m_transport->receive(packet.wireEncode());
+  }
 }
 
 template void
diff --git a/tests/unit-tests/test-face.cpp b/tests/unit-tests/test-face.cpp
index 33c3643..b519d5c 100644
--- a/tests/unit-tests/test-face.cpp
+++ b/tests/unit-tests/test-face.cpp
@@ -362,6 +362,84 @@
   BOOST_CHECK_EQUAL(nRegSuccesses, 1);
 }
 
+BOOST_AUTO_TEST_CASE(ExpressInterestWithLocalControlHeader)
+{
+  Interest i("/Hello/World");
+  i.setNextHopFaceId(1000);
+  i.setIncomingFaceId(2000);
+
+  face->expressInterest(i, bind([]{}), bind([]{}));
+  advanceClocks(time::milliseconds(10));
+
+  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
+  // only NextHopFaceId is allowed to go out
+  BOOST_CHECK(face->sentInterests[0].getLocalControlHeader().hasNextHopFaceId());
+  BOOST_CHECK(!face->sentInterests[0].getLocalControlHeader().hasIncomingFaceId());
+  BOOST_CHECK_EQUAL(face->sentInterests[0].getNextHopFaceId(), 1000);
+}
+
+BOOST_AUTO_TEST_CASE(ReceiveInterestWithLocalControlHeader)
+{
+  face->setInterestFilter("/Hello/World",
+                          [] (const InterestFilter&, const Interest& i) {
+                            BOOST_CHECK(i.getLocalControlHeader().hasNextHopFaceId());
+                            BOOST_CHECK(i.getLocalControlHeader().hasIncomingFaceId());
+                            BOOST_CHECK_EQUAL(i.getNextHopFaceId(), 1000);
+                            BOOST_CHECK_EQUAL(i.getIncomingFaceId(), 2000);
+                          },
+                          bind([]{}),
+                          bind([] {
+                              BOOST_FAIL("Unexpected setInterestFilter failure");
+                            }));
+  advanceClocks(time::milliseconds(10));
+
+  Interest i("/Hello/World/!");
+  i.setNextHopFaceId(1000);
+  i.setIncomingFaceId(2000);
+
+  face->receive(i);
+  advanceClocks(time::milliseconds(10));
+}
+
+BOOST_AUTO_TEST_CASE(PutDataWithLocalControlHeader)
+{
+  shared_ptr<Data> d = util::makeData("/Bye/World/!");
+  d->setIncomingFaceId(2000);
+  d->getLocalControlHeader().setNextHopFaceId(1000); // setNextHopFaceId is intentionally
+                                                     // not exposed directly
+
+  face->put(*d);
+  advanceClocks(time::milliseconds(10));
+
+  BOOST_REQUIRE_EQUAL(face->sentDatas.size(), 1);
+  BOOST_CHECK(!face->sentDatas[0].getLocalControlHeader().hasNextHopFaceId());
+  BOOST_CHECK(!face->sentDatas[0].getLocalControlHeader().hasIncomingFaceId());
+}
+
+BOOST_AUTO_TEST_CASE(ReceiveDataWithLocalControlHeader)
+{
+  face->expressInterest(Interest("/Hello/World", time::milliseconds(50)),
+                        [&] (const Interest& i, const Data& d) {
+                          BOOST_CHECK(d.getLocalControlHeader().hasNextHopFaceId());
+                          BOOST_CHECK(d.getLocalControlHeader().hasIncomingFaceId());
+                          BOOST_CHECK_EQUAL(d.getIncomingFaceId(), 2000);
+                          BOOST_CHECK_EQUAL(d.getLocalControlHeader().getNextHopFaceId(), 1000);
+                        },
+                        bind([] {
+                            BOOST_FAIL("Unexpected timeout");
+                          }));
+
+  advanceClocks(time::milliseconds(10));
+
+  shared_ptr<Data> d = util::makeData("/Hello/World/!");
+  d->setIncomingFaceId(2000);
+  d->getLocalControlHeader().setNextHopFaceId(1000); // setNextHopFaceId is intentionally
+                                                     // not exposed directly
+  face->receive(*d);
+
+  advanceClocks(time::milliseconds(10), 100);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // tests