A stack which works with or without an Mbed os library. Provides IPv4 or IPv6 with a full 1500 byte buffer.

Dependents:   oldheating gps motorhome heating

tcp/tcp.cpp

Committer:
andrewboyson
Date:
2017-11-13
Revision:
55:e64b8b47a2b6
Parent:
54:84ef2b29cf7e
Child:
56:35117a8b5c65

File content as of revision 55:e64b8b47a2b6:

#include   "mbed.h"
#include    "log.h"
#include    "net.h"
#include "action.h"
#include    "tcp.h"
#include    "tcb.h"
#include    "ip4.h"
#include   "dhcp.h"
#include   "http.h"

#define MAX_MSS 536 //This is 576 - 20 - 20

bool TcpTrace = false;
static bool doTrace = false;

__packed struct header
{
    uint16_t srcPort;
    uint16_t dstPort;
    uint32_t seqnum;
    uint32_t acknum;
    uint8_t  dataOffset;
    uint8_t  flags;
    uint16_t window; 
    uint16_t checksum;
    uint16_t urgent;
};

//Header variables
    
static uint16_t    srcPort;
static uint16_t    dstPort;
static uint32_t     seqnum;
static uint32_t     acknum;
static int      headersize;
static uint8_t       flags;
static bool            URG; //indicates that the Urgent pointer field is significant
static bool            ACK; //indicates that the Acknowledgment field is significant. All packets after the initial SYN packet sent by the client should have this flag set.
static bool            PSH; //Push function. Asks to push the buffered data to the receiving application.
static bool            RST; //Reset the connection
static bool            SYN; //Synchronize sequence numbers. Only the first packet sent from each end should have this flag set. Some other flags and fields change meaning based on this flag, and some are only valid for when it is set, and others when it is clear.
static bool            FIN; //No more data from sender

static uint16_t     window;
static uint16_t   checksum;
static uint16_t     urgent;
static int    optionLength;
    
static char*    pOptions;
static char*    pData;
static int      dataLength;
static int positionInRequestStream;
static int positionInReplyStream;

static uint16_t mss;

static void (*pTraceback)(void);

static struct tcb* pTcb;

static void logFlags()
{
    if (URG) Log(" URG");
    if (ACK) Log(" ACK");
    if (PSH) Log(" PSH");
    if (RST) Log(" RST");
    if (SYN) Log(" SYN");
    if (FIN) Log(" FIN");
}
void TcpLogHeader(uint16_t calculatedChecksum, bool isSend)
{
    if (NetTraceVerbose)
    {
        Log("TCP header\r\n");
        LogF("  Source port      %hu\r\n",   srcPort);
        LogF("  Destination port %hu\r\n",   dstPort);
        if (isSend)
        {
            LogF("  Loc Seq number  %u (%u)\r\n",  seqnum - pTcb->locIsn, seqnum);
            LogF("  Rem Ack number  %u (%u)\r\n",  acknum - pTcb->remIsn, acknum);
        }
        else
        {
            LogF("  Rem Seq number  %u (%u)\r\n",  seqnum - pTcb->remIsn, seqnum);
            LogF("  Loc Ack number  %u (%u)\r\n",  acknum - pTcb->locIsn, acknum);
        }
        LogF("  Header size      %u\r\n",    headersize);
        Log ("  Flags           "); logFlags(); Log("\r\n");
        LogF("  Window           %hu\r\n",   window);
        LogF("  Checksum (hex)   %04hX\r\n", checksum);
        LogF("  Calculated (hex) %04hX\r\n", calculatedChecksum);
        LogF("  Urgent pointer   %hu\r\n",   urgent);
        LogF("  Option length    %d\r\n",    optionLength);
        LogF("  Data length      %d\r\n",    dataLength);
        LogF("  locIsn           %u\r\n",    pTcb->locIsn);
        LogF("  remIsn           %u\r\n",    pTcb->remIsn);
        LogF("  locSeq           %u\r\n",    pTcb->locSeq);
        LogF("  remSeq           %u\r\n",    pTcb->remSeq);

    }
    else
    {
        LogF("TCP   header %hu >>> %hu", srcPort, dstPort);
        logFlags();
        if (isSend) LogF(", loc seq %u, rem ack %u", seqnum - pTcb->locIsn, acknum - pTcb->remIsn);
        else        LogF(", rem seq %u, loc ack %u", seqnum - pTcb->remIsn, acknum - pTcb->locIsn);
        Log("\r\n");
    }
}
void TcpAddChecksum(void* pPacket, uint16_t checksum)
{
    struct header* pHeader = (header*)pPacket;
    pHeader->checksum    = checksum;
}
static void readOptions()
{
    mss = 536; //default MSS for IPv4 [576 - 20(TCP) - 20(IP)];
    for (char* p = pOptions; p < pOptions + optionLength; p++)
    {
        switch (*p)
        {
            case 0: break; //End of options - used to pad to 32 bit boundary
            case 1: break; //NOP, padding - optional
            case 2:
                p++;
                if (*p != 4) LogTimeF("MSS option width %d when expected 4\r\n", *p);
                p++;
                mss = ((uint16_t)*p) << 8;
                p++;
                mss += *p;
                return;
            default: LogTimeF("Unrecognised TCP option %d\r\n", *p);
        }
    }
    if (mss > MAX_MSS) mss = MAX_MSS;
}
static void writeOptions()
{
    pOptions[0]  = 2;
    pOptions[1]  = 4;
    pOptions[2]  = mss >> 8;
    pOptions[3]  = mss & 0xFF;
    optionLength = 4;
}

