face: GenericLinkService encoding/decoding

refs #3104

Change-Id: I26e83cd1dd5dc87ebdc040105ab1bad4afdba5f7
diff --git a/daemon/face/generic-link-service.cpp b/daemon/face/generic-link-service.cpp
index 79010e6..4fa4c1c 100644
--- a/daemon/face/generic-link-service.cpp
+++ b/daemon/face/generic-link-service.cpp
@@ -30,10 +30,23 @@
 
 NFD_LOG_INIT("GenericLinkService");
 
+GenericLinkService::Options::Options()
+  : allowLocalFields(false)
+{
+}
+
+GenericLinkService::GenericLinkService(const GenericLinkService::Options& options)
+  : m_options(options)
+{
+}
+
 void
 GenericLinkService::doSendInterest(const Interest& interest)
 {
   lp::Packet lpPacket(interest.wireEncode());
+  if (m_options.allowLocalFields) {
+    encodeLocalFields(interest, lpPacket);
+  }
   Transport::Packet packet;
   packet.packet = lpPacket.wireEncode();
   sendPacket(std::move(packet));
@@ -43,6 +56,9 @@
 GenericLinkService::doSendData(const Data& data)
 {
   lp::Packet lpPacket(data.wireEncode());
+  if (m_options.allowLocalFields) {
+    encodeLocalFields(data, lpPacket);
+  }
   Transport::Packet packet;
   packet.packet = lpPacket.wireEncode();
   sendPacket(std::move(packet));
@@ -53,40 +69,188 @@
 {
   lp::Packet lpPacket(nack.getInterest().wireEncode());
   lpPacket.add<lp::NackField>(nack.getHeader());
+  if (m_options.allowLocalFields) {
+    encodeLocalFields(nack.getInterest(), lpPacket);
+  }
   Transport::Packet packet;
   packet.packet = lpPacket.wireEncode();
   sendPacket(std::move(packet));
 }
 
+bool
+GenericLinkService::encodeLocalFields(const Interest& interest, lp::Packet& lpPacket)
+{
+  if (interest.getLocalControlHeader().hasIncomingFaceId()) {
+    lpPacket.add<lp::IncomingFaceIdField>(interest.getIncomingFaceId());
+  }
+
+  if (interest.getLocalControlHeader().hasCachingPolicy()) {
+    // Packet must be dropped
+    return false;
+  }
+
+  return true;
+}
+
+bool
+GenericLinkService::encodeLocalFields(const Data& data, lp::Packet& lpPacket)
+{
+  if (data.getLocalControlHeader().hasIncomingFaceId()) {
+    lpPacket.add<lp::IncomingFaceIdField>(data.getIncomingFaceId());
+  }
+
+  if (data.getLocalControlHeader().hasCachingPolicy()) {
+    switch (data.getCachingPolicy()) {
+      case ndn::nfd::LocalControlHeader::CachingPolicy::NO_CACHE: {
+        lp::CachePolicy cachePolicy;
+        cachePolicy.setPolicy(lp::CachePolicyType::NO_CACHE);
+        lpPacket.add<lp::CachePolicyField>(cachePolicy);
+        break;
+      }
+      default: {
+        break;
+      }
+    }
+  }
+
+  return true;
+}
+
 void
 GenericLinkService::doReceivePacket(Transport::Packet&& packet)
 {
-  lp::Packet lpPacket(packet.packet);
-  ndn::Buffer::const_iterator fragBegin, fragEnd;
-  std::tie(fragBegin, fragEnd) = lpPacket.get<lp::FragmentField>();
-  Block netPacket(&*fragBegin, std::distance(fragBegin, fragEnd));
+  lp::Packet pkt(packet.packet);
 
-  // Forwarding expects Interest and Data to be created with make_shared,
-  // but has no such requirement on Nack.
-  switch (netPacket.type()) {
-    case tlv::Interest: {
-      auto interest = make_shared<Interest>(netPacket);
-      if (lpPacket.has<lp::NackField>()) {
-        lp::Nack nack(std::move(*interest));
-        nack.setHeader(lpPacket.get<lp::NackField>());
-        receiveNack(nack);
-      }
-      else {
-        receiveInterest(*interest);
-      }
-      break;
-    }
-    case tlv::Data: {
-      auto data = make_shared<Data>(netPacket);
-      receiveData(*data);
-      break;
+  if (pkt.has<lp::FragIndexField>() || pkt.has<lp::FragCountField>()) {
+    NFD_LOG_FACE_WARN("received fragment, but reassembly not implemented: DROP");
+    return;
+  }
+
+  try {
+    ndn::Buffer::const_iterator fragBegin, fragEnd;
+    std::tie(fragBegin, fragEnd) = pkt.get<lp::FragmentField>();
+    Block netPkt(&*fragBegin, std::distance(fragBegin, fragEnd));
+
+    switch (netPkt.type()) {
+      case tlv::Interest:
+        if (pkt.has<lp::NackField>()) {
+          this->decodeNack(netPkt, pkt);
+        }
+        else {
+          this->decodeInterest(netPkt, pkt);
+        }
+        break;
+      case tlv::Data:
+        this->decodeData(netPkt, pkt);
+        break;
+      default:
+        NFD_LOG_FACE_WARN("unrecognized network-layer packet TLV-TYPE " << netPkt.type() << ": DROP");
+        return;
     }
   }
+  catch (const tlv::Error& e) {
+    NFD_LOG_FACE_WARN("packet parse error (" << e.what() << "): DROP");
+  }
+}
+
+
+void
+GenericLinkService::decodeInterest(const Block& netPkt, const lp::Packet& firstPkt)
+{
+  BOOST_ASSERT(netPkt.type() == tlv::Interest);
+  BOOST_ASSERT(!firstPkt.has<lp::NackField>());
+
+  // forwarding expects Interest to be created with make_shared
+  auto interest = make_shared<Interest>(netPkt);
+
+  if (firstPkt.has<lp::NextHopFaceIdField>()) {
+    if (m_options.allowLocalFields) {
+      interest->setNextHopFaceId(firstPkt.get<lp::NextHopFaceIdField>());
+    }
+    else {
+      NFD_LOG_FACE_WARN("received NextHopFaceId, but local fields disabled: DROP");
+      return;
+    }
+  }
+
+  if (firstPkt.has<lp::CachePolicyField>()) {
+    NFD_LOG_FACE_WARN("received CachePolicy with Interest: DROP");
+    return;
+  }
+
+  if (firstPkt.has<lp::IncomingFaceIdField>()) {
+    NFD_LOG_FACE_WARN("received IncomingFaceId: IGNORE");
+  }
+
+  this->receiveInterest(*interest);
+}
+
+void
+GenericLinkService::decodeData(const Block& netPkt, const lp::Packet& firstPkt)
+{
+  BOOST_ASSERT(netPkt.type() == tlv::Data);
+
+  // forwarding expects Data to be created with make_shared
+  auto data = make_shared<Data>(netPkt);
+
+  if (firstPkt.has<lp::NackField>()) {
+    NFD_LOG_FACE_WARN("received Nack with Data: DROP");
+    return;
+  }
+
+  if (firstPkt.has<lp::NextHopFaceIdField>()) {
+    NFD_LOG_FACE_WARN("received NextHopFaceId with Data: DROP");
+    return;
+  }
+
+  if (firstPkt.has<lp::CachePolicyField>()) {
+    if (m_options.allowLocalFields) {
+      lp::CachePolicyType policy = firstPkt.get<lp::CachePolicyField>().getPolicy();
+      switch (policy) {
+        case lp::CachePolicyType::NO_CACHE:
+          data->setCachingPolicy(ndn::nfd::LocalControlHeader::CachingPolicy::NO_CACHE);
+          break;
+        default:
+          NFD_LOG_FACE_WARN("unrecognized CachePolicyType " << policy << ": DROP");
+          return;
+      }
+    }
+    else {
+      NFD_LOG_FACE_WARN("received CachePolicy, but local fields disabled: IGNORE");
+    }
+  }
+
+  if (firstPkt.has<lp::IncomingFaceIdField>()) {
+    NFD_LOG_FACE_WARN("received IncomingFaceId: IGNORE");
+  }
+
+  this->receiveData(*data);
+}
+
+void
+GenericLinkService::decodeNack(const Block& netPkt, const lp::Packet& firstPkt)
+{
+  BOOST_ASSERT(netPkt.type() == tlv::Interest);
+  BOOST_ASSERT(firstPkt.has<lp::NackField>());
+
+  lp::Nack nack((Interest(netPkt)));
+  nack.setHeader(firstPkt.get<lp::NackField>());
+
+  if (firstPkt.has<lp::NextHopFaceIdField>()) {
+    NFD_LOG_FACE_WARN("received NextHopFaceId with Nack: DROP");
+    return;
+  }
+
+  if (firstPkt.has<lp::CachePolicyField>()) {
+    NFD_LOG_FACE_WARN("received CachePolicy with Nack: DROP");
+    return;
+  }
+
+  if (firstPkt.has<lp::IncomingFaceIdField>()) {
+    NFD_LOG_FACE_WARN("received IncomingFaceId: IGNORE");
+  }
+
+  this->receiveNack(nack);
 }
 
 } // namespace face
diff --git a/daemon/face/generic-link-service.hpp b/daemon/face/generic-link-service.hpp
index 8d91f88..ae3328f 100644
--- a/daemon/face/generic-link-service.hpp
+++ b/daemon/face/generic-link-service.hpp
@@ -36,10 +36,40 @@
 namespace nfd {
 namespace face {
 
-/** \brief generic LinkService
+/** \brief GenericLinkService is a LinkService that implements the NDNLPv2 protocol
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/NDNLPv2
  */
 class GenericLinkService : public LinkService
 {
+public:
+  /** \brief Options that control the behavior of GenericLinkService
+   */
+  class Options
+  {
+  public:
+    Options();
+
+  public:
+    // TODO #3171: fragmentation and reassembly options
+
+    /** \brief enables encoding of IncomingFaceId, and decoding of NextHopFaceId and CachePolicy
+     */
+    bool allowLocalFields;
+  };
+
+  explicit
+  GenericLinkService(const Options& options = Options());
+
+  /** \brief get Options used by GenericLinkService
+   */
+  const Options&
+  getOptions() const;
+
+  /** \brief sets Options used by GenericLinkService
+   */
+  void
+  setOptions(const Options& options);
+
 private: // send path entrypoint
   /** \brief sends Interest
    */
@@ -62,9 +92,71 @@
    */
   void
   doReceivePacket(Transport::Packet&& packet) DECL_OVERRIDE;
+
+private: // encoding and decoding
+  /** \brief encode IncomingFaceId into LpPacket and verify local fields
+   */
+  static bool
+  encodeLocalFields(const Interest& interest, lp::Packet& lpPacket);
+
+  /** \brief encode CachingPolicy and IncomingFaceId into LpPacket and verify local fields
+   */
+  static bool
+  encodeLocalFields(const Data& data, lp::Packet& lpPacket);
+
+  /** \brief decode incoming Interest
+   *  \param netPkt reassembled network-layer packet; TLV-TYPE must be Interest
+   *  \param firstPkt LpPacket of first fragment; must not have Nack field
+   *
+   *  If decoding is successful, receiveInterest signal is emitted;
+   *  otherwise, a warning is logged.
+   *
+   *  \throw tlv::Error parse error in an LpHeader field
+   */
+  void
+  decodeInterest(const Block& netPkt, const lp::Packet& firstPkt);
+
+  /** \brief decode incoming Interest
+   *  \param netPkt reassembled network-layer packet; TLV-TYPE must be Data
+   *  \param firstPkt LpPacket of first fragment
+   *
+   *  If decoding is successful, receiveData signal is emitted;
+   *  otherwise, a warning is logged.
+   *
+   *  \throw tlv::Error parse error in an LpHeader field
+   */
+  void
+  decodeData(const Block& netPkt, const lp::Packet& firstPkt);
+
+  /** \brief decode incoming Interest
+   *  \param netPkt reassembled network-layer packet; TLV-TYPE must be Interest
+   *  \param firstPkt LpPacket of first fragment; must have Nack field
+   *
+   *  If decoding is successful, receiveNack signal is emitted;
+   *  otherwise, a warning is logged.
+   *
+   *  \throw tlv::Error parse error in an LpHeader field
+   */
+  void
+  decodeNack(const Block& netPkt, const lp::Packet& firstPkt);
+
+private:
+  Options m_options;
 };
 
+inline const GenericLinkService::Options&
+GenericLinkService::getOptions() const
+{
+  return m_options;
+}
+
+inline void
+GenericLinkService::setOptions(const GenericLinkService::Options& options)
+{
+  m_options = options;
+}
+
 } // namespace face
 } // namespace nfd
 
-#endif // NFD_DAEMON_FACE_GENERIC_LINK_SERVICE_HPP
\ No newline at end of file
+#endif // NFD_DAEMON_FACE_GENERIC_LINK_SERVICE_HPP
diff --git a/tests/daemon/face/generic-link-service.t.cpp b/tests/daemon/face/generic-link-service.t.cpp
index a918b26..aa2c7a7 100644
--- a/tests/daemon/face/generic-link-service.t.cpp
+++ b/tests/daemon/face/generic-link-service.t.cpp
@@ -44,17 +44,15 @@
     : service(nullptr)
     , transport(nullptr)
   {
-    this->initialize();
+    this->initialize(GenericLinkService::Options());
     // By default, GenericLinkService is created with default options.
     // Test cases may invoke .initialize with alternate options.
   }
 
   void
-  initialize()
+  initialize(const GenericLinkService::Options& options)
   {
-    // TODO#3104 add GenericLinkService::Options parameter,
-    //           and create GenericLinkService with options
-    face.reset(new LpFace(make_unique<GenericLinkService>(),
+    face.reset(new LpFace(make_unique<GenericLinkService>(options),
                           make_unique<DummyTransport>()));
     service = static_cast<GenericLinkService*>(face->getLinkService());
     transport = static_cast<DummyTransport*>(face->getTransport());
@@ -83,7 +81,10 @@
 
 BOOST_AUTO_TEST_CASE(SendInterest)
 {
-  // TODO#3104 initialize with Options that disables all services
+  // Initialize with Options that disables all services
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
 
   shared_ptr<Interest> interest1 = makeInterest("/localhost/test");
 
@@ -95,7 +96,10 @@
 
 BOOST_AUTO_TEST_CASE(SendData)
 {
-  // TODO#3104 initialize with Options that disables all services
+  // Initialize with Options that disables all services
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
 
   shared_ptr<Data> data1 = makeData("/localhost/test");
 
@@ -107,7 +111,10 @@
 
 BOOST_AUTO_TEST_CASE(SendNack)
 {
-  // TODO#3104 initialize with Options that disables all services
+  // Initialize with Options that disables all services
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
 
   lp::Nack nack1 = makeNack("/localhost/test", 323, lp::NackReason::NO_ROUTE);
 
@@ -122,7 +129,10 @@
 
 BOOST_AUTO_TEST_CASE(ReceiveBareInterest)
 {
-  // TODO#3104 initialize with Options that disables all services
+  // Initialize with Options that disables all services
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
 
   shared_ptr<Interest> interest1 = makeInterest("/23Rd9hEiR");
 
@@ -134,7 +144,10 @@
 
 BOOST_AUTO_TEST_CASE(ReceiveInterest)
 {
-  // TODO#3104 initialize with Options that disables all services
+  // Initialize with Options that disables all services
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
 
   shared_ptr<Interest> interest1 = makeInterest("/23Rd9hEiR");
   lp::Packet lpPacket;
@@ -150,7 +163,10 @@
 
 BOOST_AUTO_TEST_CASE(ReceiveBareData)
 {
-  // TODO#3104 initialize with Options that disables all services
+  // Initialize with Options that disables all services
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
 
   shared_ptr<Data> data1 = makeData("/12345678");
 
@@ -162,7 +178,10 @@
 
 BOOST_AUTO_TEST_CASE(ReceiveData)
 {
-  // TODO#3104 initialize with Options that disables all services
+  // Initialize with Options that disables all services
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
 
   shared_ptr<Data> data1 = makeData("/12345689");
   lp::Packet lpPacket;
@@ -178,7 +197,10 @@
 
 BOOST_AUTO_TEST_CASE(ReceiveNack)
 {
-  // TODO#3104 initialize with Options that disables all services
+  // Initialize with Options that disables all services
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
 
   lp::Nack nack1 = makeNack("/localhost/test", 323, lp::NackReason::NO_ROUTE);
   lp::Packet lpPacket;
@@ -194,91 +216,277 @@
 BOOST_AUTO_TEST_SUITE_END() // SimpleSendReceive
 
 
+BOOST_AUTO_TEST_SUITE(Fragmentation)
+
+BOOST_AUTO_TEST_CASE(ReassemblyDisabledDropFragIndex)
+{
+  // TODO#3171 Initialize with Options that disables reassembly
+
+  shared_ptr<Interest> interest = makeInterest("/IgFe6NvH");
+  lp::Packet packet(interest->wireEncode());
+  packet.set<lp::FragIndexField>(140);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_CHECK(receivedInterests.empty());
+}
+
+BOOST_AUTO_TEST_CASE(ReassemblyDisabledDropFragCount)
+{
+  // TODO#3171 Initialize with Options that disables reassembly
+
+  shared_ptr<Interest> interest = makeInterest("/SeGmEjvIVX");
+  lp::Packet packet(interest->wireEncode());
+  packet.set<lp::FragCountField>(276);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_CHECK(receivedInterests.empty());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Fragmentation
+
+
 BOOST_AUTO_TEST_SUITE(LocalFields)
 
-BOOST_AUTO_TEST_CASE(SendIncomingFaceId)
-{
-  // TODO#3104 initialize with Options that enables local fields
-  // TODO#3104 send Interest with IncomingFaceId
-  //           expect transport->sentPackets.back() has IncomingFaceId field
-}
-
-BOOST_AUTO_TEST_CASE(SendIncomingFaceIdDisabled)
-{
-  // TODO#3104 initialize with Options that disables local fields
-  // TODO#3104 send Interest with IncomingFaceId
-  //           expect transport->sentPackets.back() has no IncomingFaceId field
-}
-
-BOOST_AUTO_TEST_CASE(ReceiveIncomingFaceIdIgnore)
-{
-  // TODO#3104 initialize with Options that enables local fields
-  // TODO#3104 receive Interest with IncomingFaceId
-  //           expect receivedInterests.back() has no IncomingFaceId tag
-}
-
 BOOST_AUTO_TEST_CASE(ReceiveNextHopFaceId)
 {
-  // TODO#3104 initialize with Options that enables local fields
-  // TODO#3104 receive Interest with NextHopFaceId
-  //           expect receivedInterests.back() has NextHopFaceId tag
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  shared_ptr<Interest> interest = makeInterest("/12345678");
+  lp::Packet packet(interest->wireEncode());
+  packet.set<lp::NextHopFaceIdField>(1000);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_REQUIRE_EQUAL(receivedInterests.size(), 1);
+  BOOST_REQUIRE(receivedInterests.back().getLocalControlHeader().hasNextHopFaceId());
+  BOOST_CHECK_EQUAL(receivedInterests.back().getNextHopFaceId(), 1000);
 }
 
 BOOST_AUTO_TEST_CASE(ReceiveNextHopFaceIdDisabled)
 {
-  // TODO#3104 initialize with Options that disables local fields
-  // TODO#3104 receive Interest with NextHopFaceId
-  //           expect receivedInterests.empty()
-  //       or, expect receivedInterests.back() has no NextHopFaceId tag
+  // Initialize with Options that disables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
+
+  shared_ptr<Interest> interest = makeInterest("/12345678");
+  lp::Packet packet(interest->wireEncode());
+  packet.set<lp::NextHopFaceIdField>(1000);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_CHECK(receivedInterests.empty());
 }
 
 BOOST_AUTO_TEST_CASE(ReceiveNextHopFaceIdDropData)
 {
-  // TODO#3104 initialize with Options that enables local fields
-  // TODO#3104 receive Data with NextHopFaceId
-  //           expect receivedData.empty()
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  shared_ptr<Data> data = makeData("/12345678");
+  lp::Packet packet(data->wireEncode());
+  packet.set<lp::NextHopFaceIdField>(1000);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_CHECK(receivedData.empty());
 }
 
 BOOST_AUTO_TEST_CASE(ReceiveNextHopFaceIdDropNack)
 {
-  // TODO#3104 initialize with Options that enables local fields
-  // TODO#3104 receive Nack with NextHopFaceId
-  //           expect receivedNacks.empty()
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  lp::Nack nack = makeNack("/localhost/test", 123, lp::NackReason::NO_ROUTE);
+  lp::Packet packet;
+  packet.set<lp::FragmentField>(std::make_pair(
+    nack.getInterest().wireEncode().begin(), nack.getInterest().wireEncode().end()));
+  packet.set<lp::NackField>(nack.getHeader());
+  packet.set<lp::NextHopFaceIdField>(1000);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_CHECK(receivedNacks.empty());
 }
 
 BOOST_AUTO_TEST_CASE(ReceiveCacheControl)
 {
-  // TODO#3104 initialize with Options that enables local fields
-  // TODO#3104 receive Data with CacheControl
-  //           expect receivedData.back() has CacheControl tag
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  shared_ptr<Data> data = makeData("/12345678");
+  lp::Packet packet(data->wireEncode());
+  lp::CachePolicy policy;
+  policy.setPolicy(lp::CachePolicyType::NO_CACHE);
+  packet.set<lp::CachePolicyField>(policy);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_REQUIRE_EQUAL(receivedData.size(), 1);
+  BOOST_REQUIRE(receivedData.back().getLocalControlHeader().hasCachingPolicy());
+  BOOST_CHECK_EQUAL(receivedData.back().getCachingPolicy(),
+                    ndn::nfd::LocalControlHeader::CachingPolicy::NO_CACHE);
 }
 
 BOOST_AUTO_TEST_CASE(ReceiveCacheControlDisabled)
 {
-  // TODO#3104 initialize with Options that disables local fields
-  // TODO#3104 receive Data with CacheControl
-  //           expect receivedData.back() has no CacheControl tag
+  // Initialize with Options that disables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
+
+  shared_ptr<Data> data = makeData("/12345678");
+  lp::Packet packet(data->wireEncode());
+  lp::CachePolicy policy;
+  policy.setPolicy(lp::CachePolicyType::NO_CACHE);
+  packet.set<lp::CachePolicyField>(policy);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_REQUIRE_EQUAL(receivedData.size(), 1);
+  BOOST_CHECK(!receivedData.back().getLocalControlHeader().hasCachingPolicy());
 }
 
 BOOST_AUTO_TEST_CASE(ReceiveCacheControlDropInterest)
 {
-  // TODO#3104 initialize with Options that enables local fields
-  // TODO#3104 receive Interest with CacheControl
-  //           expect receivedInterests.empty()
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  shared_ptr<Interest> interest = makeInterest("/12345678");
+  lp::Packet packet(interest->wireEncode());
+  lp::CachePolicy policy;
+  policy.setPolicy(lp::CachePolicyType::NO_CACHE);
+  packet.set<lp::CachePolicyField>(policy);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_CHECK(receivedInterests.empty());
 }
 
 BOOST_AUTO_TEST_CASE(ReceiveCacheControlDropNack)
 {
-  // TODO#3104 initialize with Options that enables local fields
-  // TODO#3104 receive Nack with CacheControl
-  //           expect receivedNacks.empty()
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  lp::Nack nack = makeNack("/localhost/test", 123, lp::NackReason::NO_ROUTE);
+  lp::Packet packet(nack.getInterest().wireEncode());
+  packet.set<lp::NackField>(nack.getHeader());
+  lp::CachePolicy policy;
+  policy.setPolicy(lp::CachePolicyType::NO_CACHE);
+  packet.set<lp::CachePolicyField>(policy);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_CHECK(receivedNacks.empty());
+}
+
+BOOST_AUTO_TEST_CASE(SendIncomingFaceId)
+{
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  shared_ptr<Interest> interest = makeInterest("/12345678");
+  interest->setIncomingFaceId(1000);
+
+  face->sendInterest(*interest);
+
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
+  lp::Packet sent(transport->sentPackets.back().packet);
+  BOOST_REQUIRE(sent.has<lp::IncomingFaceIdField>());
+  BOOST_CHECK_EQUAL(sent.get<lp::IncomingFaceIdField>(), 1000);
+}
+
+BOOST_AUTO_TEST_CASE(SendIncomingFaceIdDisabled)
+{
+  // Initialize with Options that disables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = false;
+  initialize(options);
+
+  shared_ptr<Interest> interest = makeInterest("/12345678");
+  interest->setIncomingFaceId(1000);
+
+  face->sendInterest(*interest);
+
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
+  lp::Packet sent(transport->sentPackets.back().packet);
+  BOOST_CHECK(!sent.has<lp::IncomingFaceIdField>());
+}
+
+BOOST_AUTO_TEST_CASE(ReceiveIncomingFaceIdIgnoreInterest)
+{
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  shared_ptr<Interest> interest = makeInterest("/12345678");
+  lp::Packet packet(interest->wireEncode());
+  packet.set<lp::IncomingFaceIdField>(1000);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_REQUIRE_EQUAL(receivedInterests.size(), 1);
+  BOOST_CHECK(!receivedInterests.back().getLocalControlHeader().hasIncomingFaceId());
+}
+
+BOOST_AUTO_TEST_CASE(ReceiveIncomingFaceIdIgnoreData)
+{
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  shared_ptr<Data> data = makeData("/z1megUh9Bj");
+  lp::Packet packet(data->wireEncode());
+  packet.set<lp::IncomingFaceIdField>(1000);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_REQUIRE_EQUAL(receivedData.size(), 1);
+  BOOST_CHECK(!receivedData.back().getLocalControlHeader().hasIncomingFaceId());
+}
+
+BOOST_AUTO_TEST_CASE(ReceiveIncomingFaceIdIgnoreNack)
+{
+  // Initialize with Options that enables local fields
+  GenericLinkService::Options options;
+  options.allowLocalFields = true;
+  initialize(options);
+
+  lp::Nack nack = makeNack("/TPAhdiHz", 278, lp::NackReason::CONGESTION);
+  lp::Packet packet(nack.getInterest().wireEncode());
+  packet.set<lp::NackField>(nack.getHeader());
+  packet.set<lp::IncomingFaceIdField>(1000);
+
+  transport->receivePacket(packet.wireEncode());
+
+  BOOST_REQUIRE_EQUAL(receivedNacks.size(), 1);
+  BOOST_CHECK(!receivedNacks.back().getLocalControlHeader().hasIncomingFaceId());
 }
 
 BOOST_AUTO_TEST_SUITE_END() // LocalFields
 
 
-BOOST_AUTO_TEST_SUITE_END()
-BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END() // TestGenericLinkService
+BOOST_AUTO_TEST_SUITE_END() // Face
 
 } // namespace tests
 } // namespace face