face: Acks acknowledge TxSequence instead of Sequence

refs #3931

Change-Id: I83919fe815c2a43e47eb09d754f77166c051d013
diff --git a/daemon/face/generic-link-service.cpp b/daemon/face/generic-link-service.cpp
index 25d52bd..000245e 100644
--- a/daemon/face/generic-link-service.cpp
+++ b/daemon/face/generic-link-service.cpp
@@ -1,5 +1,5 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
  * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
@@ -134,7 +134,14 @@
 GenericLinkService::sendNetPacket(lp::Packet&& pkt)
 {
   std::vector<lp::Packet> frags;
-  const ssize_t mtu = this->getTransport()->getMtu();
+  ssize_t mtu = this->getTransport()->getMtu();
+
+  // Make space for feature fields in fragments
+  if (m_options.reliabilityOptions.isEnabled && mtu != MTU_UNLIMITED) {
+    mtu -= LpReliability::RESERVED_HEADER_SPACE;
+    BOOST_ASSERT(mtu > 0);
+  }
+
   if (m_options.allowFragmentation && mtu != MTU_UNLIMITED) {
     bool isOk = false;
     std::tie(isOk, frags) = m_fragmenter.fragmentPacket(pkt, mtu);
@@ -155,16 +162,14 @@
     BOOST_ASSERT(!frags.front().has<lp::FragCountField>());
   }
 
-  // Only assign sequences to fragments if reliability enabled and packet contains a fragment,
-  // or there is more than 1 fragment
-  if ((m_options.reliabilityOptions.isEnabled && frags.front().has<lp::FragmentField>()) ||
-      frags.size() > 1) {
+  // Only assign sequences to fragments if packet contains more than 1 fragment
+  if (frags.size() > 1) {
     // Assign sequences to all fragments
     this->assignSequences(frags);
   }
 
   if (m_options.reliabilityOptions.isEnabled && frags.front().has<lp::FragmentField>()) {
-    m_reliability.observeOutgoing(frags);
+    m_reliability.handleOutgoing(frags);
   }
 
   for (lp::Packet& frag : frags) {
diff --git a/daemon/face/lp-reliability.cpp b/daemon/face/lp-reliability.cpp
index 584e6bd..2b587f9 100644
--- a/daemon/face/lp-reliability.cpp
+++ b/daemon/face/lp-reliability.cpp
@@ -1,5 +1,5 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
  * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
@@ -34,6 +34,7 @@
   : m_options(options)
   , m_linkService(linkService)
   , m_firstUnackedFrag(m_unackedFrags.begin())
+  , m_lastTxSeqNo(-1) // set to "-1" to start TxSequence numbers at 0
   , m_isIdleAckTimerRunning(false)
 {
   BOOST_ASSERT(m_linkService != nullptr);
@@ -60,27 +61,36 @@
 }
 
 void
-LpReliability::observeOutgoing(const std::vector<lp::Packet>& frags)
+LpReliability::handleOutgoing(std::vector<lp::Packet>& frags)
 {
   BOOST_ASSERT(m_options.isEnabled);
 
-  // The sequence number of the first fragment is used to identify the NetPkt.
-  lp::Sequence netPktIdentifier = frags.at(0).get<lp::SequenceField>();
-  auto& netPkt = m_netPkts.emplace(netPktIdentifier, NetPkt{}).first->second;
   auto unackedFragsIt = m_unackedFrags.begin();
-  auto netPktUnackedFragsIt = netPkt.unackedFrags.begin();
+  auto sendTime = time::steady_clock::now();
 
-  for (const lp::Packet& frag : frags) {
+  auto netPkt = make_shared<NetPkt>();
+  netPkt->unackedFrags.reserve(frags.size());
+
+  for (lp::Packet& frag : frags) {
+    // Assign TxSequence number
+    lp::Sequence txSeq = assignTxSequence(frag);
+
     // Store LpPacket for future retransmissions
-    lp::Sequence seq = frag.get<lp::SequenceField>();
-    unackedFragsIt = m_unackedFrags.emplace_hint(unackedFragsIt, seq, frag);
+    unackedFragsIt = m_unackedFrags.emplace_hint(unackedFragsIt,
+                                                 std::piecewise_construct,
+                                                 std::forward_as_tuple(txSeq),
+                                                 std::forward_as_tuple(frag));
+    unackedFragsIt->second.sendTime = sendTime;
     unackedFragsIt->second.rtoTimer =
-      scheduler::schedule(m_rto.computeRto(), bind(&LpReliability::onLpPacketLost, this, seq));
-    unackedFragsIt->second.sendTime = time::steady_clock::now();
-    netPktUnackedFragsIt = netPkt.unackedFrags.insert(netPktUnackedFragsIt, seq);
+      scheduler::schedule(m_rto.computeRto(), bind(&LpReliability::onLpPacketLost, this, unackedFragsIt));
+    unackedFragsIt->second.netPkt = netPkt;
+
     if (m_unackedFrags.size() == 1) {
-      m_firstUnackedFrag = unackedFragsIt;
+      m_firstUnackedFrag = m_unackedFrags.begin();
     }
+
+    // Add to associated NetPkt
+    netPkt->unackedFrags.push_back(unackedFragsIt);
   }
 }
 
@@ -93,39 +103,39 @@
 
   // Extract and parse Acks
   for (lp::Sequence ackSeq : pkt.list<lp::AckField>()) {
-    auto txFrag = m_unackedFrags.find(ackSeq);
-    if (txFrag == m_unackedFrags.end()) {
-      // Ignore an Ack for an unknown sequence number
+    auto fragIt = m_unackedFrags.find(ackSeq);
+    if (fragIt == m_unackedFrags.end()) {
+      // Ignore an Ack for an unknown TxSequence number
       continue;
     }
+    auto& frag = fragIt->second;
 
     // Cancel the RTO timer for the acknowledged fragment
-    txFrag->second.rtoTimer.cancel();
+    frag.rtoTimer.cancel();
 
-    if (txFrag->second.retxCount == 0) {
+    if (frag.retxCount == 0) {
       // This sequence had no retransmissions, so use it to calculate the RTO
-      m_rto.addMeasurement(time::duration_cast<RttEstimator::Duration>(now - txFrag->second.sendTime));
+      m_rto.addMeasurement(time::duration_cast<RttEstimator::Duration>(now - frag.sendTime));
     }
 
-    // Look for Acks with sequence numbers < ackSeq (allowing for wraparound) and consider them lost
-    // if a configurable number of Acks containing greater sequence numbers have been received.
-    auto lostLpPackets = findLostLpPackets(ackSeq);
+    // Look for frags with TxSequence numbers < ackSeq (allowing for wraparound) and consider them
+    // lost if a configurable number of Acks containing greater TxSequence numbers have been
+    // received.
+    auto lostLpPackets = findLostLpPackets(fragIt);
 
-    // Remove the fragment from the map of unacknowledged sequences and from its associated network
-    // packet (removing the network packet if it has been received in whole by remote host).
-    // Potentially increment the start of the window.
-    onLpPacketAcknowledged(txFrag, getNetPktByFrag(ackSeq));
+    // Remove the fragment from the map of unacknowledged fragments and from its associated network
+    // packet. Potentially increment the start of the window.
+    onLpPacketAcknowledged(fragIt);
 
-    // Resend or fail fragments considered lost. This must be done separately from the above
-    // enhanced for loop because onLpPacketLost may delete the fragment from m_unackedFrags.
-    for (const lp::Sequence& seq : lostLpPackets) {
-      this->onLpPacketLost(seq);
+    // Resend or fail fragments considered lost. Potentially increment the start of the window.
+    for (UnackedFrags::iterator txSeqIt : lostLpPackets) {
+      this->onLpPacketLost(txSeqIt);
     }
   }
 
-  // If has Fragment field, extract Sequence and add to AckQueue
-  if (pkt.has<lp::FragmentField>() && pkt.has<lp::SequenceField>()) {
-    m_ackQueue.push(pkt.get<lp::SequenceField>());
+  // If packet has Fragment and TxSequence fields, extract TxSequence and add to AckQueue
+  if (pkt.has<lp::FragmentField>() && pkt.has<lp::TxSequenceField>()) {
+    m_ackQueue.push(pkt.get<lp::TxSequenceField>());
     if (!m_isIdleAckTimerRunning) {
       this->startIdleAckTimer();
     }
@@ -136,22 +146,44 @@
 LpReliability::piggyback(lp::Packet& pkt, ssize_t mtu)
 {
   BOOST_ASSERT(m_options.isEnabled);
+  BOOST_ASSERT(pkt.wireEncode().type() == lp::tlv::LpPacket);
 
-  int maxAcks = std::numeric_limits<int>::max();
-  if (mtu > 0) {
-    // Ack Type (3 octets) + Ack Length (1 octet) + sizeof(lp::Sequence)
-    size_t ackSize = 3 + 1 + sizeof(lp::Sequence);
-    maxAcks = (mtu - pkt.wireEncode().size()) / ackSize;
-  }
+  // up to 2 extra octets reserved for potential TLV-LENGTH size increases
+  ssize_t pktSize = pkt.wireEncode().size();
+  ssize_t reservedSpace = tlv::sizeOfVarNumber(ndn::MAX_NDN_PACKET_SIZE) -
+                          tlv::sizeOfVarNumber(pktSize);
+  ssize_t remainingSpace = (mtu == MTU_UNLIMITED ? ndn::MAX_NDN_PACKET_SIZE : mtu) - reservedSpace;
+  remainingSpace -= pktSize;
 
-  ssize_t nAcksInPkt = 0;
-  while (!m_ackQueue.empty() && nAcksInPkt < maxAcks) {
-    pkt.add<lp::AckField>(m_ackQueue.front());
+  while (!m_ackQueue.empty()) {
+    lp::Sequence ackSeq = m_ackQueue.front();
+    // Ack Size = Ack Type (3 octets) + Ack Length (1 octet) + Value (1, 2, 4, or 8 octets)
+    ssize_t ackSize = tlv::sizeOfVarNumber(lp::tlv::Ack) +
+                      tlv::sizeOfVarNumber(
+                        tlv::sizeOfNonNegativeInteger(std::numeric_limits<lp::Sequence>::max())) +
+                      tlv::sizeOfNonNegativeInteger(ackSeq);
+
+    if (ackSize > remainingSpace) {
+      break;
+    }
+
+    pkt.add<lp::AckField>(ackSeq);
     m_ackQueue.pop();
-    nAcksInPkt++;
+    remainingSpace -= ackSize;
   }
 }
 
+lp::Sequence
+LpReliability::assignTxSequence(lp::Packet& frag)
+{
+  lp::Sequence txSeq = ++m_lastTxSeqNo;
+  frag.set<lp::TxSequenceField>(txSeq);
+  if (m_unackedFrags.size() > 0 && m_lastTxSeqNo == m_firstUnackedFrag->first) {
+    BOOST_THROW_EXCEPTION(std::length_error("TxSequence range exceeded"));
+  }
+  return m_lastTxSeqNo;
+}
+
 void
 LpReliability::startIdleAckTimer()
 {
@@ -174,27 +206,25 @@
   m_isIdleAckTimerRunning = false;
 }
 
-std::vector<lp::Sequence>
-LpReliability::findLostLpPackets(lp::Sequence ackSeq)
+std::vector<LpReliability::UnackedFrags::iterator>
+LpReliability::findLostLpPackets(LpReliability::UnackedFrags::iterator ackIt)
 {
-  std::vector<lp::Sequence> lostLpPackets;
+  std::vector<UnackedFrags::iterator> lostLpPackets;
 
   for (auto it = m_firstUnackedFrag; ; ++it) {
     if (it == m_unackedFrags.end()) {
       it = m_unackedFrags.begin();
     }
 
-    if (it->first == ackSeq) {
+    if (it->first == ackIt->first) {
       break;
     }
 
     auto& unackedFrag = it->second;
-
     unackedFrag.nGreaterSeqAcks++;
 
-    if (unackedFrag.nGreaterSeqAcks >= m_options.seqNumLossThreshold && !unackedFrag.wasTimedOutBySeq) {
-      unackedFrag.wasTimedOutBySeq = true;
-      lostLpPackets.push_back(it->first);
+    if (unackedFrag.nGreaterSeqAcks >= m_options.seqNumLossThreshold) {
+      lostLpPackets.push_back(it);
     }
   }
 
@@ -202,85 +232,101 @@
 }
 
 void
-LpReliability::onLpPacketLost(lp::Sequence seq)
+LpReliability::onLpPacketLost(UnackedFrags::iterator txSeqIt)
 {
-  auto& txFrag = m_unackedFrags.at(seq);
-  auto netPktIt = getNetPktByFrag(seq);
+  BOOST_ASSERT(m_unackedFrags.count(txSeqIt->first) > 0);
+
+  auto& txFrag = txSeqIt->second;
+  txFrag.rtoTimer.cancel();
+  auto netPkt = txFrag.netPkt;
 
   // Check if maximum number of retransmissions exceeded
   if (txFrag.retxCount >= m_options.maxRetx) {
-    // Delete all LpPackets of NetPkt from TransmitCache
-    lp::Sequence firstSeq = *(netPktIt->second.unackedFrags.begin());
-    lp::Sequence lastSeq = *(std::prev(netPktIt->second.unackedFrags.end()));
-    if (lastSeq >= firstSeq) { // Normal case: no wraparound
-      m_unackedFrags.erase(m_unackedFrags.find(firstSeq), std::next(m_unackedFrags.find(lastSeq)));
+    // Delete all LpPackets of NetPkt from m_unackedFrags (except this one)
+    for (size_t i = 0; i < netPkt->unackedFrags.size(); i++) {
+      if (netPkt->unackedFrags[i] != txSeqIt) {
+        deleteUnackedFrag(netPkt->unackedFrags[i]);
+      }
     }
-    else { // sequence number wraparound
-      m_unackedFrags.erase(m_unackedFrags.find(firstSeq), m_unackedFrags.end());
-      m_unackedFrags.erase(m_unackedFrags.begin(), std::next(m_unackedFrags.find(lastSeq)));
-    }
-
-    m_netPkts.erase(netPktIt);
 
     ++m_linkService->nRetxExhausted;
+    deleteUnackedFrag(txSeqIt);
   }
   else {
-    txFrag.retxCount++;
+    // Assign new TxSequence
+    lp::Sequence newTxSeq = assignTxSequence(txFrag.pkt);
 
-    // Start RTO timer for this sequence
-    txFrag.rtoTimer = scheduler::schedule(m_rto.computeRto(),
-                                          bind(&LpReliability::onLpPacketLost, this, seq));
+    // Move fragment to new TxSequence mapping
+    auto newTxFragIt = m_unackedFrags.emplace_hint(
+      m_firstUnackedFrag != m_unackedFrags.end() && m_firstUnackedFrag->first > newTxSeq
+        ? m_firstUnackedFrag
+        : m_unackedFrags.end(),
+      std::piecewise_construct,
+      std::forward_as_tuple(newTxSeq),
+      std::forward_as_tuple(txFrag.pkt));
+    auto& newTxFrag = newTxFragIt->second;
+    newTxFrag.retxCount = txFrag.retxCount + 1;
+    newTxFrag.netPkt = netPkt;
+
+    // Update associated NetPkt
+    auto fragInNetPkt = std::find(netPkt->unackedFrags.begin(), netPkt->unackedFrags.end(), txSeqIt);
+    BOOST_ASSERT(fragInNetPkt != netPkt->unackedFrags.end());
+    *fragInNetPkt = newTxFragIt;
+
+    deleteUnackedFrag(txSeqIt);
 
     // Retransmit fragment
-    m_linkService->sendLpPacket(lp::Packet(txFrag.pkt));
+    m_linkService->sendLpPacket(lp::Packet(newTxFrag.pkt));
+
+    // Start RTO timer for this sequence
+    newTxFrag.rtoTimer = scheduler::schedule(m_rto.computeRto(),
+                                          bind(&LpReliability::onLpPacketLost, this, newTxFragIt));
   }
 }
 
 void
-LpReliability::onLpPacketAcknowledged(std::map<lp::Sequence, LpReliability::UnackedFrag>::iterator fragIt,
-                                      std::map<lp::Sequence, LpReliability::NetPkt>::iterator netPktIt)
+LpReliability::onLpPacketAcknowledged(UnackedFrags::iterator fragIt)
 {
-  lp::Sequence seq = fragIt->first;
-  // We need to store the sequence of the window begin in case we are erasing it from m_unackedFrags
-  lp::Sequence firstUnackedSeq = m_firstUnackedFrag->first;
-  auto nextSeqIt = m_unackedFrags.erase(fragIt);
-  netPktIt->second.unackedFrags.erase(seq);
+  auto netPkt = fragIt->second.netPkt;
 
-  if (!m_unackedFrags.empty() && firstUnackedSeq == seq) {
-    // If "first" fragment in send window (allowing for wraparound), increment window begin
-    if (nextSeqIt == m_unackedFrags.end()) {
-      m_firstUnackedFrag = m_unackedFrags.begin();
-    }
-    else {
-      m_firstUnackedFrag = nextSeqIt;
-    }
-  }
+  // Remove from NetPkt unacked fragment list
+  auto fragInNetPkt = std::find(netPkt->unackedFrags.begin(), netPkt->unackedFrags.end(), fragIt);
+  BOOST_ASSERT(fragInNetPkt != netPkt->unackedFrags.end());
+  *fragInNetPkt = netPkt->unackedFrags.back();
+  netPkt->unackedFrags.pop_back();
 
-  // Check if network-layer packet completely received. If so, delete network packet mapping
-  // and increment counter
-  if (netPktIt->second.unackedFrags.empty()) {
-    if (netPktIt->second.didRetx) {
+  // Check if network-layer packet completely received. If so, increment counters
+  if (netPkt->unackedFrags.empty()) {
+    if (netPkt->didRetx) {
       ++m_linkService->nRetransmitted;
     }
     else {
       ++m_linkService->nAcknowledged;
     }
-    m_netPkts.erase(netPktIt);
   }
+
+  deleteUnackedFrag(fragIt);
 }
 
-std::map<lp::Sequence, LpReliability::NetPkt>::iterator
-LpReliability::getNetPktByFrag(lp::Sequence seq)
+void
+LpReliability::deleteUnackedFrag(UnackedFrags::iterator fragIt)
 {
-  BOOST_ASSERT(!m_netPkts.empty());
-  auto it = m_netPkts.lower_bound(seq);
-  if (it == m_netPkts.end()) {
-    // This can happen because of sequence number wraparound in the middle of a network packet.
-    // In this case, the network packet will be at the end of m_netPkts and we will need to
-    // decrement the iterator to m_netPkts.end() to the one before it.
-    --it;
+  lp::Sequence firstUnackedTxSeq = m_firstUnackedFrag->first;
+  lp::Sequence currentTxSeq = fragIt->first;
+  auto nextFragIt = m_unackedFrags.erase(fragIt);
+
+  if (!m_unackedFrags.empty() && firstUnackedTxSeq == currentTxSeq) {
+    // If "first" fragment in send window (allowing for wraparound), increment window begin
+    if (nextFragIt == m_unackedFrags.end()) {
+      m_firstUnackedFrag = m_unackedFrags.begin();
+    }
+    else {
+      m_firstUnackedFrag = nextFragIt;
+    }
   }
-  return it;
+  else if (m_unackedFrags.empty()) {
+    m_firstUnackedFrag = m_unackedFrags.end();
+  }
 }
 
 LpReliability::UnackedFrag::UnackedFrag(lp::Packet pkt)
@@ -288,12 +334,6 @@
   , sendTime(time::steady_clock::now())
   , retxCount(0)
   , nGreaterSeqAcks(0)
-  , wasTimedOutBySeq(false)
-{
-}
-
-LpReliability::NetPkt::NetPkt()
-  : didRetx(false)
 {
 }
 
diff --git a/daemon/face/lp-reliability.hpp b/daemon/face/lp-reliability.hpp
index b3627a9..d1f4622 100644
--- a/daemon/face/lp-reliability.hpp
+++ b/daemon/face/lp-reliability.hpp
@@ -1,5 +1,5 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
  * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
@@ -79,11 +79,11 @@
   const GenericLinkService*
   getLinkService() const;
 
-  /** \brief observe outgoing fragment(s) of a network packet
+  /** \brief observe outgoing fragment(s) of a network packet and store for potential retransmission
    *  \param frags fragments of network packet
    */
   void
-  observeOutgoing(const std::vector<lp::Packet>& frags);
+  handleOutgoing(std::vector<lp::Packet>& frags);
 
   /** \brief extract and parse all Acks and add Ack for contained Fragment (if any) to AckQueue
    *  \param pkt incoming LpPacket
@@ -99,6 +99,19 @@
   piggyback(lp::Packet& pkt, ssize_t mtu);
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  class UnackedFrag;
+  class NetPkt;
+  using UnackedFrags = std::map<lp::Sequence, UnackedFrag>;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  /** \brief assign TxSequence number to a fragment
+   *  \param frag fragment to assign TxSequence to
+   *  \return assigned TxSequence number
+   *  \throw std::length_error assigned TxSequence is equal to the start of the existing window
+   */
+  lp::Sequence
+  assignTxSequence(lp::Packet& frag);
+
   /** \brief start the idle Ack timer
    *
    * This timer requests an IDLE packet to acknowledge pending fragments not already piggybacked.
@@ -113,44 +126,46 @@
   void
   stopIdleAckTimer();
 
-private:
-  /** \brief find and mark as lost fragments where a configurable number of Acks have been received
-   *         for greater sequence numbers
-   *  \param ackSeq sequence number of received Ack
-   *  \return vector containing sequence numbers marked lost by this mechanism
+  /** \brief find and mark as lost fragments where a configurable number of Acks
+   *         (\p m_options.seqNumLossThreshold) have been received for greater TxSequence numbers
+   *  \param ackIt iterator pointing to acknowledged fragment
+   *  \return vector containing iterators to fragments marked lost by this mechanism
    */
-  std::vector<lp::Sequence>
-  findLostLpPackets(lp::Sequence ackSeq);
+  std::vector<UnackedFrags::iterator>
+  findLostLpPackets(UnackedFrags::iterator ackIt);
 
-  /** \brief resend (or declare as lost) a lost fragment
+  /** \brief resend (or give up on) a lost fragment
    */
   void
-  onLpPacketLost(lp::Sequence seq);
-
-  class UnackedFrag;
-  class NetPkt;
+  onLpPacketLost(UnackedFrags::iterator txSeqIt);
 
   /** \brief remove the fragment with the given sequence number from the map of unacknowledged
-   *         fragments as well as its associated network packet
-   *  \param fragIt iterator to fragment to be removed
+   *         fragments, as well as its associated network packet (if any)
+   *  \param fragIt iterator to acknowledged fragment
    *
-   *  If the given sequence marks the beginning of the send window, the window will be incremented.
+   *  If the given TxSequence marks the beginning of the send window, the window will be incremented.
    *  If the associated network packet has been fully transmitted, it will be removed.
    */
   void
-  onLpPacketAcknowledged(std::map<lp::Sequence, UnackedFrag>::iterator fragIt,
-                         std::map<lp::Sequence, NetPkt>::iterator netPktIt);
+  onLpPacketAcknowledged(UnackedFrags::iterator fragIt);
 
-  std::map<lp::Sequence, NetPkt>::iterator
-  getNetPktByFrag(lp::Sequence seq);
+  /** \brief delete a fragment from UnackedFrags and advance acknowledge window if necessary
+   *  \param fragIt iterator to an UnackedFrag, must be dereferencable
+   *  \post fragIt is not in m_unackedFrags
+   *  \post if was equal to m_firstUnackedFrag,
+   *        m_firstUnackedFrag is set to the UnackedFrag after fragIt with consideration of
+   *        TxSequence number wraparound, or set to m_unackedFrags.end() if m_unackedFrags is empty
+   */
+  void
+  deleteUnackedFrag(UnackedFrags::iterator fragIt);
 
-private:
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   /** \brief contains a sent fragment that has not been acknowledged and associated data
    */
   class UnackedFrag
   {
   public:
-    // Allows implicit conversion from an lp::Packet
+    explicit
     UnackedFrag(lp::Packet pkt);
 
   public:
@@ -159,7 +174,7 @@
     time::steady_clock::TimePoint sendTime;
     size_t retxCount;
     size_t nGreaterSeqAcks; //!< number of Acks received for sequences greater than this fragment
-    bool wasTimedOutBySeq; //!< whether this fragment has been timed out by the sequence number mechanic
+    shared_ptr<NetPkt> netPkt;
   };
 
   /** \brief contains a network-layer packet with unacknowledged fragments
@@ -167,20 +182,26 @@
   class NetPkt
   {
   public:
-    NetPkt();
-
-  public:
-    std::set<lp::Sequence> unackedFrags;
-    bool didRetx;
+    std::vector<UnackedFrags::iterator> unackedFrags;
+    bool didRetx = false;
   };
 
+public:
+  /// TxSequence TLV-TYPE (3 octets) + TxSequence TLV-LENGTH (1 octet) + sizeof(lp::Sequence)
+  static constexpr size_t RESERVED_HEADER_SPACE = 3 + 1 + sizeof(lp::Sequence);
+
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   Options m_options;
   GenericLinkService* m_linkService;
-  std::map<lp::Sequence, UnackedFrag> m_unackedFrags;
-  std::map<lp::Sequence, UnackedFrag>::iterator m_firstUnackedFrag;
-  std::map<lp::Sequence, NetPkt> m_netPkts;
+  UnackedFrags m_unackedFrags;
+  /** An iterator that points to the first unacknowledged fragment in the current window. The window
+   *  can wrap around so that the beginning of the window is at a TxSequence greater than other
+   *  fragments in the window. When the window is moved past the last item in the iterator, the
+   *  first fragment in the map will become the start of the window.
+   */
+  UnackedFrags::iterator m_firstUnackedFrag;
   std::queue<lp::Sequence> m_ackQueue;
+  lp::Sequence m_lastTxSeqNo;
   scheduler::ScopedEventId m_idleAckTimer;
   bool m_isIdleAckTimerRunning;
   RttEstimator m_rto;
diff --git a/tests/daemon/face/generic-link-service.t.cpp b/tests/daemon/face/generic-link-service.t.cpp
index bcff9de..e4e82f7 100644
--- a/tests/daemon/face/generic-link-service.t.cpp
+++ b/tests/daemon/face/generic-link-service.t.cpp
@@ -1,5 +1,5 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
  * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
@@ -399,7 +399,7 @@
 
 BOOST_AUTO_TEST_SUITE(Reliability)
 
-BOOST_AUTO_TEST_CASE(SendInterestWithSequence)
+BOOST_AUTO_TEST_CASE(SendInterest)
 {
   // Initialize with Options that enables reliability
   GenericLinkService::Options options;
@@ -416,10 +416,10 @@
   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_CHECK(interest1pkt.has<lp::TxSequenceField>());
 }
 
-BOOST_AUTO_TEST_CASE(SendDataWithSequence)
+BOOST_AUTO_TEST_CASE(SendData)
 {
   // Initialize with Options that enables reliability
   GenericLinkService::Options options;
@@ -436,10 +436,10 @@
   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_CHECK(data1pkt.has<lp::TxSequenceField>());
 }
 
-BOOST_AUTO_TEST_CASE(SendNackWithSequence)
+BOOST_AUTO_TEST_CASE(SendNack)
 {
   // Initialize with Options that enables reliability
   GenericLinkService::Options options;
@@ -457,7 +457,7 @@
   BOOST_REQUIRE_NO_THROW(nack1pkt.wireDecode(transport->sentPackets.back().packet));
   BOOST_CHECK(nack1pkt.has<lp::NackField>());
   BOOST_CHECK(nack1pkt.has<lp::FragmentField>());
-  BOOST_CHECK(nack1pkt.has<lp::SequenceField>());
+  BOOST_CHECK(nack1pkt.has<lp::TxSequenceField>());
 }
 
 BOOST_AUTO_TEST_SUITE_END() // Reliability
diff --git a/tests/daemon/face/lp-reliability.t.cpp b/tests/daemon/face/lp-reliability.t.cpp
index 82e1f79..5909737 100644
--- a/tests/daemon/face/lp-reliability.t.cpp
+++ b/tests/daemon/face/lp-reliability.t.cpp
@@ -1,5 +1,5 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
  * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
@@ -25,13 +25,14 @@
 
 #include "face/lp-reliability.hpp"
 #include "face/face.hpp"
-#include "face/lp-fragmenter.hpp"
 #include "face/generic-link-service.hpp"
 
 #include "tests/test-common.hpp"
 #include "dummy-face.hpp"
 #include "dummy-transport.hpp"
 
+#include <cstring>
+
 namespace nfd {
 namespace face {
 namespace tests {
@@ -53,10 +54,10 @@
   sendLpPackets(std::vector<lp::Packet> frags)
   {
     if (frags.front().has<lp::FragmentField>()) {
-      m_reliability.observeOutgoing(frags);
+      m_reliability.handleOutgoing(frags);
     }
 
-    for (lp::Packet& frag : frags) {
+    for (lp::Packet frag : frags) {
       this->sendLpPacket(std::move(frag));
     }
   }
@@ -103,6 +104,58 @@
     linkService->setOptions(options);
 
     reliability = linkService->getLpReliability();
+    reliability->m_lastTxSeqNo = 1;
+  }
+
+  static bool
+  netPktHasUnackedFrag(const shared_ptr<LpReliability::NetPkt>& netPkt, lp::Sequence txSeq)
+  {
+    return std::any_of(netPkt->unackedFrags.begin(), netPkt->unackedFrags.end(),
+                       [txSeq] (const LpReliability::UnackedFrags::iterator& frag) {
+                         return frag->first == txSeq;
+                       });
+  }
+
+  LpReliability::UnackedFrags::iterator
+  getIteratorFromTxSeq(lp::Sequence txSeq)
+  {
+    return reliability->m_unackedFrags.find(txSeq);
+  }
+
+  /** \brief make an LpPacket with fragment of specified size
+   *  \param pktNo packet identifier, which can be extracted with \p getPktNo
+   *  \param payloadSize total payload size; if this is less than 4, 4 will be used
+   */
+  static lp::Packet
+  makeFrag(uint32_t pktNo, size_t payloadSize = 4)
+  {
+    payloadSize = std::max(payloadSize, static_cast<size_t>(4));
+    BOOST_ASSERT(payloadSize <= 255);
+
+    lp::Packet pkt;
+    ndn::Buffer buf(payloadSize);
+    std::memcpy(buf.buf(), &pktNo, sizeof(pktNo));
+    pkt.set<lp::FragmentField>(make_pair(buf.cbegin(), buf.cend()));
+    return pkt;
+  }
+
+  /** \brief extract packet identifier from LpPacket made with \p makeFrag
+   *  \retval 0 packet identifier cannot be extracted
+   */
+  static uint32_t
+  getPktNo(const lp::Packet& pkt)
+  {
+    BOOST_ASSERT(pkt.has<lp::FragmentField>());
+
+    ndn::Buffer::const_iterator begin, end;
+    std::tie(begin, end) = pkt.get<lp::FragmentField>();
+    if (std::distance(begin, end) < 4) {
+      return 0;
+    }
+
+    uint32_t value = 0;
+    std::memcpy(&value, &*begin, sizeof(value));
+    return value;
   }
 
 public:
@@ -114,388 +167,414 @@
 
 BOOST_FIXTURE_TEST_SUITE(TestLpReliability, LpReliabilityFixture)
 
+BOOST_AUTO_TEST_SUITE(Sender)
+
 BOOST_AUTO_TEST_CASE(SendNoFragmentField)
 {
   lp::Packet pkt;
-  pkt.add<lp::AckField>(0);
 
   linkService->sendLpPackets({pkt});
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.size(), 0);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts.size(), 0);
-}
-
-BOOST_AUTO_TEST_CASE(SendNotFragmented)
-{
-  shared_ptr<Interest> interest = makeInterest("/abc/def");
-
-  lp::Packet pkt;
-  pkt.add<lp::SequenceField>(123);
-  pkt.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
-
-  linkService->sendLpPackets({pkt});
-
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.size(), 1);
-  lp::Packet cached;
-  BOOST_REQUIRE_NO_THROW(cached.wireDecode(transport->sentPackets.front().packet));
-  BOOST_REQUIRE(cached.has<lp::SequenceField>());
-  BOOST_CHECK_EQUAL(cached.get<lp::SequenceField>(), 123);
-  lp::Sequence seq = cached.get<lp::SequenceField>();
-  ndn::Buffer::const_iterator begin, end;
-  std::tie(begin, end) = cached.get<lp::FragmentField>();
-  Block block(&*begin, std::distance(begin, end));
-  Interest decodedInterest(block);
-  BOOST_CHECK_EQUAL(decodedInterest, *interest);
-
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(seq), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(seq).retxCount, 0);
-
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.size(), 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.count(seq), 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.at(seq).unackedFrags.size(), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts.at(seq).unackedFrags.count(seq), 1);
-
   BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
 }
 
-BOOST_AUTO_TEST_CASE(SendFragmented)
+BOOST_AUTO_TEST_CASE(SendUnfragmentedRetx)
 {
-  // Limit MTU
-  transport->setMtu(100);
-
-  Data data("/abc/def");
-
-  // Create a Data block containing 60 octets of content, which should fragment into 2 packets
-  uint8_t content[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
-
-  data.setContent(content, sizeof(content));
-  signData(data);
-
-  lp::Packet pkt;
-  pkt.add<lp::FragmentField>(make_pair(data.wireEncode().begin(), data.wireEncode().end()));
-
-  LpFragmenter fragmenter;
-  bool wasFragmentSuccessful;
-  std::vector<lp::Packet> frags;
-  std::tie(wasFragmentSuccessful, frags) = fragmenter.fragmentPacket(pkt, 100);
-  BOOST_REQUIRE(wasFragmentSuccessful);
-  BOOST_REQUIRE_EQUAL(frags.size(), 2);
-
-  frags.at(0).add<lp::SequenceField>(123);
-  frags.at(1).add<lp::SequenceField>(124);
-  linkService->sendLpPackets(std::move(frags));
-
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.size(), 2);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(123), 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(124), 1);
-  lp::Packet cached1;
-  BOOST_REQUIRE_NO_THROW(cached1.wireDecode(transport->sentPackets.front().packet));
-  BOOST_REQUIRE(cached1.has<lp::SequenceField>());
-  BOOST_CHECK_EQUAL(cached1.get<lp::SequenceField>(), 123);
-  lp::Packet cached2;
-  BOOST_REQUIRE_NO_THROW(cached2.wireDecode(transport->sentPackets.back().packet));
-  BOOST_REQUIRE(cached2.has<lp::SequenceField>());
-  BOOST_CHECK_EQUAL(cached2.get<lp::SequenceField>(), 124);
-  lp::Sequence firstSeq = cached1.get<lp::SequenceField>();
-
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstSeq).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstSeq + 1).retxCount, 0);
-
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.size(), 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.count(firstSeq), 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.at(firstSeq).unackedFrags.size(), 2);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts.at(firstSeq).unackedFrags.count(firstSeq), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts.at(firstSeq).unackedFrags.count(firstSeq + 1), 1);
-
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
-}
-
-BOOST_AUTO_TEST_CASE(ProcessIncomingPacket)
-{
-  BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
-
-  shared_ptr<Interest> interest = makeInterest("/abc/def");
-
-  lp::Packet pkt1;
-  pkt1.add<lp::SequenceField>(999888);
-  pkt1.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
-
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
-
-  reliability->processIncomingPacket(pkt1);
-
-  BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
-  BOOST_REQUIRE_EQUAL(reliability->m_ackQueue.size(), 1);
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 999888);
-
-  lp::Packet pkt2;
-  pkt2.add<lp::SequenceField>(111222);
-  pkt2.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
-
-  reliability->processIncomingPacket(pkt2);
-
-  BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
-  BOOST_REQUIRE_EQUAL(reliability->m_ackQueue.size(), 2);
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 999888);
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.back(), 111222);
-
-  // T+5ms
-  advanceClocks(time::milliseconds(1), 5);
-  BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
-}
-
-BOOST_AUTO_TEST_CASE(ProcessReceivedAcks)
-{
-  shared_ptr<Interest> interest = makeInterest("/abc/def");
-
-  lp::Packet pkt1;
-  pkt1.add<lp::SequenceField>(1024);
-  pkt1.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
-
-  lp::Packet pkt2;
-  pkt2.add<lp::SequenceField>(1025);
-  pkt2.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
-
-  linkService->sendLpPackets({pkt1, pkt2});
-
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1024), 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1025), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1024).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1025).retxCount, 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.count(1024), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts[1024].unackedFrags.size(), 2);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts[1024].unackedFrags.count(1024), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts[1024].unackedFrags.count(1025), 1);
-
-  advanceClocks(time::milliseconds(1), 500);
-
-  lp::Packet ackPkt1;
-  ackPkt1.add<lp::AckField>(101010); // Unknown sequence number - ignored
-  ackPkt1.add<lp::AckField>(1025);
-
-  reliability->processIncomingPacket(ackPkt1);
-
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1024), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(1025), 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1024).retxCount, 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.count(1024), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts[1024].unackedFrags.size(), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts[1024].unackedFrags.count(1024), 1);
-
-  lp::Packet ackPkt2;
-  ackPkt2.add<lp::AckField>(1024);
-
-  reliability->processIncomingPacket(ackPkt2);
-
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.size(), 0);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts.size(), 0);
-}
-
-BOOST_AUTO_TEST_CASE(RetxUnackedSequence)
-{
-  shared_ptr<Interest> interest = makeInterest("/abc/def");
-
-  lp::Packet pkt1;
-  pkt1.add<lp::SequenceField>(1024);
-  pkt1.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
-
-  lp::Packet pkt2;
-  pkt2.add<lp::SequenceField>(1025);
-  pkt2.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
+  lp::Packet pkt1 = makeFrag(1024, 50);
+  lp::Packet pkt2 = makeFrag(3000, 30);
 
   linkService->sendLpPackets({pkt1});
+  lp::Packet cached1(transport->sentPackets.front().packet);
+  BOOST_REQUIRE(cached1.has<lp::TxSequenceField>());
+  BOOST_CHECK_EQUAL(cached1.get<lp::TxSequenceField>(), 2);
+  BOOST_CHECK(!cached1.has<lp::SequenceField>());
+  lp::Sequence firstTxSeq = cached1.get<lp::TxSequenceField>();
+  BOOST_CHECK_EQUAL(getPktNo(cached1), 1024);
+
   // T+500ms
-  // 1024 rto: 1000ms, started T+0ms, retx 0
+  // 1024 rto: 1000ms, txSeq: 2, started T+0ms, retx 0
   advanceClocks(time::milliseconds(1), 500);
   linkService->sendLpPackets({pkt2});
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 2);
 
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1024), 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1025), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1024).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1025).retxCount, 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.count(1024), 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_netPkts.count(1025), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts[1024].unackedFrags.size(), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts[1024].unackedFrags.count(1024), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts[1025].unackedFrags.size(), 1);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts[1025].unackedFrags.count(1025), 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.size(), 2);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(firstTxSeq), 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 1), 1);
+  BOOST_CHECK(reliability->m_unackedFrags.at(firstTxSeq).netPkt);
+  BOOST_CHECK(reliability->m_unackedFrags.at(firstTxSeq + 1).netPkt);
+  BOOST_CHECK_NE(reliability->m_unackedFrags.at(firstTxSeq).netPkt,
+                    reliability->m_unackedFrags.at(firstTxSeq + 1).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstTxSeq).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstTxSeq + 1).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, firstTxSeq);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
 
   // T+1250ms
