From 18eedbb2662a4749ef5fa5ce59e2f5fd50e68a0d Mon Sep 17 00:00:00 2001 From: Natale Patriciello Date: Fri, 3 Feb 2017 14:01:36 +0100 Subject: [PATCH] tcp: TcpTxBuffer stores a list of TcpTxItem --- src/internet/model/tcp-tx-buffer.cc | 575 +++++++++++++++++++++++----- src/internet/model/tcp-tx-buffer.h | 264 +++++++++++-- 2 files changed, 721 insertions(+), 118 deletions(-) diff --git a/src/internet/model/tcp-tx-buffer.cc b/src/internet/model/tcp-tx-buffer.cc index 38ee485d0..f884ac0d0 100644 --- a/src/internet/model/tcp-tx-buffer.cc +++ b/src/internet/model/tcp-tx-buffer.cc @@ -1,6 +1,7 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2010 Adrian Sai-wah Tam + * Copyright (c) 2010-2015 Adrian Sai-wah Tam + * Copyright (c) 2016 Natale Patriciello * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -15,16 +16,16 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * Author: Adrian Sai-wah Tam + * Original author: Adrian Sai-wah Tam */ -#include #include -#include +#include #include "ns3/packet.h" -#include "ns3/fatal-error.h" #include "ns3/log.h" +#include "ns3/abort.h" +#include "ns3/tcp-option-ts.h" #include "tcp-tx-buffer.h" @@ -32,6 +33,62 @@ namespace ns3 { NS_LOG_COMPONENT_DEFINE ("TcpTxBuffer"); +TcpTxItem::TcpTxItem () + : m_packet (0), + m_lost (false), + m_retrans (false), + m_lastSent (Time::Min ()), + m_sacked (false) +{ +} + +TcpTxItem::TcpTxItem (const TcpTxItem &other) + : m_packet (other.m_packet), + m_lost (other.m_lost), + m_retrans (other.m_retrans), + m_lastSent (other.m_lastSent), + m_sacked (other.m_sacked) +{ +} + +void +TcpTxItem::Print (std::ostream &os) const +{ + NS_LOG_FUNCTION (this); + bool comma = false; + os << "pkt pointer: " << m_packet; + + if (m_lost) + { + os << "[lost]"; + comma = true; + } + if (m_retrans) + { + if (comma) + { + os << ","; + } + + os << "[retrans]"; + comma = true; + } + if (m_sacked) + { + if (comma) + { + os << ","; + } + os << "[sacked]"; + comma = true; + } + if (comma) + { + os << ","; + } + os << "last sent: " << m_lastSent; +} + NS_OBJECT_ENSURE_REGISTERED (TcpTxBuffer); TypeId @@ -57,12 +114,27 @@ TcpTxBuffer::GetTypeId (void) * initialized below is insignificant. */ TcpTxBuffer::TcpTxBuffer (uint32_t n) - : m_firstByteSeq (n), m_size (0), m_maxBuffer (32768), m_data (0) + : m_maxBuffer (32768), m_size (0), m_sentSize (0), m_firstByteSeq (n) { } TcpTxBuffer::~TcpTxBuffer (void) { + PacketList::iterator it; + + for (it = m_sentList.begin (); it != m_sentList.end (); ++it) + { + TcpTxItem *item = *it; + m_sentSize -= item->m_packet->GetSize (); + delete item; + } + + for (it = m_appList.begin (); it != m_appList.end (); ++it) + { + TcpTxItem *item = *it; + m_size -= item->m_packet->GetSize (); + delete item; + } } SequenceNumber32 @@ -101,23 +173,34 @@ TcpTxBuffer::Available (void) const return m_maxBuffer - m_size; } +void +TcpTxBuffer::SetHeadSequence (const SequenceNumber32& seq) +{ + NS_LOG_FUNCTION (this << seq); + m_firstByteSeq = seq; +} + bool TcpTxBuffer::Add (Ptr p) { NS_LOG_FUNCTION (this << p); - NS_LOG_LOGIC ("Packet of size " << p->GetSize () << " appending to window starting at " - << m_firstByteSeq << ", availSize="<< Available ()); + NS_LOG_INFO ("Try to append " << p->GetSize () << " bytes to window starting at " + << m_firstByteSeq << ", availSize=" << Available ()); if (p->GetSize () <= Available ()) { if (p->GetSize () > 0) { - m_data.push_back (p); + TcpTxItem *item = new TcpTxItem (); + item->m_packet = p; + m_appList.insert (m_appList.end (), item); m_size += p->GetSize (); - NS_LOG_LOGIC ("Updated size=" << m_size << ", lastSeq=" << m_firstByteSeq + SequenceNumber32 (m_size)); + + NS_LOG_INFO ("Updated size=" << m_size << ", lastSeq=" << + m_firstByteSeq + SequenceNumber32 (m_size)); } return true; } - NS_LOG_LOGIC ("Rejected. Not enough room to buffer packet."); + NS_LOG_WARN ("Rejected. Not enough room to buffer packet."); return false; } @@ -126,120 +209,378 @@ TcpTxBuffer::SizeFromSequence (const SequenceNumber32& seq) const { NS_LOG_FUNCTION (this << seq); // Sequence of last byte in buffer - SequenceNumber32 lastSeq = m_firstByteSeq + SequenceNumber32 (m_size); - // Non-negative size - NS_LOG_LOGIC ("HeadSeq=" << m_firstByteSeq << ", lastSeq=" << lastSeq << ", size=" << m_size << - ", returns " << lastSeq - seq); - return lastSeq - seq; + SequenceNumber32 lastSeq = TailSequence (); + + if (lastSeq >= seq) + { + return lastSeq - seq; + } + + NS_LOG_ERROR ("Requested a sequence beyond our space (" << seq << " > " << lastSeq << + "). Returning 0 for convenience."); + return 0; } Ptr TcpTxBuffer::CopyFromSequence (uint32_t numBytes, const SequenceNumber32& seq) { - NS_LOG_FUNCTION (this << numBytes << seq); - uint32_t s = std::min (numBytes, SizeFromSequence (seq)); // Real size to extract. Insure not beyond end of data - if (s == 0) + NS_LOG_FUNCTION (*this << numBytes << seq); + + if (m_firstByteSeq > seq) { - return Create (); // Empty packet returned - } - if (m_data.size () == 0) - { // No actual data, just return dummy-data packet of correct size - return Create (s); + NS_LOG_ERROR ("Requested a sequence number which is not in the buffer anymore"); + return Create (); } - // Extract data from the buffer and return - uint32_t offset = seq - m_firstByteSeq.Get (); - uint32_t count = 0; // Offset of the first byte of a packet in the buffer - uint32_t pktSize = 0; - bool beginFound = false; - int pktCount = 0; - Ptr outPacket; - NS_LOG_LOGIC ("There are " << m_data.size () << " number of packets in buffer"); - for (BufIterator i = m_data.begin (); i != m_data.end (); ++i) + // Real size to extract. Insure not beyond end of data + uint32_t s = std::min (numBytes, SizeFromSequence (seq)); + + if (s == 0) { - pktCount++; - pktSize = (*i)->GetSize (); - if (!beginFound) - { // Look for first fragment - if (count + pktSize > offset) - { - NS_LOG_LOGIC ("First byte found in packet #" << pktCount << " at buffer offset " << count - << ", packet len=" << pktSize); - beginFound = true; - uint32_t packetOffset = offset - count; - uint32_t fragmentLength = count + pktSize - offset; - if (fragmentLength >= s) - { // Data to be copied falls entirely in this packet - return (*i)->CreateFragment (packetOffset, s); - } - else - { // This packet only fulfills part of the request - outPacket = (*i)->CreateFragment (packetOffset, fragmentLength); - } - NS_LOG_LOGIC ("Output packet is now of size " << outPacket->GetSize ()); - } - } - else if (count + pktSize >= offset + s) - { // Last packet fragment found - NS_LOG_LOGIC ("Last byte found in packet #" << pktCount << " at buffer offset " << count - << ", packet len=" << pktSize); - uint32_t fragmentLength = offset + s - count; - Ptr endFragment = (*i)->CreateFragment (0, fragmentLength); - outPacket->AddAtEnd (endFragment); - NS_LOG_LOGIC ("Output packet is now of size " << outPacket->GetSize ()); - break; - } - else - { - NS_LOG_LOGIC ("Appending to output the packet #" << pktCount << " of offset " << count << " len=" << pktSize); - outPacket->AddAtEnd (*i); - NS_LOG_LOGIC ("Output packet is now of size " << outPacket->GetSize ()); - } - count += pktSize; + return Create (); } - NS_ASSERT (outPacket->GetSize () == s); - return outPacket; + + TcpTxItem *outItem = 0; + + if (m_firstByteSeq + m_sentSize >= seq + s) + { + // already sent this block completely + outItem = GetTransmittedSegment (s, seq); + NS_ASSERT (outItem != 0); + outItem->m_retrans = true; + + NS_LOG_DEBUG ("Retransmitting [" << seq << ";" << seq + s << "|" << s << + "] from " << *this); + } + else if (m_firstByteSeq + m_sentSize <= seq) + { + NS_ABORT_MSG_UNLESS (m_firstByteSeq + m_sentSize == seq, + "Requesting a piece of new data with an hole"); + + // this is the first time we transmit this block + outItem = GetNewSegment (s); + NS_ASSERT (outItem != 0); + NS_ASSERT (outItem->m_retrans == false); + + NS_LOG_DEBUG ("New segment [" << seq << ";" << seq + s << "|" << s << + "] from " << *this); + } + else if (m_firstByteSeq + m_sentSize > seq && m_firstByteSeq + m_sentSize < seq + s) + { + // Partial: a part is retransmission, the remaining data is new + + // Take the new data and move it into sent list + uint32_t amount = seq + s - m_firstByteSeq.Get () - m_sentSize; + NS_LOG_DEBUG ("Moving segment [" << m_firstByteSeq + m_sentSize << ";" << + m_firstByteSeq + m_sentSize + amount <<"|" << amount << + "] from " << *this); + + outItem = GetNewSegment (amount); + NS_ASSERT (outItem != 0); + + // Now get outItem from the sent list (there will be a merge) + return CopyFromSequence (numBytes, seq); + } + + outItem->m_lost = false; + outItem->m_lastSent = Simulator::Now (); + Ptr toRet = outItem->m_packet->Copy (); + + NS_ASSERT (toRet->GetSize () == s); + + return toRet; +} + +TcpTxItem* +TcpTxBuffer::GetNewSegment (uint32_t numBytes) +{ + NS_LOG_FUNCTION (this << numBytes); + + SequenceNumber32 startOfAppList = m_firstByteSeq + m_sentSize; + + TcpTxItem *item = GetPacketFromList (m_appList, startOfAppList, + numBytes, startOfAppList); + + // Move item from AppList to SentList (should be the first, not too complex) + PacketList::iterator it = std::find (m_appList.begin (), m_appList.end (), item); + NS_ASSERT (it != m_appList.end ()); + + m_appList.erase (it); + m_sentList.insert (m_sentList.end (), item); + m_sentSize += item->m_packet->GetSize (); + + return item; +} + +TcpTxItem* +TcpTxBuffer::GetTransmittedSegment (uint32_t numBytes, const SequenceNumber32 &seq) +{ + NS_LOG_FUNCTION (this << numBytes << seq); + NS_ASSERT (seq >= m_firstByteSeq); + NS_ASSERT (numBytes <= m_sentSize); + + return GetPacketFromList (m_sentList, m_firstByteSeq, numBytes, seq); } void -TcpTxBuffer::SetHeadSequence (const SequenceNumber32& seq) +TcpTxBuffer::SplitItems (TcpTxItem &t1, TcpTxItem &t2, uint32_t size) const { - NS_LOG_FUNCTION (this << seq); - m_firstByteSeq = seq; + NS_LOG_FUNCTION (this << size); + + t1.m_packet = t2.m_packet->CreateFragment (0, size); + t2.m_packet->RemoveAtStart (size); + + t1.m_sacked = t2.m_sacked; + t1.m_lastSent = t2.m_lastSent; + t1.m_retrans = t2.m_retrans; + t1.m_lost = t2.m_lost; +} + +TcpTxItem* +TcpTxBuffer::GetPacketFromList (PacketList &list, const SequenceNumber32 &listStartFrom, + uint32_t numBytes, const SequenceNumber32 &seq) const +{ + NS_LOG_FUNCTION (this << numBytes << seq); + + /* + * Our possibilites are sketched out in the following: + * + * |------| |----| |----| + * GetList (m_data) = | | --> | | --> | | + * |------| |----| |----| + * + * ^ ^ ^ ^ + * | | | | (1) + * seq | | numBytes + * | | + * | | + * seq numBytes (2) + * + * (1) seq and numBytes are the boundary of some packet + * (2) seq and numBytes are not the boundary of some packet + * + * We can have mixed case (e.g. seq over the boundary while numBytes not). + * + * If we discover that we are in (2) or in a mixed case, we split + * packets accordingly to the requested bounds and re-run the function. + * + * In (1), things are pretty easy, it's just a matter of walking the list and + * defragment packets, if needed (e.g. seq is the beginning of the first packet + * while maxBytes is the end of some packet next in the list). + */ + + Ptr currentPacket = 0; + TcpTxItem *currentItem = 0; + TcpTxItem *outItem = 0; + PacketList::iterator it = list.begin (); + SequenceNumber32 beginOfCurrentPacket = listStartFrom; + + while (it != list.end ()) + { + currentItem = *it; + currentPacket = currentItem->m_packet; + + // The objective of this snippet is to find (or to create) the packet + // that begin with the sequence seq + + if (seq < beginOfCurrentPacket + currentPacket->GetSize ()) + { + // seq is inside the current packet + if (seq == beginOfCurrentPacket) + { + // seq is the beginning of the current packet. Hurray! + outItem = currentItem; + NS_LOG_INFO ("Current packet starts at seq " << seq << + " ends at " << seq + currentPacket->GetSize ()); + } + else if (seq > beginOfCurrentPacket) + { + // seq is inside the current packet but seq is not the beginning, + // it's somewhere in the middle. Just fragment the beginning and + // start again. + NS_LOG_INFO ("we are at " << beginOfCurrentPacket << + " searching for " << seq << + " and now we recurse because packet ends at " + << beginOfCurrentPacket + currentPacket->GetSize ()); + TcpTxItem *firstPart = new TcpTxItem (); + SplitItems (*firstPart, *currentItem, seq - beginOfCurrentPacket); + + // insert firstPart before currentItem + list.insert (it, firstPart); + + return GetPacketFromList (list, listStartFrom, numBytes, seq); + } + else + { + NS_FATAL_ERROR ("seq < beginOfCurrentPacket: our data is before"); + } + } + else + { + // Walk the list, the current packet does not contain seq + beginOfCurrentPacket += currentPacket->GetSize (); + it++; + continue; + } + + NS_ASSERT (outItem != 0); + + // The objective of this snippet is to find (or to create) the packet + // that ends after numBytes bytes. We are sure that outPacket starts + // at seq. + + if (seq + numBytes <= beginOfCurrentPacket + currentPacket->GetSize ()) + { + // the end boundary is inside the current packet + if (numBytes == currentPacket->GetSize ()) + { + // the end boundary is exactly the end of the current packet. Hurray! + if (currentItem->m_packet == outItem->m_packet) + { + // A perfect match! + return outItem; + } + else + { + // the end is exactly the end of current packet, but + // current > outPacket in the list. Merge current with the + // previous, and recurse. + NS_ASSERT (it != list.begin ()); + TcpTxItem *previous = *(--it); + + list.erase (it); + + MergeItems (*previous, *currentItem); + delete currentItem; + + return GetPacketFromList (list, listStartFrom, numBytes, seq); + } + } + else if (numBytes < currentPacket->GetSize ()) + { + // the end is inside the current packet, but it isn't exactly + // the packet end. Just fragment, fix the list, and return. + TcpTxItem *firstPart = new TcpTxItem (); + SplitItems (*firstPart, *currentItem, numBytes); + + // insert firstPart before currentItem + list.insert (it, firstPart); + + return firstPart; + } + } + else + { + // The end isn't inside current packet, but there is an exception for + // the merge and recurse strategy... + if (++it == list.end ()) + { + // ...current is the last packet we sent. We have not more data; + // Go for this one. + NS_LOG_WARN ("Cannot reach the end, but this case is covered " + "with conditional statements inside CopyFromSequence." + "Something has gone wrong, report a bug"); + return outItem; + } + + // The current packet does not contain the requested end. Merge current + // with the packet that follows, and recurse + TcpTxItem *next = (*it); // Please remember we have incremented it + // in the previous if + + MergeItems (*currentItem, *next); + list.erase (it); + + delete next; + + return GetPacketFromList (list, listStartFrom, numBytes, seq); + } + } + + NS_FATAL_ERROR ("This point is not reachable"); +} + +void +TcpTxBuffer::MergeItems (TcpTxItem &t1, TcpTxItem &t2) const +{ + NS_LOG_FUNCTION (this); + if (t1.m_sacked == true && t2.m_sacked == true) + { + t1.m_sacked = true; + } + else + { + t1.m_sacked = false; + } + + if (t2.m_retrans == true && t1.m_retrans == false) + { + t1.m_retrans = true; + } + if (t1.m_lastSent < t2.m_lastSent) + { + t1.m_lastSent = t2.m_lastSent; + } + if (t2.m_lost) + { + t1.m_lost = true; + } + + t1.m_packet->AddAtEnd (t2.m_packet); } void TcpTxBuffer::DiscardUpTo (const SequenceNumber32& seq) { NS_LOG_FUNCTION (this << seq); - NS_LOG_LOGIC ("current data size=" << m_size << ", headSeq=" << m_firstByteSeq << ", maxBuffer=" << m_maxBuffer - << ", numPkts=" << m_data.size ()); + // Cases do not need to scan the buffer - if (m_firstByteSeq >= seq) return; + if (m_firstByteSeq >= seq) + { + NS_LOG_DEBUG ("Seq " << seq << " already discarded."); + return; + } // Scan the buffer and discard packets uint32_t offset = seq - m_firstByteSeq.Get (); // Number of bytes to remove uint32_t pktSize; - NS_LOG_LOGIC ("Offset=" << offset); - BufIterator i = m_data.begin (); - while (i != m_data.end ()) + PacketList::iterator i = m_sentList.begin (); + while (m_size > 0 && offset > 0) { - if (offset > (*i)->GetSize ()) + if (i == m_sentList.end ()) + { + Ptr p = CopyFromSequence (offset, m_firstByteSeq); + NS_ASSERT (p != 0); + i = m_sentList.begin (); + NS_ASSERT (i != m_sentList.end ()); + } + TcpTxItem *item = *i; + Ptr p = item->m_packet; + pktSize = p->GetSize (); + + if (offset >= pktSize) { // This packet is behind the seqnum. Remove this packet from the buffer - pktSize = (*i)->GetSize (); m_size -= pktSize; + m_sentSize -= pktSize; offset -= pktSize; m_firstByteSeq += pktSize; - i = m_data.erase (i); - NS_LOG_LOGIC ("Removed one packet of size " << pktSize << ", offset=" << offset); + i = m_sentList.erase (i); + delete item; + NS_LOG_INFO ("While removing up to " << seq << + ".Removed one packet of size " << pktSize << + " starting from " << m_firstByteSeq - pktSize << + ". Remaining data " << m_size); } else if (offset > 0) { // Part of the packet is behind the seqnum. Fragment - pktSize = (*i)->GetSize () - offset; - *i = (*i)->CreateFragment (offset, pktSize); + pktSize -= offset; + // PacketTags are preserved when fragmenting + item->m_packet = item->m_packet->CreateFragment (offset, pktSize); m_size -= offset; + m_sentSize -= offset; m_firstByteSeq += offset; - NS_LOG_LOGIC ("Fragmented one packet by size " << offset << ", new size=" << pktSize); + NS_LOG_INFO ("Fragmented one packet by size " << offset << + ", new size=" << pktSize); break; } } @@ -248,9 +589,57 @@ TcpTxBuffer::DiscardUpTo (const SequenceNumber32& seq) { m_firstByteSeq = seq; } - NS_LOG_LOGIC ("size=" << m_size << " headSeq=" << m_firstByteSeq << " maxBuffer=" << m_maxBuffer - <<" numPkts="<< m_data.size ()); - NS_ASSERT (m_firstByteSeq == seq); + + if (!m_sentList.empty ()) + { + TcpTxItem *head = m_sentList.front (); + if (head->m_sacked) + { + // It is not possible to have the UNA sacked; otherwise, it would + // have been ACKed. This is, most likely, our wrong guessing + // when crafting the SACK option for a non-SACK receiver. + head->m_sacked = false; + } + } + + NS_LOG_DEBUG ("Discarded up to " << seq); + NS_LOG_LOGIC ("Buffer status after discarding data " << *this); + NS_ASSERT (m_firstByteSeq >= seq); +} + +std::ostream & +operator<< (std::ostream & os, TcpTxBuffer const & tcpTxBuf) +{ + TcpTxBuffer::PacketList::const_iterator it; + std::stringstream ss; + SequenceNumber32 beginOfCurrentPacket = tcpTxBuf.m_firstByteSeq; + uint32_t sentSize = 0, appSize = 0; + + Ptr p; + for (it = tcpTxBuf.m_sentList.begin (); it != tcpTxBuf.m_sentList.end (); ++it) + { + p = (*it)->m_packet; + ss << "[" << beginOfCurrentPacket << ";" + << beginOfCurrentPacket + p->GetSize () << "|" << p->GetSize () << "|"; + (*it)->Print (ss); + ss << "]"; + sentSize += p->GetSize (); + beginOfCurrentPacket += p->GetSize (); + } + + for (it = tcpTxBuf.m_appList.begin (); it != tcpTxBuf.m_appList.end (); ++it) + { + appSize += (*it)->m_packet->GetSize (); + } + + os << "Sent list: " << ss.str () << ", size = " << tcpTxBuf.m_sentList.size () << + " Total size: " << tcpTxBuf.m_size << + " m_firstByteSeq = " << tcpTxBuf.m_firstByteSeq << + " m_sentSize = " << tcpTxBuf.m_sentSize; + + NS_ASSERT (sentSize == tcpTxBuf.m_sentSize); + NS_ASSERT (tcpTxBuf.m_size - tcpTxBuf.m_sentSize == appSize); + return os; } } // namepsace ns3 diff --git a/src/internet/model/tcp-tx-buffer.h b/src/internet/model/tcp-tx-buffer.h index c738e7083..700f464f0 100644 --- a/src/internet/model/tcp-tx-buffer.h +++ b/src/internet/model/tcp-tx-buffer.h @@ -1,6 +1,7 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2010 Adrian Sai-wah Tam + * Copyright (c) 2010-2015 Adrian Sai-wah Tam + * Copyright (c) 2016 Natale Patriciello * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -15,18 +16,16 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * Author: Adrian Sai-wah Tam + * Original author: Adrian Sai-wah Tam */ #ifndef TCP_TX_BUFFER_H #define TCP_TX_BUFFER_H -#include -#include "ns3/traced-value.h" -#include "ns3/trace-source-accessor.h" #include "ns3/object.h" +#include "ns3/traced-value.h" #include "ns3/sequence-number.h" -#include "ns3/ptr.h" +#include "ns3/nstime.h" namespace ns3 { class Packet; @@ -34,8 +33,72 @@ class Packet; /** * \ingroup tcp * - * \brief class for keeping the data sent by the application to the TCP socket, i.e. - * the sending buffer. + * \brief Item that encloses the application packet and some flags for it + */ +class TcpTxItem +{ +public: + /** + * \brief Constructor + */ + TcpTxItem (); + + /** + * \brief Copy-constructor + * \param other TcpTxTag to copy values from + */ + TcpTxItem (const TcpTxItem &other); + + /** + * \brief Print the time + * \param os ostream + */ + void Print (std::ostream &os) const; + + Ptr m_packet; //!< Application packet + bool m_lost; //!< Indicates if the segment has been lost (RTO) + bool m_retrans; //!< Indicates if the segment is retransmitted + Time m_lastSent; //!< Timestamp of the time at which the segment has + // been sent last time + bool m_sacked; //!< Indicates if the segment has been SACKed +}; + +/** + * \ingroup tcp + * + * \brief Tcp sender buffer + * + * The class keeps track of all data that the application wishes to transmit + * to the other end. When the data is acknowledged, it is removed from the buffer. + * The buffer has a maximum size, and data is not saved if the amount exceeds + * the limit. Packets can be added to the class through the method Add(). + * An important thing to remember is that all the data managed is strictly + * sequential. It can be divided in blocks, but all the data follow a strict + * ordering. That ordering is managed through SequenceNumber. + * + * In other words, this buffer contains numbered bytes (e.g 1,2,3), and the class + * is allowed to return only ordered (using "<" as operator) subsets (e.g. 1,2 + * or 2,3 or 1,2,3). + * + * The data structure underlying this is composed by two distinct packet lists. + * + * The first (SentList) is initially empty, and it contains the packets returned + * by the method CopyFromSequence. + * + * The second (AppList) is initially empty, and it contains the packets coming + * from the applications, but that are not transmitted yet as segments. + * + * To discover how the chunk are managed and retrieved from these lists, check + * CopyFromSequence documentation. + * + * The head of the data is represented by m_firstByteSeq, and it is returned by + * HeadSequence(). The last byte is returned by TailSequence(). + * In this class we store also the size (in bytes) of the packets inside the + * SentList in the variable m_sentSize. + * + * \see Size + * \see SizeFromSequence + * \see CopyFromSequence */ class TcpTxBuffer : public Object { @@ -55,43 +118,43 @@ public: // Accessors /** - * Returns the first byte's sequence number + * \brief Get the sequence number of the buffer head * \returns the first byte's sequence number */ SequenceNumber32 HeadSequence (void) const; /** - * Returns the last byte's sequence number + 1 + * \brief Get the sequence number of the buffer tail (plus one) * \returns the last byte's sequence number + 1 */ SequenceNumber32 TailSequence (void) const; /** - * Returns total number of bytes in this Tx buffer + * \brief Returns total number of bytes in this buffer * \returns total number of bytes in this Tx buffer */ uint32_t Size (void) const; /** - * Returns the Tx window size + * \brief Get the maximum buffer size * \returns the Tx window size (in bytes) */ uint32_t MaxBufferSize (void) const; /** - * Set the Tx window size + * \brief Set the maximum buffer size * \param n Tx window size (in bytes) */ void SetMaxBufferSize (uint32_t n); /** - * Returns the available capacity in this Tx window + * \brief Returns the available capacity of this buffer * \returns available capacity in this Tx window */ uint32_t Available (void) const; /** - * Append a data packet to the end of the buffer + * \brief Append a data packet to the end of the buffer * * \param p The packet to be appended to the Tx buffer * \return Boolean to indicate success @@ -99,14 +162,28 @@ public: bool Add (Ptr p); /** - * Returns the number of bytes from the buffer in the range [seq, tailSequence) + * \brief Returns the number of bytes from the buffer in the range [seq, tailSequence) + * * \param seq initial sequence number * \returns the number of bytes from the buffer in the range */ uint32_t SizeFromSequence (const SequenceNumber32& seq) const; /** - * Copy data of size numBytes into a packet, data from the range [seq, seq+numBytes) + * \brief Copy data from the range [seq, seq+numBytes) into a packet + * + * In the following, we refer to the block [seq, seq+numBytes) simply as "block". + * We check the boundary of the block, and divide the possibilities in three + * cases: + * + * - the block have already been transmitted (managed in GetTransmittedSegment) + * - the block have not been transmitted yet (managed in GetNewSegment) + * + * The last case is when the block is partially transmitted and partially + * not transmitted. We trick this case by requesting the portion not transmitted + * from GetNewSegment, and then calling GetTransmittedSegment with the full + * block range. + * * \param numBytes number of bytes to copy * \param seq start sequence number to extract * \returns a packet @@ -114,29 +191,166 @@ public: Ptr CopyFromSequence (uint32_t numBytes, const SequenceNumber32& seq); /** - * Set the m_firstByteSeq to seq. Supposed to be called only when the + * \brief Set the head sequence of the buffer + * + * Set the head (m_firstByteSeq) to seq. Supposed to be called only when the * connection is just set up and we did not send any data out yet. * \param seq The sequence number of the head byte */ void SetHeadSequence (const SequenceNumber32& seq); /** - * Discard data up to but not including this sequence number. + * \brief Discard data up to but not including this sequence number. * - * \param seq The sequence number of the head byte + * \param seq The first sequence number to maintain after discarding all the + * previous sequences. */ void DiscardUpTo (const SequenceNumber32& seq); private: - /// container for data stored in the buffer - typedef std::list >::iterator BufIterator; + friend std::ostream & operator<< (std::ostream & os, TcpTxBuffer const & tcpTxBuf); + + typedef std::list PacketList; //!< container for data stored in the buffer + + /** + * \brief Get a block of data not transmitted yet and move it into SentList + * + * If the block is not yet transmitted, hopefully, seq is exactly the sequence + * number of the first byte of the first packet inside AppList. We extract + * the block from AppList and move it into the SentList, before returning the + * block itself. We manage possible fragmentation (or merges) inside AppList + * through GetPacketFromList. + * + * \see GetPacketFromList + * \param numBytes number of bytes to copy + * + * \return the item that contains the right packet + */ + TcpTxItem* GetNewSegment (uint32_t numBytes); + + /** + * \brief Get a block of data previously transmitted + * + * This is clearly a retransmission, and if everything is going well, + * the block requested is matching perfectly with another one requested + * in the past. If not, fragmentation or merge are required. We manage + * both inside GetPacketFromList. + * + * \see GetPacketFromList + * + * \param numBytes number of bytes to copy + * \param seq sequence requested + * \return the item that contains the right packet + */ + TcpTxItem* GetTransmittedSegment (uint32_t numBytes, const SequenceNumber32 &seq); + + /** + * \brief Get a block (which is returned as Packet) from a list + * + * This function extract a block [requestedSeq,numBytes) from the list, which + * starts at startingSeq. + * + * The cases we need to manage are two, and they are depicted in the following + * image: + * + *\verbatim + |------| |----| |----| + list = | | --> | | --> | | + |------| |----| |----| + + ^ ^ + | ^ ^ | (1) + seq | | seq + numBytes + | | + | | + seq seq + numBytes (2) + \endverbatim + * + * The case 1 is easy to manage: the requested block is exactly a packet + * already stored. If one value (seq or seq + numBytes) does not align + * to a packet boundary, or when both values does not align (case 2), it is + * a bit more complex. + * + * Basically, we have two possible operations: + * + * - fragment : split an existing packet in two + * - merge : merge two existing packets in one + * + * and we reduce case (2) to case (1) through sequentially applying fragment + * or merge. For instance: + * + *\verbatim + |------| + | | + |------| + + ^ ^ ^ ^ + | | | | + start | | | + | | end + seq | + seq + numBytes + \endverbatim + * + * To reduce to case (1), we need to perform two fragment operations: + * + * - fragment (start, seq) + * - fragment (seq + numBytes, end) + * + * After these operations, the requested block is exactly the resulting packet. + * Merge operation is required when the requested block span over two (or more) + * existing packets. + * + * While this could be extremely slow in the worst possible scenario (one big + * packet which is split in small packets for transmission, and merged for + * re-transmission) that scenario is unlikely during a TCP transmission (since + * MSS can change, but it is stable, and retransmissions do not happen for + * each segment). + * + * \param list List to extract block from + * \param startingSeq Starting sequence of the list + * \param numBytes Bytes to extract, starting from requestedSeq + * \param requestedSeq Requested sequence + * \return the item that contains the right packet + */ + TcpTxItem* GetPacketFromList (PacketList &list, const SequenceNumber32 &startingSeq, + uint32_t numBytes, const SequenceNumber32 &requestedSeq) const; + + /** + * \brief Merge two TcpTxItem + * + * Merge t2 in t1. It consists in copying the lastSent field if t2 is more + * recent than t1. Retransmitted field is copied only if it set in t2 but not + * in t1. Sacked is copied only if it is true in both items. + * + * \param t1 first item + * \param t2 second item + */ + void MergeItems (TcpTxItem &t1, TcpTxItem &t2) const; + + /** + * \brief Split one TcpTxItem + * + * Move "size" bytes from t2 into t1, copying all the fields. + * + * \param t1 first item + * \param t2 second item + * \param size Size to split + */ + void SplitItems (TcpTxItem &t1, TcpTxItem &t2, uint32_t size) const; + + PacketList m_appList; //!< Buffer for application data + PacketList m_sentList; //!< Buffer for sent (but not acked) data + uint32_t m_maxBuffer; //!< Max number of data bytes in buffer (SND.WND) + uint32_t m_size; //!< Size of all data in this buffer + uint32_t m_sentSize; //!< Size of sent (and not discarded) segments TracedValue m_firstByteSeq; //!< Sequence number of the first byte in data (SND.UNA) - uint32_t m_size; //!< Number of data bytes - uint32_t m_maxBuffer; //!< Max number of data bytes in buffer (SND.WND) - std::list > m_data; //!< Corresponding data (may be null) + }; +std::ostream & operator<< (std::ostream & os, TcpTxBuffer const & tcpTxBuf); + } // namepsace ns3 #endif /* TCP_TX_BUFFER_H */