face: implement NDNLP fragmentation in EthernetFace

Change-Id: I5b9ff271087ca0b2d5ee38be2f77911cfe9283f4
Refs: #1209
diff --git a/daemon/face/ethernet-face.cpp b/daemon/face/ethernet-face.cpp
index 0e13844..21a12a4 100644
--- a/daemon/face/ethernet-face.cpp
+++ b/daemon/face/ethernet-face.cpp
@@ -60,6 +60,8 @@
 
 NFD_LOG_INIT("EthernetFace");
 
+const time::nanoseconds EthernetFace::REASSEMBLER_LIFETIME = time::seconds(60);
+
 EthernetFace::EthernetFace(const shared_ptr<boost::asio::posix::stream_descriptor>& socket,
                            const NetworkInterfaceInfo& interface,
                            const ethernet::Address& address)
@@ -90,6 +92,8 @@
   NFD_LOG_DEBUG("[id:" << getId() << ",endpoint:" << m_interfaceName
                 << "] Interface MTU is: " << m_interfaceMtu);
 
+  m_slicer.reset(new ndnlp::Slicer(m_interfaceMtu));
+
   char filter[100];
   std::snprintf(filter, sizeof(filter),
                 "(ether proto 0x%x) && (ether dst %s) && (not ether src %s)",
@@ -114,14 +118,20 @@
 EthernetFace::sendInterest(const Interest& interest)
 {
   onSendInterest(interest);
-  sendPacket(interest.wireEncode());
+  ndnlp::PacketArray pa = m_slicer->slice(interest.wireEncode());
+  for (const auto& packet : *pa) {
+    sendPacket(packet);
+  }
 }
 
 void
 EthernetFace::sendData(const Data& data)
 {
   onSendData(data);
-  sendPacket(data.wireEncode());
+  ndnlp::PacketArray pa = m_slicer->slice(data.wireEncode());
+  for (const auto& packet : *pa) {
+    sendPacket(packet);
+  }
 }
 
 void
@@ -257,13 +267,7 @@
       return fail("Face closed");
     }
 
-  /// \todo Fragmentation
-  if (block.size() > m_interfaceMtu)
-    {
-      NFD_LOG_ERROR("[id:" << getId() << ",endpoint:" << m_interfaceName
-                    << "] Fragmentation not implemented: dropping packet larger than MTU");
-      return;
-    }
+  BOOST_ASSERT(block.size() <= m_interfaceMtu);
 
   /// \todo Right now there is no reserve when packet is received, but
   ///       we should reserve some space at the beginning and at the end
@@ -341,11 +345,12 @@
     }
 
   const ether_header* eh = reinterpret_cast<const ether_header*>(packet);
+  const ethernet::Address sourceAddress(eh->ether_shost);
 
   // assert in case BPF fails to filter unwanted frames
   BOOST_ASSERT_MSG(ethernet::Address(eh->ether_dhost) == m_destAddress,
                    "Received frame addressed to a different multicast group");
-  BOOST_ASSERT_MSG(ethernet::Address(eh->ether_shost) != m_srcAddress,
+  BOOST_ASSERT_MSG(sourceAddress != m_srcAddress,
                    "Received frame sent by this host");
   BOOST_ASSERT_MSG(ntohs(eh->ether_type) == ethernet::ETHERTYPE_NDN,
                    "Received frame with unrecognized ethertype");
@@ -354,25 +359,51 @@
   length -= ethernet::HDR_LEN;
 
   /// \todo Reserve space in front and at the back of the underlying buffer
-  Block element;
-  bool isOk = Block::fromBuffer(packet, length, element);
-  if (isOk)
-    {
-      NFD_LOG_TRACE("[id:" << getId() << ",endpoint:" << m_interfaceName
-                    << "] Received: " << element.size() << " bytes");
-      this->getMutableCounters().getNInBytes() += element.size();
-
-      if (!decodeAndDispatchInput(element))
-        {
-          NFD_LOG_WARN("[id:" << getId() << ",endpoint:" << m_interfaceName
-                       << "] Received unrecognized block of type " << element.type());
-        }
-    }
-  else
+  Block fragment;
+  bool isOk = Block::fromBuffer(packet, length, fragment);
+  if (!isOk)
     {
       NFD_LOG_WARN("[id:" << getId() << ",endpoint:" << m_interfaceName
-                   << "] Received block is invalid or too large to process");
+                   << "] Block received from " << sourceAddress.toString()
+                   << " is invalid or too large to process");
+      return;
     }