-  // 1024 rto: 1000ms, started T+1000ms, retx 1
-  // 1025 rto: 1000ms, started T+500ms, retx 0
+  // 1024 rto: 1000ms, txSeq: 4, started T+1000ms, retx 1
+  // 3000 rto: 1000ms, txSeq: 3, started T+500ms, retx 0
   advanceClocks(time::milliseconds(1), 750);
 
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1024), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1024).retxCount, 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1025), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1025).retxCount, 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.size(), 2);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(firstTxSeq), 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 2), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstTxSeq + 2).retxCount, 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 1), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstTxSeq + 1).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, firstTxSeq + 1);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 3);
 
   // T+2250ms
-  // 1024 rto: 1000ms, started T+2000ms, retx 2
-  // 1025 rto: 1000ms, started T+1500ms, retx 1
+  // 1024 rto: 1000ms, txSeq: 6, started T+2000ms, retx 2
+  // 3000 rto: 1000ms, txSeq: 5, started T+1500ms, retx 1
   advanceClocks(time::milliseconds(1), 1000);
 
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1024), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1024).retxCount, 2);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1025), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1025).retxCount, 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.size(), 2);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 1), 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 2), 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 4), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstTxSeq + 4).retxCount, 2);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 3), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstTxSeq + 3).retxCount, 1);
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, firstTxSeq + 3);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 5);
 
   // T+3250ms
