face: NDNLPv2 fragmentation and reassembly

refs #3171

Change-Id: If29035b697b904ee49cb86d9248be488657c6f9e
diff --git a/tests/daemon/face/dummy-transport.hpp b/tests/daemon/face/dummy-transport.hpp
index 6d1451c..ce2833b 100644
--- a/tests/daemon/face/dummy-transport.hpp
+++ b/tests/daemon/face/dummy-transport.hpp
@@ -56,6 +56,12 @@
   }
 
   void
+  setMtu(ssize_t mtu)
+  {
+    this->Transport::setMtu(mtu);
+  }
+
+  void
   setState(FaceState state)
   {
     this->Transport::setState(state);
diff --git a/tests/daemon/face/generic-link-service.t.cpp b/tests/daemon/face/generic-link-service.t.cpp
index ac01c7d..f3c3c7b 100644
--- a/tests/daemon/face/generic-link-service.t.cpp
+++ b/tests/daemon/face/generic-link-service.t.cpp
@@ -90,8 +90,12 @@
 
   face->sendInterest(*interest1);
 
+  BOOST_CHECK_EQUAL(service->getCounters().nOutInterests, 1);
   BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
-  BOOST_CHECK(transport->sentPackets.back().packet == interest1->wireEncode());
+  lp::Packet interest1pkt;
+  BOOST_REQUIRE_NO_THROW(interest1pkt.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK(interest1pkt.has<lp::FragmentField>());
+  BOOST_CHECK(!interest1pkt.has<lp::SequenceField>());
 }
 
 BOOST_AUTO_TEST_CASE(SendData)
@@ -105,8 +109,12 @@
 
   face->sendData(*data1);
 
+  BOOST_CHECK_EQUAL(service->getCounters().nOutData, 1);
   BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
-  BOOST_CHECK(transport->sentPackets.back().packet == data1->wireEncode());
+  lp::Packet data1pkt;
+  BOOST_REQUIRE_NO_THROW(data1pkt.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK(data1pkt.has<lp::FragmentField>());
+  BOOST_CHECK(!data1pkt.has<lp::SequenceField>());
 }
 
 BOOST_AUTO_TEST_CASE(SendNack)
@@ -120,11 +128,13 @@
 
   face->sendNack(nack1);
 
+  BOOST_CHECK_EQUAL(service->getCounters().nOutNacks, 1);
   BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
   lp::Packet nack1pkt;
   BOOST_REQUIRE_NO_THROW(nack1pkt.wireDecode(transport->sentPackets.back().packet));
-  BOOST_CHECK_EQUAL(nack1pkt.has<lp::NackField>(), true);
-  BOOST_CHECK_EQUAL(nack1pkt.has<lp::FragmentField>(), true);
+  BOOST_CHECK(nack1pkt.has<lp::NackField>());
+  BOOST_CHECK(nack1pkt.has<lp::FragmentField>());
+  BOOST_CHECK(!nack1pkt.has<lp::SequenceField>());
 }
 
 BOOST_AUTO_TEST_CASE(ReceiveBareInterest)
@@ -138,6 +148,7 @@
 
   transport->receivePacket(interest1->wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInInterests, 1);
   BOOST_REQUIRE_EQUAL(receivedInterests.size(), 1);
   BOOST_CHECK_EQUAL(receivedInterests.back(), *interest1);
 }
@@ -157,6 +168,7 @@
 
   transport->receivePacket(lpPacket.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInInterests, 1);
   BOOST_REQUIRE_EQUAL(receivedInterests.size(), 1);
   BOOST_CHECK_EQUAL(receivedInterests.back(), *interest1);
 }
@@ -172,6 +184,7 @@
 
   transport->receivePacket(data1->wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInData, 1);
   BOOST_REQUIRE_EQUAL(receivedData.size(), 1);
   BOOST_CHECK_EQUAL(receivedData.back(), *data1);
 }
@@ -191,6 +204,7 @@
 
   transport->receivePacket(lpPacket.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInData, 1);
   BOOST_REQUIRE_EQUAL(receivedData.size(), 1);
   BOOST_CHECK_EQUAL(receivedData.back(), *data1);
 }
@@ -210,7 +224,10 @@
 
   transport->receivePacket(lpPacket.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNacks, 1);
   BOOST_REQUIRE_EQUAL(receivedNacks.size(), 1);
+  BOOST_CHECK(receivedNacks.back().getReason() == nack1.getReason());
+  BOOST_CHECK(receivedNacks.back().getInterest() == nack1.getInterest());
 }
 
 BOOST_AUTO_TEST_CASE(ReceiveIdlePacket)
@@ -225,7 +242,8 @@
 
   BOOST_CHECK_NO_THROW(transport->receivePacket(lpPacket.wireEncode()));
 
