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-10-22
Revision:
44:83ce5ace337b
Parent:
43:bc028d5a6424
Child:
49:1a6336f2b3f9

File content as of revision 44:83ce5ace337b:

#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-request.h"
#include "http-reply.h"

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

__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;
};

uint32_t now = 0;
static void ticked()
{
    now++;
    TcbReap(now);
}
static Ticker ticker;

void TcpInit()
{
    ticker.attach(&ticked, 1);
    TcbInit();
}

//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    void*    pData;
static    int      dataLength;
static    uint16_t mss;

void TcpLogHeader(uint16_t calculatedChecksum)
{
    if (NetTraceVerbose)
    {
        Log("TCP header\r\n");
        LogF("  Source port      %hu\r\n",   srcPort);
        LogF("  Destination port %hu\r\n",   dstPort);
        LogF("  Sequence number  %u\r\n",    seqnum);
        LogF("  Ack number       %u\r\n",    acknum);
        LogF("  Header size      %u\r\n",    headersize);
        LogF("  Flags            %02X",      flags);
        if (URG) LogF("    URG");
        if (ACK) LogF("    ACK");
        if (PSH) LogF("    PSH");
        if (RST) LogF("    RST");
        if (SYN) LogF("    SYN");
        if (FIN) LogF("    FIN");
        LogF("\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);
    }
    else
    {
        LogF("TCP   header %hu >>> %hu", srcPort, dstPort);
        if (URG) Log(" URG");
        if (ACK) Log(" ACK");
        if (PSH) Log(" PSH");
        if (RST) Log(" RST");
        if (SYN) Log(" SYN");
        if (FIN) Log(" FIN");
        LogF("\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 int stateClosed(struct tcb* pTcb)
{
    if (!SYN)    //Reset if anything other than a request to establish conection from client
    {
        dataLength = 0;
        headersize = 20;
        seqnum = acknum;
        acknum = 0;
        ACK = false;                    //Send RST
        PSH = false;
        RST = true;
        SYN = false;
        FIN = false;
        return 1;        
    }
    
    readOptions();                 //Get the MSS
    pTcb->mss    = mss;
    
    pTcb->state  = TCB_SYN_RECEIVED;
    pTcb->timer  = now;
    pTcb->port   = srcPort;
    pTcb->locIsn = TcbGetIsn();
    pTcb->locSeq = pTcb->locIsn;  
    pTcb->remIsn = seqnum;
    pTcb->remSeq = pTcb->remIsn;
    
    pTcb->remSeq += 1;             //Add one to acknowledge the SYN
    acknum = pTcb->remSeq;
    
    seqnum = pTcb->locSeq;         //Set the sequence number to the first byte of this message
    pTcb->locSeq += 1;             //Add one to the next sequence number as we are sending a SYN
    
    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(struct tcb* pTcb)
{
    if (dataLength) LogTimeF("%d bytes data received before TCB established\r\n", dataLength);
    if (ACK)
    {
        pTcb->state = TCB_ESTABLISHED;
        pTcb->timer = now;
        pTcb->tag = 0;
    }
    return 0;
}
static int stateEstablished(struct tcb* pTcb)
{
    if (!ACK) return 0;              //Ignore any packets which don't contain an ACK
    
    //Handle reception of data
    if (dataLength)
    {
        pTcb->tag = HttpRequest(seqnum - pTcb->remIsn - 1, dataLength, (char*)pData);
        pTcb->remSeq = seqnum + dataLength;
    }
    acknum = pTcb->remSeq;      //Set the ack num to the next byte expected from the client
    
    
    //Rearrange the buffers
    headersize = 20;
    pData = pOptions;
   
    //Handle sending of any data
    dataLength = HttpReply(pTcb->locSeq - pTcb->locIsn - 1, mss, (char*)pData, pTcb->tag);
    
    seqnum = pTcb->locSeq;      //Set the sequence number to the first byte of this message
    
    ACK = true;                 //Send ACK
    RST = false;
    SYN = false;
    PSH = false;
    
    pTcb->locSeq += dataLength; //Record the next sequence number
    
    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->locSeq += 1;          //Record the FIN in the sequence
        pTcb->state = TCB_CLOSING;  //Start closing
    }

    pTcb->timer = now;
    return 1;
}
static int stateClosing(struct tcb* pTcb)
{
    if (!FIN) return 0;       //Ignore any packets which don't contain a FIN
    
    pTcb->remSeq += 1;        //Add one to acknowledge the FIN
    acknum = pTcb->remSeq;
    
    seqnum = pTcb->locSeq;    //Set the sequence number to the first byte of this message
                              //but don't change it for the next
    
    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)
{
    struct tcb* pTcb = TcbGetExisting(srcPort);
    if (!pTcb)  pTcb = TcbGetEmpty();
    if (!pTcb) return DO_NOTHING; //Bomb out if no more tcbs are available

    if (RST)
    {
        pTcb->state  = TCB_CLOSED; //Reset connection
        return DO_NOTHING;         //Bomb out
    }

    switch (dstPort)
    {
        case 80: break;
        default: return DO_NOTHING; //This needs to become a reset
    }
    
    //Drop duplicate packets
    //if (!TCB_CLOSED && (int32_t)(seqnum - pTcb->remSeq) < 0)
    //{
    //    LogTimeF("Dropped duplicate seq num=%d expected=%d packet length %d from port %u\r\n", seqnum, pTcb->remSeq, size, srcPort);
    //    return IP4_DO_NOTHING;
    //}

    switch (pTcb->state)
    {
        case TCB_CLOSED:       if (stateClosed     (pTcb)) break; return DO_NOTHING;
        case TCB_SYN_RECEIVED: if (stateSynReceived(pTcb)) break; return DO_NOTHING;
        case TCB_ESTABLISHED:  if (stateEstablished(pTcb)) break; return DO_NOTHING;
        case TCB_CLOSING:      if (stateClosing    (pTcb)) break; return DO_NOTHING;
    }
    
    srcPort = dstPort;
    dstPort = pTcb->port;
    
    *pSize = dataLength + headersize;
    return UNICAST;

}