-  // 1024 rto: 1000ms, started T+3000ms, retx 3
-  // 1025 rto: 1000ms, started T+2500ms, retx 2
+  // 1024 rto: 1000ms, txSeq: 8, started T+3000ms, retx 3
+  // 3000 rto: 1000ms, txSeq: 7, started T+2500ms, retx 2
   advanceClocks(time::milliseconds(1), 1000);
 
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1024), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1024).retxCount, 3);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1025), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1025).retxCount, 2);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.size(), 2);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 3), 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 4), 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 6), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstTxSeq + 6).retxCount, 3);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 5), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstTxSeq + 5).retxCount, 2);
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, firstTxSeq + 5);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 7);
 
   // T+4250ms
   // 1024 rto: expired, removed
-  // 1025 rto: 1000ms, started T+3500ms, retx 3
+  // 3000 rto: 1000ms, txSeq: 9, started T+3500ms, retx 3
   advanceClocks(time::milliseconds(1), 1000);
 
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(1024), 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(1025), 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.size(), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 5), 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 6), 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(firstTxSeq + 7), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(firstTxSeq + 7).retxCount, 3);
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, firstTxSeq + 7);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 8);
 
   // T+4750ms
   // 1024 rto: expired, removed
-  // 1025 rto: expired, removed
+  // 3000 rto: expired, removed
   advanceClocks(time::milliseconds(1), 1000);
 
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.size(), 0);
-  BOOST_CHECK_EQUAL(reliability->m_netPkts.size(), 0);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 8);
 }
 