-  // IDLE packet should be ignored
+  // IDLE packet should be ignored, but is not an error
+  BOOST_CHECK_EQUAL(service->getCounters().nInLpInvalid, 0);
   BOOST_CHECK_EQUAL(receivedInterests.size(), 0);
   BOOST_CHECK_EQUAL(receivedData.size(), 0);
   BOOST_CHECK_EQUAL(receivedNacks.size(), 0);
@@ -236,9 +254,112 @@
 
 BOOST_AUTO_TEST_SUITE(Fragmentation)
 
+BOOST_AUTO_TEST_CASE(FragmentationDisabledExceedMtuDrop)
+{
+  // Initialize with Options that disable fragmentation
+  GenericLinkService::Options options;
+  options.allowFragmentation = false;
+  initialize(options);
+
+  transport->setMtu(55);
+
+  shared_ptr<Data> data = makeData("/test/data/123456789/987654321/123456789");
+  face->sendData(*data);
+
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nOutOverMtu, 1);
+}
+
+BOOST_AUTO_TEST_CASE(FragmentationUnlimitedMtu)
+{
+  // Initialize with Options that enable fragmentation
+  GenericLinkService::Options options;
+  options.allowFragmentation = true;
+  initialize(options);
+
+  transport->setMtu(MTU_UNLIMITED);
+
+  shared_ptr<Data> data = makeData("/test/data/123456789/987654321/123456789");
+  face->sendData(*data);
+
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(FragmentationUnderMtu)
+{
+  // Initialize with Options that enable fragmentation
+  GenericLinkService::Options options;
+  options.allowFragmentation = true;
+  initialize(options);
+
+  transport->setMtu(105);
+
+  shared_ptr<Data> data = makeData("/test/data/123456789/987654321/123456789");
+  face->sendData(*data);
+
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(FragmentationOverMtu)
+{
+  // Initialize with Options that enable fragmentation
+  GenericLinkService::Options options;
+  options.allowFragmentation = true;
+  initialize(options);
+
+  transport->setMtu(60);
+
+  shared_ptr<Data> data = makeData("/test/data/123456789/987654321/123456789");
+  face->sendData(*data);
+
+  BOOST_CHECK_GT(transport->sentPackets.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ReassembleFragments)
+{
+  // Initialize with Options that enables reassembly
+  GenericLinkService::Options options;
+  options.allowReassembly = true;
+  initialize(options);
+
+  shared_ptr<Interest> interest = makeInterest(
+    "/mt7P130BHXmtLm5dwaY5dpUM6SWYNN2B05g7y3UhsQuLvDdnTWdNnTeEiLuW3FAbJRSG3tzQ0UfaSEgG9rvYHmsKtgPMag1Hj4Tr");
+  lp::Packet packet(interest->wireEncode());
+
+  // fragment the packet
+  LpFragmenter fragmenter;
+  size_t mtu = 100;
+  bool isOk = false;
+  std::vector<lp::Packet> frags;
+  std::tie(isOk, frags) = fragmenter.fragmentPacket(packet, mtu);
+  BOOST_REQUIRE(isOk);
+  BOOST_CHECK_GT(frags.size(), 1);
+
+  // receive the fragments
+  for (ssize_t fragIndex = frags.size() - 1; fragIndex >= 0; --fragIndex) {
+    size_t sequence = 1000 + fragIndex;
+    frags[fragIndex].add<lp::SequenceField>(sequence);
+
+    transport->receivePacket(frags[fragIndex].wireEncode());
+
+    if (fragIndex > 0) {
+      BOOST_CHECK(receivedInterests.empty());
+      BOOST_CHECK_EQUAL(service->getCounters().nReassembling, 1);
+    }
+    else {
+      BOOST_CHECK_EQUAL(receivedInterests.size(), 1);
+      BOOST_CHECK_EQUAL(receivedInterests.back(), *interest);
+      BOOST_CHECK_EQUAL(service->getCounters().nReassembling, 0);
+    }
+  }
+}
+
 BOOST_AUTO_TEST_CASE(ReassemblyDisabledDropFragIndex)
 {
-  // TODO#3171 Initialize with Options that disables reassembly
+  // Initialize with Options that disables reassembly
+  GenericLinkService::Options options;
+  options.allowReassembly = false;
+  initialize(options);
 
   shared_ptr<Interest> interest = makeInterest("/IgFe6NvH");
   lp::Packet packet(interest->wireEncode());
@@ -246,12 +367,16 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInLpInvalid, 0); // not an error
   BOOST_CHECK(receivedInterests.empty());
 }
 
 BOOST_AUTO_TEST_CASE(ReassemblyDisabledDropFragCount)
 {
-  // TODO#3171 Initialize with Options that disables reassembly
+  // Initialize with Options that disables reassembly
+  GenericLinkService::Options options;
+  options.allowReassembly = false;
+  initialize(options);
 
   shared_ptr<Interest> interest = makeInterest("/SeGmEjvIVX");
   lp::Packet packet(interest->wireEncode());
@@ -259,6 +384,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInLpInvalid, 0); // not an error
   BOOST_CHECK(receivedInterests.empty());
 }
 
@@ -298,6 +424,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNetInvalid, 0); // not an error
   BOOST_CHECK(receivedInterests.empty());
 }
 