void TcpReadHeader(void* pPacket, uint16_t size)
{
    struct header* pHeader = (header*)pPacket;
                        
            srcPort = NetToHost16(pHeader->srcPort);
            dstPort = NetToHost16(pHeader->dstPort);
             seqnum = NetToHost32(pHeader->seqnum);
             acknum = NetToHost32(pHeader->acknum);
         headersize =            (pHeader->dataOffset >> 2) & 0xFC; //Same as right shifting by 4 bits and multiplying by 4
              flags =             pHeader->flags;
                URG = flags & 0x20; //indicates that the Urgent pointer field is significant
                ACK = flags & 0x10; //indicates that the Acknowledgment field is significant. All packets after the initial SYN packet sent by the client should have this flag set.
                PSH = flags & 0x08; //Push function. Asks to push the buffered data to the receiving application.
                RST = flags & 0x04; //Reset the connection
                SYN = flags & 0x02; //Synchronize sequence numbers. Only the first packet sent from each end should have this flag set. Some other flags and fields change meaning based on this flag, and some are only valid for when it is set, and others when it is clear.
                FIN = flags & 0x01; //No more data from sender
    
             window = NetToHost16(pHeader->window);
           checksum = NetToHost16(pHeader->checksum);
             urgent = NetToHost16(pHeader->urgent);
           pOptions = (char*)pPacket + 20;
       optionLength = headersize - 20;

                  pData = (char*)pPacket + headersize;
             dataLength =           size - headersize;
}

void TcpMakeHeader(int size, void* pPacket)
{
    struct header* pHeader = (header*)pPacket;
    
    pHeader->dstPort    = NetToHost16(dstPort);
    pHeader->srcPort    = NetToHost16(srcPort);
    pHeader->seqnum     = NetToHost32(seqnum); //This is the sequence number of the first byte of this message
    pHeader->acknum     = NetToHost32(acknum); //This is the sequence number we expect in the next message
    pHeader->dataOffset = headersize << 2;     //Same as dividing by 4 to get bytes and left shifting by 4 bits
    flags = 0;
    if(URG) flags |= 0x20; //indicates that the Urgent pointer field is significant
    if(ACK) flags |= 0x10; //indicates that the Acknowledgment field is significant. All packets after the initial SYN packet sent by the client should have this flag set.
    if(PSH) flags |= 0x08; //Push function. Asks to push the buffered data to the receiving application.
    if(RST) flags |= 0x04; //Reset the connection
    if(SYN) flags |= 0x02; //Synchronize sequence numbers. Only the first packet sent from each end should have this flag set. Some other flags and fields change meaning based on this flag, and some are only valid for when it is set, and others when it is clear.
    if(FIN) flags |= 0x01; //No more data from sender
    pHeader->flags      = flags;
    pHeader->window     = NetToHost16(window);
    pHeader->urgent     = NetToHost16(urgent);
    
    pHeader->checksum   = 0;
}
static void makeRST()
{
    dataLength = 0;
    headersize = 20;
    seqnum = acknum;
    acknum = 0;
    ACK = false;
    PSH = false;
    RST = true;
    SYN = false;
    FIN = false;
}
static int stateClosed()
{
    if (!SYN)    //Reset if anything other than a request to establish conection from client
    {
        if (TcpTrace) LogTimeF("TCP did not received other than a SYN when closed - sent reset.\r\n");
        makeRST();
        return 1;        
    }
    
    readOptions();                 //Get the MSS
    pTcb->mss     = mss;
    pTcb->state   = TCB_SYN_RECEIVED;
    pTcb->elapsed = TcbElapsed;
    pTcb->port    = srcPort;
        
    dataLength = 0;
    
    mss = MAX_MSS;                 //Ethernet 1500 - 20 - 20; or, in our case 768 - 20 - 20
    writeOptions();
    headersize = 24;               //20 header plus 4 option
   
    ACK = true;                    //Send ACK and SYN
    PSH = false;
    RST = false;
    SYN = true;
    FIN = false;
    
    return 1;
}
static int stateSynReceived()
{
    if (dataLength) LogTimeF("%d bytes data received before TCB established - ignoring\r\n", dataLength);
    if (ACK)
    {
        pTcb->state   = TCB_ESTABLISHED;
        pTcb->elapsed = TcbElapsed;
        pTcb->todo     = 0;
    }
    return 0;
}
static int stateEstablished()
{
    if (!ACK) return 0;              //Ignore any packets which don't contain an ACK
    
    //Handle reception of data
    char* pRequestStream = pData;
    char* pReplyStream   = pOptions;

    HttpHandleRequest(&dataLength, pRequestStream, positionInRequestStream - 1, pReplyStream, positionInReplyStream - 1, mss, &pTcb->todo);
    
    //Rearrange the buffers
    headersize = 20;
    pData = pReplyStream;
        
    ACK = true;                 //Send ACK
    RST = false;
    SYN = false;
    PSH = false;
    
    if (dataLength < mss) //If a part packet then there can be no more to send
    {
        FIN = true;                 //Inform the client that we have no more to send after this
        pTcb->state = TCB_CLOSING;  //Start closing
    }

    pTcb->elapsed = TcbElapsed;
    return 1;
}
static int stateClosing()
{
    if (!FIN) return 0;       //Ignore any packets which don't contain a FIN
        
    ACK = true;               //Send ACK
    PSH = false;
    RST = false;
    SYN = false;
    FIN = false;
    
    headersize = 20;
    dataLength = 0;
    
    pTcb->state  = TCB_CLOSED;
    
    return 1;
}