-BOOST_AUTO_TEST_CASE(LostPacketsWraparound)
+BOOST_AUTO_TEST_CASE(SendFragmentedRetx)
 {
-  shared_ptr<Interest> interest = makeInterest("/abc/def");
+  lp::Packet pkt1 = makeFrag(2048, 30);
+  lp::Packet pkt2 = makeFrag(2049, 30);
+  lp::Packet pkt3 = makeFrag(2050, 10);
 
-  lp::Packet pkt1;
-  pkt1.add<lp::SequenceField>(0xFFFFFFFFFFFFFFFF);
-  pkt1.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
+  linkService->sendLpPackets({pkt1, pkt2, pkt3});
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 3);
 
-  lp::Packet pkt2;
-  pkt2.add<lp::SequenceField>(4);
-  pkt2.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
+  lp::Packet cached1(transport->sentPackets.at(0).packet);
+  BOOST_REQUIRE(cached1.has<lp::TxSequenceField>());
+  BOOST_CHECK_EQUAL(cached1.get<lp::TxSequenceField>(), 2);
+  BOOST_CHECK_EQUAL(getPktNo(cached1), 2048);
+  lp::Packet cached2(transport->sentPackets.at(1).packet);
+  BOOST_REQUIRE(cached2.has<lp::TxSequenceField>());
+  BOOST_CHECK_EQUAL(cached2.get<lp::TxSequenceField>(), 3);
+  BOOST_CHECK_EQUAL(getPktNo(cached2), 2049);
+  lp::Packet cached3(transport->sentPackets.at(2).packet);
+  BOOST_REQUIRE(cached3.has<lp::TxSequenceField>());
+  BOOST_CHECK_EQUAL(cached3.get<lp::TxSequenceField>(), 4);
+  BOOST_CHECK_EQUAL(getPktNo(cached3), 2050);
 