@@ -314,6 +441,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNetInvalid, 1);
   BOOST_CHECK(receivedData.empty());
 }
 
@@ -333,6 +461,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNetInvalid, 1);
   BOOST_CHECK(receivedNacks.empty());
 }
 
@@ -372,6 +501,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNetInvalid, 0); // not an error
   BOOST_REQUIRE_EQUAL(receivedData.size(), 1);
   BOOST_CHECK(!receivedData.back().getLocalControlHeader().hasCachingPolicy());
 }
@@ -391,6 +521,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNetInvalid, 1);
   BOOST_CHECK(receivedInterests.empty());
 }
 
@@ -410,6 +541,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNetInvalid, 1);
   BOOST_CHECK(receivedNacks.empty());
 }
 
@@ -461,6 +593,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNetInvalid, 0); // not an error
   BOOST_REQUIRE_EQUAL(receivedInterests.size(), 1);
   BOOST_CHECK(!receivedInterests.back().getLocalControlHeader().hasIncomingFaceId());
 }
@@ -478,6 +611,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNetInvalid, 0); // not an error
   BOOST_REQUIRE_EQUAL(receivedData.size(), 1);
   BOOST_CHECK(!receivedData.back().getLocalControlHeader().hasIncomingFaceId());
 }
@@ -496,6 +630,7 @@
 
   transport->receivePacket(packet.wireEncode());
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInNetInvalid, 0); // not an error
   BOOST_REQUIRE_EQUAL(receivedNacks.size(), 1);
   BOOST_CHECK(!receivedNacks.back().getLocalControlHeader().hasIncomingFaceId());
 }
@@ -516,6 +651,7 @@
 
   BOOST_CHECK_NO_THROW(transport->receivePacket(packet));
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInLpInvalid, 1);
   BOOST_CHECK_EQUAL(receivedInterests.size(), 0);
   BOOST_CHECK_EQUAL(receivedData.size(), 0);
   BOOST_CHECK_EQUAL(receivedNacks.size(), 0);
@@ -533,6 +669,7 @@
 
   BOOST_CHECK_NO_THROW(transport->receivePacket(packet));
 
+  BOOST_CHECK_EQUAL(service->getCounters().nInLpInvalid, 1);
   BOOST_CHECK_EQUAL(receivedInterests.size(), 0);
   BOOST_CHECK_EQUAL(receivedData.size(), 0);
   BOOST_CHECK_EQUAL(receivedNacks.size(), 0);