int TcpHandleReceivedPacket(void (*traceback)(void), int* pSize, void* pPacket)
{
    pTraceback = traceback;
    
    //Get the Transmission Control Block
    pTcb = TcbGetExisting(srcPort);
    if (!pTcb)  pTcb = TcbGetEmpty();
    if (!pTcb)
    {
        if (TcpTrace)
        {
            LogTime("TCP no more tcbs are available\r\n");
            pTraceback(); //This will already include the TCP header
        }
        return DO_NOTHING; //Bomb out if no more tcbs are available
    }

    //Handle request to reset
    if (RST)
    {
        pTcb->state  = TCB_CLOSED; //Reset connection
        return DO_NOTHING;         //Bomb out
    }
    
    //Handle request to synchronise
    if (SYN)
    {
        pTcb->remIsn = seqnum;
        pTcb->remSeq = pTcb->remIsn;
        pTcb->locIsn = TcbGetIsn();
        pTcb->locSeq = pTcb->locIsn;
        acknum       = pTcb->locIsn;
    }
    
    //Check the sequence is not lost or out of order
    if (seqnum != pTcb->remSeq)
    {
        LogTimeF("TCP had sequence %u but expected %u\r\n", seqnum, pTcb->remSeq);
    }
    
    //Check the remote host has received all bytes
    if (acknum != pTcb->locSeq)
    {
        LogTimeF("TCP acknowledged %u but expected %u\r\n", acknum, pTcb->locSeq);
    }

    positionInRequestStream = seqnum - pTcb->remIsn;
    positionInReplyStream   = acknum - pTcb->locIsn;

    //Filter out unwanted links
    switch (dstPort)
    {
        case 80:
            doTrace = HttpTrace;
            if (doTrace)
            {
                if (NetTraceNewLine) Log("\r\n");
                LogTime("HTTP server request\r\n");
            }
            break;
            
        default:
            doTrace = TcpTrace;
            if (doTrace)
            {
                if (NetTraceNewLine) Log("\r\n");
                LogTimeF("TCP unknown port %d\r\n", dstPort);
            }
            return DO_NOTHING; //Ignore unknown ports
    }
    if (doTrace && NetTraceStack) pTraceback(); //This will already include the TCP header
    
    if (SYN) pTcb->remSeq += 1;          //Add one to acknowledge the SYN
             pTcb->remSeq += dataLength; //Add the number of bytes received
    if (FIN) pTcb->remSeq += 1;          //Add one to acknowledge the FIN
    acknum = pTcb->remSeq;               //Set up the acknowledgement field ready to send

    switch (pTcb->state)
    {
        case TCB_CLOSED:       if (stateClosed     ()) break; return DO_NOTHING;
        case TCB_SYN_RECEIVED: if (stateSynReceived()) break; return DO_NOTHING;
        case TCB_ESTABLISHED:  if (stateEstablished()) break; return DO_NOTHING;
        case TCB_CLOSING:      if (stateClosing    ()) break; return DO_NOTHING;
    }
    
    seqnum = pTcb->locSeq;               //Set the sequence number to the first byte of this message
    if (SYN) pTcb->locSeq += 1;          //Add one to acknowledge the SYN
             pTcb->locSeq += dataLength; //Record the next sequence number
    if (FIN) pTcb->locSeq += 1;          //Add one to acknowledge the FIN

    
    srcPort = dstPort;
    dstPort = pTcb->port;
    
    *pSize = dataLength + headersize;
        
    return ActionMakeFromDestAndTrace(UNICAST, doTrace && NetTraceStack);

}