-  lp::Packet pkt3;
-  pkt3.add<lp::SequenceField>(5);
-  pkt3.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
+  // T+0ms
+  // 2048 rto: 1000ms, txSeq: 2, started T+0ms, retx 0
+  // 2049 rto: 1000ms, txSeq: 3, started T+0ms, retx 0
+  // 2050 rto: 1000ms, txSeq: 4, started T+0ms, retx 0
 
-  lp::Packet pkt4;
-  pkt4.add<lp::SequenceField>(7);
-  pkt4.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(2), 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(3), 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(4), 1);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(2).pkt), 2048);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(3).pkt), 2049);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(4).pkt), 2050);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).retxCount, 0);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(2).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(3).retxCount, 0);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(3).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(4).retxCount, 0);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(4).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt, reliability->m_unackedFrags.at(3).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt, reliability->m_unackedFrags.at(4).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt->unackedFrags.size(), 3);
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 2));
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 3));
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 4));
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 2);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 3);
 
-  lp::Packet pkt5;
-  pkt5.add<lp::SequenceField>(8);
-  pkt5.add<lp::FragmentField>(make_pair(interest->wireEncode().begin(), interest->wireEncode().end()));
+  // T+250ms
+  // 2048 rto: 1000ms, txSeq: 2, started T+0ms, retx 0
+  // 2049 rto: 1000ms, txSeq: 5, started T+250ms, retx 1
+  // 2050 rto: 1000ms, txSeq: 4, started T+0ms, retx 0
+  advanceClocks(time::milliseconds(1), 250);
+  reliability->onLpPacketLost(getIteratorFromTxSeq(3));
 