diff --git a/tests/daemon/face/lp-fragmenter.t.cpp b/tests/daemon/face/lp-fragmenter.t.cpp
new file mode 100644
index 0000000..4bc7022
--- /dev/null
+++ b/tests/daemon/face/lp-fragmenter.t.cpp
@@ -0,0 +1,163 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "face/lp-fragmenter.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+using namespace nfd::tests;
+
+BOOST_AUTO_TEST_SUITE(Face)
+
+class LpFragmenterFixture
+{
+public:
+  LpFragmenter fragmenter;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestLpFragmenter, LpFragmenterFixture)
+
+BOOST_AUTO_TEST_CASE(FragmentSingleFragment)
+{
+  size_t mtu = 256;
+
+  lp::Packet packet;
+  packet.add<lp::IncomingFaceIdField>(123);
+
+  shared_ptr<Data> data = makeData("/test/data1");
+  BOOST_REQUIRE_EQUAL(data->wireEncode().size(), 30);
+  packet.add<lp::FragmentField>(std::make_pair(data->wireEncode().begin(),
+                                               data->wireEncode().end()));
+
+  bool isOk = false;
+  std::vector<lp::Packet> frags;
+  std::tie(isOk, frags) = fragmenter.fragmentPacket(packet, mtu);
+
+  BOOST_REQUIRE(isOk);
+  BOOST_REQUIRE_EQUAL(frags.size(), 1);
+  BOOST_CHECK(frags[0].has<lp::FragmentField>());
+  BOOST_CHECK_EQUAL(frags[0].get<lp::IncomingFaceIdField>(), 123);
+  BOOST_CHECK(!frags[0].has<lp::FragIndexField>());
+  BOOST_CHECK(!frags[0].has<lp::FragCountField>());
+  BOOST_CHECK_LE(frags[0].wireEncode().size(), mtu);
+
+  ndn::Buffer::const_iterator fragBegin, fragEnd;
+  std::tie(fragBegin, fragEnd) = frags[0].get<lp::FragmentField>();
+  BOOST_CHECK_EQUAL_COLLECTIONS(data->wireEncode().begin(), data->wireEncode().end(),
+                                fragBegin, fragEnd);
+}
+
+BOOST_AUTO_TEST_CASE(FragmentMultipleFragments)
+{
+  size_t mtu = 90;
+
+  lp::Packet packet;
+  packet.add<lp::IncomingFaceIdField>(123);
+
+  shared_ptr<Data> data = makeData("/test/data1/123456789/987654321/123456789");
+  BOOST_REQUIRE_EQUAL(data->wireEncode().size(), 63);
+  packet.add<lp::FragmentField>(std::make_pair(data->wireEncode().begin(),
+                                               data->wireEncode().end()));
+
+  bool isOk = false;
+  std::vector<lp::Packet> frags;
+  std::tie(isOk, frags) = fragmenter.fragmentPacket(packet, mtu);
+
+  BOOST_REQUIRE(isOk);
+  BOOST_REQUIRE_EQUAL(frags.size(), 2);
+
+  ndn::Buffer reassembledPayload(63);
+
+  BOOST_CHECK(frags[0].has<lp::FragmentField>());
+  BOOST_CHECK_EQUAL(frags[0].get<lp::IncomingFaceIdField>(), 123);
+  BOOST_CHECK_EQUAL(frags[0].get<lp::FragIndexField>(), 0);
+  BOOST_CHECK_EQUAL(frags[0].get<lp::FragCountField>(), 2);
+  BOOST_CHECK_LE(frags[0].wireEncode().size(), mtu);
+  ndn::Buffer::const_iterator frag0Begin, frag0End;
+  std::tie(frag0Begin, frag0End) = frags[0].get<lp::FragmentField>();
+  BOOST_REQUIRE_LE(std::distance(frag0Begin, frag0End), reassembledPayload.size());
+  auto reassembledPos = std::copy(frag0Begin, frag0End, reassembledPayload.begin());
+
+  BOOST_CHECK(frags[1].has<lp::FragmentField>());
+  BOOST_CHECK(!frags[1].has<lp::IncomingFaceIdField>());
+  BOOST_CHECK_EQUAL(frags[1].get<lp::FragIndexField>(), 1);
+  BOOST_CHECK_EQUAL(frags[1].get<lp::FragCountField>(), 2);
+  BOOST_CHECK_LE(frags[1].wireEncode().size(), mtu);
+  ndn::Buffer::const_iterator frag1Begin, frag1End;
+  std::tie(frag1Begin, frag1End) = frags[1].get<lp::FragmentField>();
+  BOOST_REQUIRE_LE(std::distance(frag1Begin, frag1End),
+                   std::distance(reassembledPos, reassembledPayload.end()));
+  std::copy(frag1Begin, frag1End, reassembledPos);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(data->wireEncode().begin(), data->wireEncode().end(),
+                                reassembledPayload.begin(), reassembledPayload.end());
+}
+
+BOOST_AUTO_TEST_CASE(FragmentMtuTooSmall)
+{
+  size_t mtu = 20;
+
+  lp::Packet packet;
+  packet.add<lp::IncomingFaceIdField>(123);
+
+  shared_ptr<Data> data = makeData("/test/data1/123456789/987654321/123456789");
+  packet.add<lp::FragmentField>(std::make_pair(data->wireEncode().begin(),
+                                               data->wireEncode().end()));
+
+  bool isOk = false;
+  std::tie(isOk, std::ignore) = fragmenter.fragmentPacket(packet, mtu);
+  BOOST_REQUIRE(!isOk);
+}
+
+BOOST_AUTO_TEST_CASE(FragmentOverFragCount)
+{
+  LpFragmenter::Options options;
+  options.nMaxFragments = 2;
+  fragmenter.setOptions(options);
+
+  size_t mtu = 70;
+
+  lp::Packet packet;
+  packet.add<lp::IncomingFaceIdField>(123);
+
+  shared_ptr<Data> data = makeData("/test/data1/123456789/987654321/123456789");
+  packet.add<lp::FragmentField>(std::make_pair(data->wireEncode().begin(),
+                                               data->wireEncode().end()));
+
+  bool isOk = false;
+  std::tie(isOk, std::ignore) = fragmenter.fragmentPacket(packet, mtu);
+  BOOST_REQUIRE(!isOk);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestLpFragmentation
+BOOST_AUTO_TEST_SUITE_END() // Face
+
+} // namespace tests
+} // namespace face
+} // namespace nfd
diff --git a/tests/daemon/face/lp-reassembler.t.cpp b/tests/daemon/face/lp-reassembler.t.cpp
new file mode 100644
index 0000000..5b62d4e
--- /dev/null
+++ b/tests/daemon/face/lp-reassembler.t.cpp
@@ -0,0 +1,518 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "face/lp-reassembler.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+using namespace nfd::tests;
+
+BOOST_AUTO_TEST_SUITE(Face)
+
+class LpReassemblerFixture : public UnitTestTimeFixture
+{
+public:
+  LpReassemblerFixture()
+  {
+    reassembler.beforeTimeout.connect(
+      [this] (Transport::EndpointId remoteEp, size_t nDroppedFragments) {
+        timeoutHistory.push_back(std::make_tuple(remoteEp, nDroppedFragments));
+      });
+  }
+
+public:
+  LpReassembler reassembler;
+  std::vector<std::tuple<Transport::EndpointId, size_t>> timeoutHistory;
+
+  static const uint8_t data[10];
+};
+
+const uint8_t LpReassemblerFixture::data[10] = {
+  0x06, 0x08, // Data
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestLpReassembler, LpReassemblerFixture)
+
+BOOST_AUTO_TEST_SUITE(SingleFragment)
+
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  ndn::Buffer dataBuffer(data, sizeof(data));
+
+  lp::Packet received;
+  received.add<lp::FragmentField>(std::make_pair(dataBuffer.begin(), dataBuffer.end()));
+  received.add<lp::FragIndexField>(0);
+  received.add<lp::FragCountField>(1);
+  received.add<lp::SequenceField>(1000);
+  received.add<lp::NextHopFaceIdField>(200);
+
+  bool isComplete = false;
+  Block netPacket;
+  lp::Packet packet;
+  std::tie(isComplete, netPacket, packet) = reassembler.receiveFragment(0, received);
+
+  BOOST_REQUIRE(isComplete);
+  BOOST_CHECK(packet.has<lp::NextHopFaceIdField>());
+  BOOST_CHECK_EQUAL_COLLECTIONS(data, data + sizeof(data), netPacket.begin(), netPacket.end());
+  BOOST_CHECK_EQUAL(reassembler.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(OmitFragIndex)
+{
+  ndn::Buffer dataBuffer(data, sizeof(data));
+
+  lp::Packet received;
+  received.add<lp::FragmentField>(std::make_pair(dataBuffer.begin(), dataBuffer.end()));
+  received.add<lp::FragCountField>(1);
+  received.add<lp::SequenceField>(1000);
+  received.add<lp::NextHopFaceIdField>(200);
+
+  bool isComplete = false;
+  Block netPacket;
+  lp::Packet packet;
+  std::tie(isComplete, netPacket, packet) = reassembler.receiveFragment(0, received);
+
+  BOOST_REQUIRE(isComplete);
+  BOOST_CHECK(packet.has<lp::NextHopFaceIdField>());
+  BOOST_CHECK_EQUAL_COLLECTIONS(data, data + sizeof(data), netPacket.begin(), netPacket.end());
+}
+
+BOOST_AUTO_TEST_CASE(OmitFragCount)
+{
+  ndn::Buffer dataBuffer(data, sizeof(data));
+
+  lp::Packet received;
+  received.add<lp::FragmentField>(std::make_pair(dataBuffer.begin(), dataBuffer.end()));
+  received.add<lp::FragIndexField>(0);
+  received.add<lp::SequenceField>(1000);
+  received.add<lp::NextHopFaceIdField>(200);
+
+  bool isComplete = false;
+  Block netPacket;
+  lp::Packet packet;
+  std::tie(isComplete, netPacket, packet) = reassembler.receiveFragment(0, received);
+
+  BOOST_REQUIRE(isComplete);
+  BOOST_CHECK(packet.has<lp::NextHopFaceIdField>());
+  BOOST_CHECK_EQUAL_COLLECTIONS(data, data + sizeof(data), netPacket.begin(), netPacket.end());
+}
+
+BOOST_AUTO_TEST_CASE(OmitFragIndexAndFragCount)
+{
+  ndn::Buffer dataBuffer(data, sizeof(data));
+
+  lp::Packet received;
+  received.add<lp::FragmentField>(std::make_pair(dataBuffer.begin(), dataBuffer.end()));
+  received.add<lp::SequenceField>(1000);
+  received.add<lp::NextHopFaceIdField>(200);
+
+  bool isComplete = false;
+  Block netPacket;
+  lp::Packet packet;
+  std::tie(isComplete, netPacket, packet) = reassembler.receiveFragment(0, received);
+
+  BOOST_REQUIRE(isComplete);
+  BOOST_CHECK(packet.has<lp::NextHopFaceIdField>());
+  BOOST_CHECK_EQUAL_COLLECTIONS(data, data + sizeof(data), netPacket.begin(), netPacket.end());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // SingleFragment
+
+BOOST_AUTO_TEST_SUITE(MultiFragment)
+
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  ndn::Buffer data1Buffer(data, 4);
+  ndn::Buffer data2Buffer(data + 4, 4);
+  ndn::Buffer data3Buffer(data + 8, 2);
+
+  lp::Packet received1;
+  received1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  received1.add<lp::FragIndexField>(0);
+  received1.add<lp::FragCountField>(3);
+  received1.add<lp::SequenceField>(1000);
+  received1.add<lp::NextHopFaceIdField>(200);
+
+  lp::Packet received2;
+  received2.add<lp::FragmentField>(std::make_pair(data2Buffer.begin(), data2Buffer.end()));
+  received2.add<lp::FragIndexField>(1);
+  received2.add<lp::FragCountField>(3);
+  received2.add<lp::SequenceField>(1001);
+
+  lp::Packet received3;
+  received3.add<lp::FragmentField>(std::make_pair(data3Buffer.begin(), data3Buffer.end()));
+  received3.add<lp::FragIndexField>(2);
+  received3.add<lp::FragCountField>(3);
+  received3.add<lp::SequenceField>(1002);
+
+  bool isComplete = false;
+  Block netPacket;
+  lp::Packet packet;
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received1);
+  BOOST_REQUIRE(!isComplete);
+  BOOST_CHECK_EQUAL(reassembler.size(), 1);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received2);
+  BOOST_REQUIRE(!isComplete);
+  BOOST_CHECK_EQUAL(reassembler.size(), 1);
+
+  std::tie(isComplete, netPacket, packet) = reassembler.receiveFragment(0, received3);
+  BOOST_REQUIRE(isComplete);
+  BOOST_CHECK(packet.has<lp::NextHopFaceIdField>());
+  BOOST_CHECK_EQUAL_COLLECTIONS(data, data + sizeof(data), netPacket.begin(), netPacket.end());
+  BOOST_CHECK_EQUAL(reassembler.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(OmitFragIndex0)
+{
+  ndn::Buffer data1Buffer(data, 4);
+  ndn::Buffer data2Buffer(data + 4, 4);
+  ndn::Buffer data3Buffer(data + 8, 2);
+
+  lp::Packet received1;
+  received1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  received1.add<lp::FragCountField>(3);
+  received1.add<lp::SequenceField>(1000);
+  received1.add<lp::NextHopFaceIdField>(200);
+
+  lp::Packet received2;
+  received2.add<lp::FragmentField>(std::make_pair(data2Buffer.begin(), data2Buffer.end()));
+  received2.add<lp::FragIndexField>(1);
+  received2.add<lp::FragCountField>(3);
+  received2.add<lp::SequenceField>(1001);
+
+  lp::Packet received3;
+  received3.add<lp::FragmentField>(std::make_pair(data3Buffer.begin(), data3Buffer.end()));
+  received3.add<lp::FragIndexField>(2);
+  received3.add<lp::FragCountField>(3);
+  received3.add<lp::SequenceField>(1002);
+
+  bool isComplete = false;
+  Block netPacket;
+  lp::Packet packet;
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received1);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received2);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, netPacket, packet) = reassembler.receiveFragment(0, received3);
+  BOOST_REQUIRE(isComplete);
+  BOOST_CHECK(packet.has<lp::NextHopFaceIdField>());
+  BOOST_CHECK_EQUAL_COLLECTIONS(data, data + sizeof(data), netPacket.begin(), netPacket.end());
+}
+
+BOOST_AUTO_TEST_CASE(OutOfOrder)
+{
+  ndn::Buffer data0Buffer(data, 4);
+  ndn::Buffer data1Buffer(data + 4, 4);
+  ndn::Buffer data2Buffer(data + 8, 2);
+
+  lp::Packet frag0;
+  frag0.add<lp::FragmentField>(std::make_pair(data0Buffer.begin(), data0Buffer.end()));
+  frag0.add<lp::FragIndexField>(0);
+  frag0.add<lp::FragCountField>(3);
+  frag0.add<lp::SequenceField>(1000);
+  frag0.add<lp::NextHopFaceIdField>(200);
+
+  lp::Packet frag1;
+  frag1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  frag1.add<lp::FragIndexField>(1);
+  frag1.add<lp::FragCountField>(3);
+  frag1.add<lp::SequenceField>(1001);
+
+  lp::Packet frag2;
+  frag2.add<lp::FragmentField>(std::make_pair(data2Buffer.begin(), data2Buffer.end()));
+  frag2.add<lp::FragIndexField>(2);
+  frag2.add<lp::FragCountField>(3);
+  frag2.add<lp::SequenceField>(1002);
+
+  bool isComplete = false;
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, frag2);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, frag0);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, frag1);
+  BOOST_REQUIRE(isComplete);
+}
+
+BOOST_AUTO_TEST_CASE(Duplicate)
+{
+  ndn::Buffer data0Buffer(data, 5);
+
+  lp::Packet frag0;
+  frag0.add<lp::FragmentField>(std::make_pair(data0Buffer.begin(), data0Buffer.end()));
+  frag0.add<lp::FragIndexField>(0);
+  frag0.add<lp::FragCountField>(2);
+  frag0.add<lp::SequenceField>(1000);
+
+  bool isComplete = false;
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, frag0);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(1, frag0);
+  BOOST_REQUIRE(!isComplete);
+}
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  ndn::Buffer data1Buffer(data, 5);
+  ndn::Buffer data2Buffer(data + 5, 5);
+
+  lp::Packet received1;
+  received1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  received1.add<lp::FragIndexField>(0);
+  received1.add<lp::FragCountField>(2);
+  received1.add<lp::SequenceField>(1000);
+  received1.add<lp::NextHopFaceIdField>(200);
+
+  lp::Packet received2;
+  received2.add<lp::FragmentField>(std::make_pair(data2Buffer.begin(), data2Buffer.end()));
+  received2.add<lp::FragIndexField>(1);
+  received2.add<lp::FragCountField>(2);
+  received2.add<lp::SequenceField>(1001);
+
+  const Transport::EndpointId REMOTE_EP = 11028;
+  bool isComplete = false;
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(REMOTE_EP, received1);
+  BOOST_REQUIRE(!isComplete);
+  BOOST_CHECK_EQUAL(reassembler.size(), 1);
+  BOOST_CHECK(timeoutHistory.empty());
+
+  advanceClocks(time::milliseconds(1), 600);
+  BOOST_CHECK_EQUAL(reassembler.size(), 0);
+  BOOST_REQUIRE_EQUAL(timeoutHistory.size(), 1);
+  BOOST_CHECK_EQUAL(std::get<0>(timeoutHistory.back()), REMOTE_EP);
+  BOOST_CHECK_EQUAL(std::get<1>(timeoutHistory.back()), 1);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(REMOTE_EP, received2);
+  BOOST_REQUIRE(!isComplete);
+}
+
+BOOST_AUTO_TEST_CASE(MissingSequence)
+{
+  ndn::Buffer data1Buffer(data, 4);
+  ndn::Buffer data2Buffer(data + 4, 4);
+  ndn::Buffer data3Buffer(data + 8, 2);
+
+  lp::Packet received1;
+  received1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  received1.add<lp::FragIndexField>(0);
+  received1.add<lp::FragCountField>(3);
+  received1.add<lp::SequenceField>(1000);
+  received1.add<lp::NextHopFaceIdField>(200);
+
+  lp::Packet received2;
+  received2.add<lp::FragmentField>(std::make_pair(data2Buffer.begin(), data2Buffer.end()));
+  received2.add<lp::FragIndexField>(1);
+  received2.add<lp::FragCountField>(3);
+
+  lp::Packet received3;
+  received3.add<lp::FragmentField>(std::make_pair(data3Buffer.begin(), data3Buffer.end()));
+  received3.add<lp::FragIndexField>(2);
+  received3.add<lp::FragCountField>(3);
+  received3.add<lp::SequenceField>(1002);
+
+  bool isComplete = false;
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received1);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received2);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received3);
+  BOOST_REQUIRE(!isComplete);
+
+  advanceClocks(time::milliseconds(1), 600);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received2);
+  BOOST_REQUIRE(!isComplete);
+}
+
+BOOST_AUTO_TEST_CASE(FragCountOverLimit)
+{
+  ndn::Buffer data1Buffer(data, sizeof(data));
+
+  lp::Packet received1;
+  received1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  received1.add<lp::FragIndexField>(0);
+  received1.add<lp::FragCountField>(256);
+  received1.add<lp::SequenceField>(1000);
+  received1.add<lp::NextHopFaceIdField>(200);
+
+  bool isComplete = false;
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received1);
+  BOOST_REQUIRE(!isComplete);
+}
+
+BOOST_AUTO_TEST_CASE(MissingFragCount)
+{
+  ndn::Buffer data1Buffer(data, 4);
+  ndn::Buffer data2Buffer(data + 4, 4);
+  ndn::Buffer data3Buffer(data + 8, 2);
+
+  lp::Packet received1;
+  received1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  received1.add<lp::FragIndexField>(0);
+  received1.add<lp::FragCountField>(3);
+  received1.add<lp::SequenceField>(1000);
+  received1.add<lp::NextHopFaceIdField>(200);
+
+  lp::Packet received2;
+  received2.add<lp::FragmentField>(std::make_pair(data2Buffer.begin(), data2Buffer.end()));
+  received2.add<lp::FragIndexField>(1);
+  received2.add<lp::FragCountField>(50);
+  received2.add<lp::SequenceField>(1001);
+
+  lp::Packet received3;
+  received3.add<lp::FragmentField>(std::make_pair(data3Buffer.begin(), data3Buffer.end()));
+  received3.add<lp::FragIndexField>(2);
+  received3.add<lp::FragCountField>(3);
+  received3.add<lp::SequenceField>(1002);
+
+  bool isComplete = false;
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received1);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received2);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received3);
+  BOOST_REQUIRE(!isComplete);
+}
+
+BOOST_AUTO_TEST_CASE(OverFragCount)
+{
+  LpReassembler::Options options;
+  options.nMaxFragments = 2;
+  reassembler.setOptions(options);
+
+  ndn::Buffer data1Buffer(data, 4);
+  ndn::Buffer data2Buffer(data + 4, 4);
+  ndn::Buffer data3Buffer(data + 8, 2);
+
+  lp::Packet received1;
+  received1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  received1.add<lp::FragIndexField>(0);
+  received1.add<lp::FragCountField>(3);
+  received1.add<lp::SequenceField>(1000);
+  received1.add<lp::NextHopFaceIdField>(200);
+
+  lp::Packet received2;
+  received2.add<lp::FragmentField>(std::make_pair(data2Buffer.begin(), data2Buffer.end()));
+  received2.add<lp::FragIndexField>(1);
+  received2.add<lp::FragCountField>(3);
+  received2.add<lp::SequenceField>(1001);
+
+  lp::Packet received3;
+  received3.add<lp::FragmentField>(std::make_pair(data3Buffer.begin(), data3Buffer.end()));
+  received3.add<lp::FragIndexField>(2);
+  received3.add<lp::FragCountField>(3);
+  received3.add<lp::SequenceField>(1002);
+
+  bool isComplete = false;
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received1);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received2);
+  BOOST_REQUIRE(!isComplete);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(0, received3);
+  BOOST_REQUIRE(!isComplete);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // MultiFragment
+
+BOOST_AUTO_TEST_CASE(MultiRemoteEndpoints)
+{
+  ndn::Buffer data1Buffer(data, 5);
+  ndn::Buffer data2Buffer(data + 5, 5);
+
+  lp::Packet frag1_1;
+  frag1_1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  frag1_1.add<lp::FragIndexField>(0);
+  frag1_1.add<lp::FragCountField>(2);
+  frag1_1.add<lp::SequenceField>(2000);
+  frag1_1.add<lp::NextHopFaceIdField>(200);
+
+  lp::Packet frag1_2;
+  frag1_2.add<lp::FragmentField>(std::make_pair(data2Buffer.begin(), data2Buffer.end()));
+  frag1_2.add<lp::FragIndexField>(1);
+  frag1_2.add<lp::FragCountField>(2);
+  frag1_2.add<lp::SequenceField>(2001);
+
+  lp::Packet frag2_1;
+  frag2_1.add<lp::FragmentField>(std::make_pair(data1Buffer.begin(), data1Buffer.end()));
+  frag2_1.add<lp::FragIndexField>(0);
+  frag2_1.add<lp::FragCountField>(2);
+  frag2_1.add<lp::SequenceField>(2000);
+  frag2_1.add<lp::NextHopFaceIdField>(200);
+
+  lp::Packet frag2_2;
+  frag2_2.add<lp::FragmentField>(std::make_pair(data2Buffer.begin(), data2Buffer.end()));
+  frag2_2.add<lp::FragIndexField>(1);
+  frag2_2.add<lp::FragCountField>(2);
+  frag2_2.add<lp::SequenceField>(2001);
+
+  bool isComplete = false;
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(1, frag1_1);
+  BOOST_REQUIRE(!isComplete);
+  BOOST_CHECK_EQUAL(reassembler.size(), 1);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(2, frag2_2);
+  BOOST_REQUIRE(!isComplete);
+  BOOST_CHECK_EQUAL(reassembler.size(), 2);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(1, frag1_2);
+  BOOST_REQUIRE(isComplete);
+  BOOST_CHECK_EQUAL(reassembler.size(), 1);
+
+  std::tie(isComplete, std::ignore, std::ignore) = reassembler.receiveFragment(2, frag2_1);
+  BOOST_REQUIRE(isComplete);
+  BOOST_CHECK_EQUAL(reassembler.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestLpReassembler
+BOOST_AUTO_TEST_SUITE_END() // Face
+
+} // namespace tests
+} // namespace face
+} // namespace nfd