+
+  NFD_LOG_TRACE("[id:" << getId() << ",endpoint:" << m_interfaceName
+                << "] Received: " << fragment.size() << " bytes from "
+                << sourceAddress.toString());
+  this->getMutableCounters().getNInBytes() += fragment.size();
+
+  Reassembler& reassembler = m_reassemblers[sourceAddress];
+  if (!reassembler.pms)
+    {
+      // new sender, setup a PartialMessageStore for it
+      reassembler.pms.reset(new ndnlp::PartialMessageStore);
+      reassembler.pms->onReceive +=
+        [this, sourceAddress] (const Block& block) {
+          NFD_LOG_TRACE("[id:" << getId() << ",endpoint:" << m_interfaceName
+                        << "] All fragments received from " << sourceAddress.toString());
+          if (!decodeAndDispatchInput(block))
+            NFD_LOG_WARN("[id:" << getId() << ",endpoint:" << m_interfaceName
+                         << "] Received unrecognized TLV block of type " << block.type()
+                         << " from " << sourceAddress.toString());
+        };
+    }
+
+  scheduler::cancel(reassembler.expireEvent);
+  reassembler.expireEvent = scheduler::schedule(REASSEMBLER_LIFETIME,
+    [this, sourceAddress] {
+      BOOST_VERIFY(m_reassemblers.erase(sourceAddress) == 1);
+    });
+
+  try {
+    reassembler.pms->receiveNdnlpData(fragment);
+  }
+  catch (const ndnlp::ParseError& e) {
+    NFD_LOG_WARN("[id:" << getId() << ",endpoint:" << m_interfaceName
+                 << "] Received invalid NDNLP fragment from "
+                 << sourceAddress.toString() << " : " << e.what());
+  }
 }
 
 void
diff --git a/daemon/face/ethernet-face.hpp b/daemon/face/ethernet-face.hpp
index 1dd6593..87e2238 100644
--- a/daemon/face/ethernet-face.hpp
+++ b/daemon/face/ethernet-face.hpp
@@ -28,8 +28,12 @@
 
 #include "common.hpp"
 #include "face.hpp"
+#include "ndnlp-partial-message-store.hpp"
+#include "ndnlp-slicer.hpp"
 #include "core/network-interface.hpp"
 
+#include <unordered_map>
+
 #ifndef HAVE_LIBPCAP
 #error "Cannot include this file when libpcap is not available"
 #endif
@@ -136,6 +140,12 @@
   getInterfaceMtu() const;
 
 private:
+  struct Reassembler
+  {
+    unique_ptr<ndnlp::PartialMessageStore> pms;
+    EventId expireEvent;
+  };
+
   unique_ptr<pcap_t, void(*)(pcap_t*)> m_pcap;
   shared_ptr<boost::asio::posix::stream_descriptor> m_socket;
 
@@ -145,7 +155,11 @@
   std::string m_interfaceName;
   ethernet::Address m_srcAddress;
   ethernet::Address m_destAddress;
+
   size_t m_interfaceMtu;
+  unique_ptr<ndnlp::Slicer> m_slicer;
+  std::unordered_map<ethernet::Address, Reassembler> m_reassemblers;
+  static const time::nanoseconds REASSEMBLER_LIFETIME;
 };
 
 } // namespace nfd
diff --git a/tests/daemon/face/ethernet.cpp b/tests/daemon/face/ethernet.cpp
index 54cca5d..ab0181c 100644
--- a/tests/daemon/face/ethernet.cpp
+++ b/tests/daemon/face/ethernet.cpp
@@ -133,10 +133,12 @@
   face->sendInterest(*interest2);
   face->sendData    (*data2    );
 
-  BOOST_CHECK_EQUAL(face->getCounters().getNOutBytes(), interest1->wireEncode().size() +
-                                                        data1->wireEncode().size() +
-                                                        interest2->wireEncode().size() +
-                                                        data2->wireEncode().size());
+  BOOST_CHECK_EQUAL(face->getCounters().getNOutBytes(),
+                    14 * 4 + // 4 NDNLP headers
+                    interest1->wireEncode().size() +
+                    data1->wireEncode().size() +
+                    interest2->wireEncode().size() +
+                    data2->wireEncode().size());
 
 //  m_ioRemaining = 4;
 //  m_ioService.run();