-  // Passed to sendLpPackets individually since they are from separate (encoded) network packets
-  linkService->sendLpPackets({pkt1});
-  linkService->sendLpPackets({pkt2});
-  linkService->sendLpPackets({pkt3});
-  linkService->sendLpPackets({pkt4});
-  linkService->sendLpPackets({pkt5});
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(2), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(3), 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(5), 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(4), 1);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(2).pkt), 2048);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(5).pkt), 2049);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(4).pkt), 2050);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).retxCount, 0);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(2).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(5).retxCount, 1);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(5).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(4).retxCount, 0);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(4).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt, reliability->m_unackedFrags.at(5).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt, reliability->m_unackedFrags.at(4).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt->unackedFrags.size(), 3);
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 2));
+  BOOST_CHECK(!netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 3));
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 5));
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 4));
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 2);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 4);
+
+  // T+500ms
+  // 2048 rto: 1000ms, txSeq: 2, started T+0ms, retx 0
+  // 2049 rto: 1000ms, txSeq: 6, started T+500ms, retx 2
+  // 2050 rto: 1000ms, txSeq: 4, started T+0ms, retx 0
+  advanceClocks(time::milliseconds(1), 250);
+  reliability->onLpPacketLost(getIteratorFromTxSeq(5));
+
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(2), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(5), 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(6), 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(4), 1);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(2).pkt), 2048);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(6).pkt), 2049);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(4).pkt), 2050);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).retxCount, 0);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(2).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(6).retxCount, 2);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(6).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(4).retxCount, 0);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(4).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt, reliability->m_unackedFrags.at(6).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt, reliability->m_unackedFrags.at(4).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt->unackedFrags.size(), 3);
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 2));
+  BOOST_CHECK(!netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 5));
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 6));
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 4));
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 2);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 5);
+
+  // T+750ms
+  // 2048 rto: 1000ms, txSeq: 2, started T+0ms, retx 0
+  // 2049 rto: 1000ms, txSeq: 7, started T+750ms, retx 3
+  // 2050 rto: 1000ms, txSeq: 4, started T+0ms, retx 0
+  advanceClocks(time::milliseconds(1), 250);
+  reliability->onLpPacketLost(getIteratorFromTxSeq(6));
+
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(2), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(6), 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(7), 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(4), 1);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(2).pkt), 2048);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(7).pkt), 2049);
+  BOOST_CHECK_EQUAL(getPktNo(reliability->m_unackedFrags.at(4).pkt), 2050);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).retxCount, 0);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(2).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(7).retxCount, 3);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(7).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(4).retxCount, 0);
+  BOOST_REQUIRE(reliability->m_unackedFrags.at(4).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt, reliability->m_unackedFrags.at(7).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt, reliability->m_unackedFrags.at(4).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).netPkt->unackedFrags.size(), 3);
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 2));
+  BOOST_CHECK(!netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 6));
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 7));
+  BOOST_CHECK(netPktHasUnackedFrag(reliability->m_unackedFrags.at(2).netPkt, 4));
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 2);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 6);
+
+  // T+850ms
+  // 2048 rto: expired, removed
+  // 2049 rto: expired, removed
+  // 2050 rto: expired, removed
+  advanceClocks(time::milliseconds(1), 100);
+  reliability->onLpPacketLost(getIteratorFromTxSeq(7));
+
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.size(), 0);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(LossByGreaterAcks) // detect loss by 3x greater Acks, also tests wraparound
+{
+  reliability->m_lastTxSeqNo = 0xFFFFFFFFFFFFFFFE;
+
+  // Passed to sendLpPackets individually since they are from separate, non-fragmented network packets
+  linkService->sendLpPackets({makeFrag(1, 50)});
+  linkService->sendLpPackets({makeFrag(2, 50)});
+  linkService->sendLpPackets({makeFrag(3, 50)});
+  linkService->sendLpPackets({makeFrag(4, 50)});
+  linkService->sendLpPackets({makeFrag(5, 50)});
 
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.size(), 5);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(4), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(5), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(7), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(8), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 1); // pkt1
+  BOOST_CHECK(reliability->m_unackedFrags.at(0xFFFFFFFFFFFFFFFF).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0), 1); // pkt2
+  BOOST_CHECK(reliability->m_unackedFrags.at(0).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(1), 1); // pkt3
+  BOOST_CHECK(reliability->m_unackedFrags.at(1).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(2), 1); // pkt4
+  BOOST_CHECK(reliability->m_unackedFrags.at(2).netPkt);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(3), 1); // pkt5
+  BOOST_CHECK(reliability->m_unackedFrags.at(3).netPkt);
   BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 0xFFFFFFFFFFFFFFFF);
 
   lp::Packet ackPkt1;
-  ackPkt1.add<lp::AckField>(4);
+  ackPkt1.add<lp::AckField>(0);
 
   BOOST_CHECK_EQUAL(transport->sentPackets.size(), 5);
 
   reliability->processIncomingPacket(ackPkt1);
 
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.size(), 4);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 1); // pkt1
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(0xFFFFFFFFFFFFFFFF).retxCount, 0);
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(0xFFFFFFFFFFFFFFFF).nGreaterSeqAcks, 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(4), 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(5), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(5).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(5).nGreaterSeqAcks, 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(7), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(7).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(7).nGreaterSeqAcks, 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(8), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(8).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(8).nGreaterSeqAcks, 0);
-  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 5);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0), 0); // pkt2
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1), 1); // pkt3
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1).nGreaterSeqAcks, 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(2), 1); // pkt4
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(2).nGreaterSeqAcks, 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(3), 1); // pkt5
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(3).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(3).nGreaterSeqAcks, 0);
   BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 0xFFFFFFFFFFFFFFFF);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 5);
 
   lp::Packet ackPkt2;
-  ackPkt2.add<lp::AckField>(7);
+  ackPkt2.add<lp::AckField>(2);
+  ackPkt1.add<lp::AckField>(101010); // Unknown TxSequence number - ignored
 
   BOOST_CHECK_EQUAL(transport->sentPackets.size(), 5);
 
   reliability->processIncomingPacket(ackPkt2);
 
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.size(), 3);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 1); // pkt1
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(0xFFFFFFFFFFFFFFFF).retxCount, 0);
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(0xFFFFFFFFFFFFFFFF).nGreaterSeqAcks, 2);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(4), 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(5), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(5).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(5).nGreaterSeqAcks, 1);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(7), 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(8), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(8).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(8).nGreaterSeqAcks, 0);
-  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 5);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0), 0); // pkt2
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(1), 1); // pkt3
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(1).nGreaterSeqAcks, 1);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(2), 0); // pkt4
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(3), 1); // pkt5
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(3).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(3).nGreaterSeqAcks, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(101010), 0);
   BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 0xFFFFFFFFFFFFFFFF);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 5);
 
   lp::Packet ackPkt3;
-  ackPkt3.add<lp::AckField>(5);
+  ackPkt3.add<lp::AckField>(1);
 
   BOOST_CHECK_EQUAL(transport->sentPackets.size(), 5);
 
   reliability->processIncomingPacket(ackPkt3);
 
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.size(), 2);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(0xFFFFFFFFFFFFFFFF).retxCount, 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(0xFFFFFFFFFFFFFFFF).nGreaterSeqAcks, 3);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(4), 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(5), 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(7), 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(8), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(8).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(8).nGreaterSeqAcks, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 0); // pkt1 old TxSeq
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0), 0); // pkt2
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(1), 0); // pkt3
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(2), 0); // pkt4
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(3), 1); // pkt5
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(3).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(3).nGreaterSeqAcks, 0);
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(4), 1); // pkt1 new TxSeq
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(4).retxCount, 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(4).nGreaterSeqAcks, 0);
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 3);
   BOOST_CHECK_EQUAL(transport->sentPackets.size(), 6);
-  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 0xFFFFFFFFFFFFFFFF);
   lp::Packet sentRetxPkt(transport->sentPackets.back().packet);
-  BOOST_REQUIRE(sentRetxPkt.has<lp::SequenceField>());
-  BOOST_CHECK_EQUAL(sentRetxPkt.get<lp::SequenceField>(), 0xFFFFFFFFFFFFFFFF);
+  BOOST_REQUIRE(sentRetxPkt.has<lp::TxSequenceField>());
+  BOOST_CHECK_EQUAL(sentRetxPkt.get<lp::TxSequenceField>(), 4);
+  BOOST_REQUIRE(sentRetxPkt.has<lp::FragmentField>());
+  BOOST_CHECK_EQUAL(getPktNo(sentRetxPkt), 1);
 
   lp::Packet ackPkt4;
-  ackPkt4.add<lp::AckField>(0xFFFFFFFFFFFFFFFF);
+  ackPkt4.add<lp::AckField>(4);
 
   BOOST_CHECK_EQUAL(transport->sentPackets.size(), 6);
 
   reliability->processIncomingPacket(ackPkt4);
 
   BOOST_CHECK_EQUAL(reliability->m_unackedFrags.size(), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(4), 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(5), 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(7), 0);
