tcp: TcpTxBuffer stores a list of TcpTxItem

This commit is contained in:
Natale Patriciello
2017-02-03 14:01:36 +01:00
parent a97a735523
commit 18eedbb266
2 changed files with 721 additions and 118 deletions

View File

@@ -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 <natale.patriciello@gmail.com>
*
* 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 <adrian.sw.tam@gmail.com>
* Original author: Adrian Sai-wah Tam <adrian.sw.tam@gmail.com>
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <iostream>
#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<Packet> 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<Packet>
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<Packet> (); // Empty packet returned
}
if (m_data.size () == 0)
{ // No actual data, just return dummy-data packet of correct size
return Create<Packet> (s);
NS_LOG_ERROR ("Requested a sequence number which is not in the buffer anymore");
return Create<Packet> ();
}
// 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<Packet> 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<Packet> 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<Packet> ();
}
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<Packet> 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<Packet> 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<Packet> p = CopyFromSequence (offset, m_firstByteSeq);
NS_ASSERT (p != 0);
i = m_sentList.begin ();
NS_ASSERT (i != m_sentList.end ());
}
TcpTxItem *item = *i;
Ptr<Packet> 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<Packet> 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

View File

@@ -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 <natale.patriciello@gmail.com>
*
* 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 <adrian.sw.tam@gmail.com>
* Original author: Adrian Sai-wah Tam <adrian.sw.tam@gmail.com>
*/
#ifndef TCP_TX_BUFFER_H
#define TCP_TX_BUFFER_H
#include <list>
#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<Packet> 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<Packet> 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<Packet> 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<Ptr<Packet> >::iterator BufIterator;
friend std::ostream & operator<< (std::ostream & os, TcpTxBuffer const & tcpTxBuf);
typedef std::list<TcpTxItem*> 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<SequenceNumber32> 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<Ptr<Packet> > m_data; //!< Corresponding data (may be null)
};
std::ostream & operator<< (std::ostream & os, TcpTxBuffer const & tcpTxBuf);
} // namepsace ns3
#endif /* TCP_TX_BUFFER_H */