@@ -191,11 +193,11 @@
   // valid frame, but TLV block has invalid length
   static const pcap_pkthdr header3{{}, ethernet::HDR_LEN + ethernet::MIN_DATA_LEN};
   static const uint8_t packet3[ethernet::HDR_LEN + ethernet::MIN_DATA_LEN]{
-    0x01, 0x00, 0x5E, 0x00, 0x17, 0xAA, // destination address
+    0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, // destination address
     0x02, 0x00, 0x00, 0x00, 0x00, 0x02, // source address
     0x86, 0x24,       // NDN ethertype
-    tlv::Data,        // TLV type
-    0xFD, 0xFF, 0xFF  // TLV length (invalid because greater than packet size)
+    tlv::NdnlpData,   // TLV type
+    0xfd, 0xff, 0xff  // TLV length (invalid because greater than buffer size)
   };
   face->processIncomingPacket(&header3, packet3);
   BOOST_CHECK_EQUAL(face->getCounters().getNInBytes(), 0);
@@ -205,7 +207,7 @@
   // valid frame, but TLV block has invalid type
   static const pcap_pkthdr header4{{}, ethernet::HDR_LEN + ethernet::MIN_DATA_LEN};
   static const uint8_t packet4[ethernet::HDR_LEN + ethernet::MIN_DATA_LEN]{
-    0x01, 0x00, 0x5E, 0x00, 0x17, 0xAA, // destination address
+    0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, // destination address
     0x02, 0x00, 0x00, 0x00, 0x00, 0x02, // source address
     0x86, 0x24,       // NDN ethertype
     0x00,             // TLV type (invalid)
@@ -216,21 +218,41 @@
   BOOST_CHECK_EQUAL(recInterests.size(), 0);
   BOOST_CHECK_EQUAL(recDatas.size(), 0);
 
-  // valid frame and valid TLV block
+  // valid frame and valid NDNLP header, but invalid payload
   static const pcap_pkthdr header5{{}, ethernet::HDR_LEN + ethernet::MIN_DATA_LEN};
   static const uint8_t packet5[ethernet::HDR_LEN + ethernet::MIN_DATA_LEN]{
-    0x01, 0x00, 0x5E, 0x00, 0x17, 0xAA, // destination address
+    0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, // destination address
     0x02, 0x00, 0x00, 0x00, 0x00, 0x02, // source address
-    0x86, 0x24,       // NDN ethertype
-    tlv::Interest,    // TLV type
-    0x16,             // TLV length
+    0x86, 0x24,                   // NDN ethertype
+    tlv::NdnlpData,     0x0e,     // NDNLP header
+    tlv::NdnlpSequence, 0x08,
+    0, 0, 0, 0, 0, 0, 0, 0,
+    tlv::NdnlpPayload,  0x02,
+    0x00,             // NDN TLV type (invalid)
+    0x00              // NDN TLV length
+  };
+  face->processIncomingPacket(&header5, packet5);
+  BOOST_CHECK_EQUAL(face->getCounters().getNInBytes(), 18);
+  BOOST_CHECK_EQUAL(recInterests.size(), 0);
+  BOOST_CHECK_EQUAL(recDatas.size(), 0);
+
+  // valid frame, valid NDNLP header, and valid NDN (interest) packet
+  static const pcap_pkthdr header6{{}, ethernet::HDR_LEN + ethernet::MIN_DATA_LEN};
+  static const uint8_t packet6[ethernet::HDR_LEN + ethernet::MIN_DATA_LEN]{
+    0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, // destination address
+    0x02, 0x00, 0x00, 0x00, 0x00, 0x02, // source address
+    0x86, 0x24,                         // NDN ethertype
+    tlv::NdnlpData, 0x24,               // NDNLP TLV type and length
+    0x51, 0x08, 0x00, 0x00, 0x00, 0x00, // rest of NDNLP header
+    0x00, 0x00, 0x00, 0x00, 0x54, 0x18,
+    tlv::Interest, 0x16,                // NDN TLV type and length
     0x07, 0x0e, 0x08, 0x07, 0x65, 0x78, // payload
     0x61, 0x6d, 0x70, 0x6c, 0x65, 0x08,
     0x03, 0x66, 0x6f, 0x6f, 0x0a, 0x04,
     0x03, 0xef, 0xe9, 0x7c
   };
-  face->processIncomingPacket(&header5, packet5);
-  BOOST_CHECK_EQUAL(face->getCounters().getNInBytes(), 26);
+  face->processIncomingPacket(&header6, packet6);
+  BOOST_CHECK_EQUAL(face->getCounters().getNInBytes(), 56);
   BOOST_CHECK_EQUAL(recInterests.size(), 1);
   BOOST_CHECK_EQUAL(recDatas.size(), 0);
 }