-  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(8), 1);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(8).retxCount, 0);
-  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(8).nGreaterSeqAcks, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0xFFFFFFFFFFFFFFFF), 0); // pkt1 old TxSeq
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(0), 0); // pkt2
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(1), 0); // pkt3
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(2), 0); // pkt4
+  BOOST_REQUIRE_EQUAL(reliability->m_unackedFrags.count(3), 1); // pkt5
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(3).retxCount, 0);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.at(3).nGreaterSeqAcks, 1);
+  BOOST_CHECK_EQUAL(reliability->m_unackedFrags.count(4), 0); // pkt1 new TxSeq
+  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 3);
   BOOST_CHECK_EQUAL(transport->sentPackets.size(), 6);
-  BOOST_CHECK_EQUAL(reliability->m_firstUnackedFrag->first, 8);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Sender
+
+BOOST_AUTO_TEST_SUITE(Receiver)
+
+BOOST_AUTO_TEST_CASE(ProcessIncomingPacket)
+{
+  BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
+
+  lp::Packet pkt1 = makeFrag(100, 40);
+  pkt1.add<lp::TxSequenceField>(765432);
+
+  reliability->processIncomingPacket(pkt1);
+
+  BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
+  BOOST_REQUIRE_EQUAL(reliability->m_ackQueue.size(), 1);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 765432);
+
+  lp::Packet pkt2 = makeFrag(276, 40);
+  pkt2.add<lp::TxSequenceField>(234567);
+
+  reliability->processIncomingPacket(pkt2);
+
+  BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
+  BOOST_REQUIRE_EQUAL(reliability->m_ackQueue.size(), 2);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 765432);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.back(), 234567);
+
+  // T+5ms
+  advanceClocks(time::milliseconds(1), 5);
+  BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
 }
 
 BOOST_AUTO_TEST_CASE(PiggybackAcks)
@@ -505,7 +584,6 @@
   reliability->m_ackQueue.push(10);
 
   lp::Packet pkt;
-  pkt.add<lp::SequenceField>(123456);
   linkService->sendLpPackets({pkt});
 
   BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
@@ -515,6 +593,7 @@
   BOOST_CHECK_EQUAL(sentPkt.get<lp::AckField>(0), 256);
   BOOST_CHECK_EQUAL(sentPkt.get<lp::AckField>(1), 257);
   BOOST_CHECK_EQUAL(sentPkt.get<lp::AckField>(2), 10);
+  BOOST_CHECK(!sentPkt.has<lp::TxSequenceField>());
 
   BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
 }
@@ -523,81 +602,85 @@
 {
   // This test case tests for piggybacking Acks when there is an MTU on the link.
 
-  reliability->m_ackQueue.push(1010);
-  reliability->m_ackQueue.push(1011);
-  reliability->m_ackQueue.push(1013);
-  reliability->m_ackQueue.push(1014);
+  transport->setMtu(1500);
 
-  Data data("/abc/def");
+  for (lp::Sequence i = 1000; i < 2000; i++) {
+    reliability->m_ackQueue.push(i);
+  }
 
-  // Create a Data block containing 60 octets of content, which should fragment into 2 packets
-  uint8_t content[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
+  BOOST_CHECK(!reliability->m_ackQueue.empty());
 
-  data.setContent(content, sizeof(content));
-  signData(data);
+  for (int i = 0; i < 5; i++) {
+    lp::Packet pkt = makeFrag(i, 60);
+    linkService->sendLpPackets({pkt});
 
-  lp::Packet pkt1;
-  pkt1.add<lp::SequenceField>(123);
-  pkt1.add<lp::FragmentField>(make_pair(data.wireEncode().begin(), data.wireEncode().end()));
+    BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), i + 1);
+    lp::Packet sentPkt(transport->sentPackets.back().packet);
+    BOOST_CHECK_EQUAL(getPktNo(sentPkt), i);
+    BOOST_CHECK(sentPkt.has<lp::AckField>());
+  }
 
-  // Allow 2 Acks per packet, plus a little bit of extra space
-  // sizeof(lp::Sequence) + Ack Type (3 octets) + Ack Length (1 octet)
-  transport->setMtu(pkt1.wireEncode().size() + 2 * (sizeof(lp::Sequence) + 3 + 1) + 3);
+  BOOST_CHECK(reliability->m_ackQueue.empty());
+}
 
-  linkService->sendLpPackets({pkt1});
+BOOST_AUTO_TEST_CASE(PiggybackAcksMtuNoSpace)
+{
+  // This test case tests for piggybacking Acks when there is an MTU on the link, resulting in no
+  // space for Acks (and negative remainingSpace in the piggyback function).
+
+  transport->setMtu(250);
+
+  for (lp::Sequence i = 1000; i < 1100; i++) {
+    reliability->m_ackQueue.push(i);
+  }
+
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 100);
+
+  lp::Packet pkt = makeFrag(1, 240);
+  linkService->sendLpPackets({pkt});
+
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 100);
 
   BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
-  lp::Packet sentPkt1(transport->sentPackets.front().packet);
+  lp::Packet sentPkt(transport->sentPackets.back().packet);
+  BOOST_CHECK_EQUAL(getPktNo(sentPkt), 1);
+  BOOST_CHECK(!sentPkt.has<lp::AckField>());
+}
 
-  BOOST_REQUIRE_EQUAL(sentPkt1.count<lp::AckField>(), 2);
-  BOOST_CHECK_EQUAL(sentPkt1.get<lp::AckField>(0), 1010);
-  BOOST_CHECK_EQUAL(sentPkt1.get<lp::AckField>(1), 1011);
+BOOST_AUTO_TEST_CASE(StartIdleAckTimer)
+{
+  BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
 
-  BOOST_REQUIRE_EQUAL(reliability->m_ackQueue.size(), 2);
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 1013);
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.back(), 1014);
+  lp::Packet pkt1 = makeFrag(1, 100);
+  pkt1.add<lp::TxSequenceField>(12);
+  reliability->processIncomingPacket({pkt1});
+  BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
 
-  lp::Packet pkt2;
-  pkt2.add<lp::SequenceField>(105623);
-  pkt2.add<lp::FragmentField>(make_pair(data.wireEncode().begin(), data.wireEncode().end()));
+  // T+1ms
+  advanceClocks(time::milliseconds(1), 1);
+  BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
 
-  // Allow 1 Acks per packet, plus a little bit of extra space (1 Ack - 1 octet)
-  // sizeof(lp::Sequence) + Ack Type (3 octets) + Ack Length (1 octet)
-  transport->setMtu(pkt2.wireEncode().size() + 2 * (sizeof(lp::Sequence) + 3 + 1) - 1);
+  lp::Packet pkt2 = makeFrag(2, 100);
+  pkt2.add<lp::TxSequenceField>(13);
+  reliability->processIncomingPacket({pkt2});
+  BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
 
-  linkService->sendLpPackets({pkt2});
+  // T+5ms
+  advanceClocks(time::milliseconds(1), 4);
+  BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
 
-  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 2);
-  lp::Packet sentPkt2(transport->sentPackets.back().packet);
+  lp::Packet pkt3 = makeFrag(3, 100);
+  pkt3.add<lp::TxSequenceField>(15);
+  reliability->processIncomingPacket({pkt3});
+  BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
 
-  BOOST_REQUIRE_EQUAL(sentPkt2.count<lp::AckField>(), 1);
-  BOOST_CHECK_EQUAL(sentPkt2.get<lp::AckField>(), 1013);
+  // T+9ms
+  advanceClocks(time::milliseconds(1), 4);
+  BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
 
-  BOOST_REQUIRE_EQUAL(reliability->m_ackQueue.size(), 1);
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 1014);
-
-  lp::Packet pkt3;
-  pkt3.add<lp::SequenceField>(969456);
-  pkt3.add<lp::FragmentField>(make_pair(data.wireEncode().begin(), data.wireEncode().end()));
-
-  // Allow 3 Acks per packet
-  // sizeof(lp::Sequence) + Ack Type (3 octets) + Ack Length (1 octet)
-  transport->setMtu(pkt3.wireEncode().size() + 3 * (sizeof(lp::Sequence) + 3 + 1));
-
-  linkService->sendLpPackets({pkt3});
-
-  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 3);
-  lp::Packet sentPkt3(transport->sentPackets.back().packet);
-
-  BOOST_REQUIRE_EQUAL(sentPkt3.count<lp::AckField>(), 1);
-  BOOST_CHECK_EQUAL(sentPkt3.get<lp::AckField>(), 1014);
-
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
+  // T+10ms
+  advanceClocks(time::milliseconds(1), 1);
+  BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
 }
 
 BOOST_AUTO_TEST_CASE(IdleAckTimer)
@@ -605,9 +688,11 @@
   // T+1ms
   advanceClocks(time::milliseconds(1), 1);
 
-  reliability->m_ackQueue.push(5000);
-  reliability->m_ackQueue.push(5001);
-  reliability->m_ackQueue.push(5002);
+  for (lp::Sequence i = 1000; i < 2000; i++) {
+    reliability->m_ackQueue.push(i);
+  }
+
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
   BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
   reliability->startIdleAckTimer();
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
@@ -615,42 +700,51 @@
   // T+5ms
   advanceClocks(time::milliseconds(1), 4);
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 1000);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.back(), 1999);
   BOOST_CHECK_EQUAL(transport->sentPackets.size(), 0);
 
   // T+6ms
   advanceClocks(time::milliseconds(1), 1);
 
   BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
   BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
   lp::Packet sentPkt1(transport->sentPackets.back().packet);
 
-  BOOST_REQUIRE_EQUAL(sentPkt1.count<lp::AckField>(), 3);
-  BOOST_CHECK_EQUAL(sentPkt1.get<lp::AckField>(0), 5000);
-  BOOST_CHECK_EQUAL(sentPkt1.get<lp::AckField>(1), 5001);
-  BOOST_CHECK_EQUAL(sentPkt1.get<lp::AckField>(2), 5002);
+  BOOST_CHECK(!sentPkt1.has<lp::TxSequenceField>());
+  BOOST_CHECK_EQUAL(sentPkt1.count<lp::AckField>(), 1000);
 
-  reliability->m_ackQueue.push(5003);
-  reliability->m_ackQueue.push(5004);
+  for (lp::Sequence i = 10000; i < 11000; i++) {
+    reliability->m_ackQueue.push(i);
+  }
+
   reliability->startIdleAckTimer();
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 10000);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.back(), 10999);
 
   // T+10ms
   advanceClocks(time::milliseconds(1), 4);
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
   BOOST_CHECK_EQUAL(transport->sentPackets.size(), 1);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
 
   // T+11ms
   advanceClocks(time::milliseconds(1), 1);
 
   BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
   BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 2);
   lp::Packet sentPkt2(transport->sentPackets.back().packet);
 
-  BOOST_REQUIRE_EQUAL(sentPkt2.count<lp::AckField>(), 2);
-  BOOST_CHECK_EQUAL(sentPkt2.get<lp::AckField>(0), 5003);
-  BOOST_CHECK_EQUAL(sentPkt2.get<lp::AckField>(1), 5004);
+  BOOST_REQUIRE_EQUAL(sentPkt2.count<lp::AckField>(), 1000);
+  BOOST_CHECK(!sentPkt2.has<lp::TxSequenceField>());
 
   BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
+  reliability->startIdleAckTimer();
 
   // T+16ms
   advanceClocks(time::milliseconds(1), 5);
@@ -662,17 +756,16 @@
 
 BOOST_AUTO_TEST_CASE(IdleAckTimerMtu)
 {
-  // 1 (LpPacket Type) + 1 (LpPacket Length) + 2 Acks
-  transport->setMtu(lp::Packet().wireEncode().size() + 2 * (sizeof(lp::Sequence) + 3 + 1));
+  transport->setMtu(1500);
 
   // T+1ms
   advanceClocks(time::milliseconds(1), 1);
 
-  reliability->m_ackQueue.push(3000);
-  reliability->m_ackQueue.push(3001);
-  reliability->m_ackQueue.push(3002);
-  reliability->m_ackQueue.push(3003);
-  reliability->m_ackQueue.push(3004);
+  for (lp::Sequence i = 1000; i < 2000; i++) {
+    reliability->m_ackQueue.push(i);
+  }
+
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
   BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
   reliability->startIdleAckTimer();
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
@@ -680,6 +773,7 @@
   // T+5ms
   advanceClocks(time::milliseconds(1), 4);
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
   BOOST_CHECK_EQUAL(transport->sentPackets.size(), 0);
 
   // T+6ms
@@ -687,78 +781,97 @@
 
   BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
 
-  reliability->m_ackQueue.push(3005);
-  reliability->m_ackQueue.push(3006);
+  for (lp::Sequence i = 5000; i < 6000; i++) {
+    reliability->m_ackQueue.push(i);
+  }
+
   reliability->startIdleAckTimer();
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
 
-  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 3);
-  lp::Packet sentPkt1(transport->sentPackets[0].packet);
-  BOOST_REQUIRE_EQUAL(sentPkt1.count<lp::AckField>(), 2);
-  BOOST_CHECK_EQUAL(sentPkt1.get<lp::AckField>(0), 3000);
-  BOOST_CHECK_EQUAL(sentPkt1.get<lp::AckField>(1), 3001);
-  lp::Packet sentPkt2(transport->sentPackets[1].packet);
-  BOOST_REQUIRE_EQUAL(sentPkt2.count<lp::AckField>(), 2);
-  BOOST_CHECK_EQUAL(sentPkt2.get<lp::AckField>(0), 3002);
-  BOOST_CHECK_EQUAL(sentPkt2.get<lp::AckField>(1), 3003);
-  lp::Packet sentPkt3(transport->sentPackets[2].packet);
-  BOOST_REQUIRE_EQUAL(sentPkt3.count<lp::AckField>(), 1);
-  BOOST_CHECK_EQUAL(sentPkt3.get<lp::AckField>(), 3004);
+  // given Ack of size 6 and MTU of 1500, 249 Acks/IDLE packet
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 5);
+  for (int i = 0; i < 4; i++) {
+    lp::Packet sentPkt(transport->sentPackets[i].packet);
+    BOOST_CHECK(!sentPkt.has<lp::TxSequenceField>());
+    BOOST_CHECK_EQUAL(sentPkt.count<lp::AckField>(), 249);
+  }
+  lp::Packet sentPkt(transport->sentPackets[4].packet);
+  BOOST_CHECK(!sentPkt.has<lp::TxSequenceField>());
+  BOOST_CHECK_LE(sentPkt.count<lp::AckField>(), 249);
 
-  BOOST_REQUIRE_EQUAL(reliability->m_ackQueue.size(), 2);
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 3005);
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.back(), 3006);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 5000);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.back(), 5999);
 
   // T+10ms
   advanceClocks(time::milliseconds(1), 4);
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
-  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 3);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 5);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
 
   // T+11ms
   advanceClocks(time::milliseconds(1), 1);
 
   BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
 
-  reliability->m_ackQueue.push(3007);
+  for (lp::Sequence i = 100000; i < 101000; i++) {
+    reliability->m_ackQueue.push(i);
+  }
+
   reliability->startIdleAckTimer();
-
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
-  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 4);
-  lp::Packet sentPkt4(transport->sentPackets[3].packet);
-  BOOST_REQUIRE_EQUAL(sentPkt4.count<lp::AckField>(), 2);
-  BOOST_CHECK_EQUAL(sentPkt4.get<lp::AckField>(0), 3005);
-  BOOST_CHECK_EQUAL(sentPkt4.get<lp::AckField>(1), 3006);
 
-  BOOST_REQUIRE_EQUAL(reliability->m_ackQueue.size(), 1);
-  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 3007);
+  // given Ack of size 6 and MTU of 1500, 249 Acks/IDLE packet
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 10);
+  for (int i = 5; i < 9; i++) {
+    lp::Packet sentPkt(transport->sentPackets[i].packet);
+    BOOST_CHECK(!sentPkt.has<lp::TxSequenceField>());
+    BOOST_CHECK_EQUAL(sentPkt.count<lp::AckField>(), 249);
+  }
+  sentPkt.wireDecode(transport->sentPackets[9].packet);
+  BOOST_CHECK(!sentPkt.has<lp::TxSequenceField>());
+  BOOST_CHECK_LE(sentPkt.count<lp::AckField>(), 249);
+
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.front(), 100000);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.back(), 100999);
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
 
   // T+15ms
   advanceClocks(time::milliseconds(1), 4);
   BOOST_CHECK(reliability->m_isIdleAckTimerRunning);
-  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 4);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 10);
+  BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 1000);
 
   // T+16ms
   advanceClocks(time::milliseconds(1), 1);
 
+  // given Ack of size 8 and MTU of 1500, approx 187 Acks/IDLE packet
   BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
-  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 5);
-  lp::Packet sentPkt5(transport->sentPackets[4].packet);
-  BOOST_REQUIRE_EQUAL(sentPkt5.count<lp::AckField>(), 1);
-  BOOST_CHECK_EQUAL(sentPkt5.get<lp::AckField>(), 3007);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 16);
+  for (int i = 10; i < 15; i++) {
+    lp::Packet sentPkt(transport->sentPackets[i].packet);
+    BOOST_CHECK(!sentPkt.has<lp::TxSequenceField>());
+    BOOST_CHECK_EQUAL(sentPkt.count<lp::AckField>(), 187);
+  }
+  sentPkt.wireDecode(transport->sentPackets[15].packet);
+  BOOST_CHECK(!sentPkt.has<lp::TxSequenceField>());
+  BOOST_CHECK_LE(sentPkt.count<lp::AckField>(), 187);
 
   BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
+  reliability->startIdleAckTimer();
 
   // T+21ms
   advanceClocks(time::milliseconds(1), 5);
 
   BOOST_CHECK(!reliability->m_isIdleAckTimerRunning);
-  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 5);
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 16);
   BOOST_CHECK_EQUAL(reliability->m_ackQueue.size(), 0);
 }
 
-BOOST_AUTO_TEST_SUITE_END() // TestLpReliability
+BOOST_AUTO_TEST_SUITE_END() // Receiver
 
+BOOST_AUTO_TEST_SUITE_END() // TestLpReliability
 BOOST_AUTO_TEST_SUITE_END() // Face
 
 } // namespace tests