Andrew Boyson / net

Dependents:   oldheating gps motorhome heating

tcp/tcp.cpp

Committer:
andrewboyson
Date:
2017-11-14
Revision:
56:35117a8b5c65
Parent:
55:e64b8b47a2b6
Child:
57:e0fb648acf48

File content as of revision 56:35117a8b5c65:

#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 positionInQuery;
static int positionInReply;

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)
{
    if (NetTraceVerbose)
    {
        Log("TCP header\r\n");
        LogF("  Source port      %hu\r\n",      srcPort);
        LogF("  Destination port %hu\r\n",      dstPort);
        LogF("  Seq number       %u (%u)\r\n",  positionInQuery, seqnum);
        LogF("  Ack number       %u (%u)\r\n",  positionInReply, 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();
        LogF(", seq %u, ack %u", positionInQuery, positionInReply);
        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 resetConnection(char* message)
{
    if (TcpTrace)
    {
        LogTime("TCP ");
        Log(message);
        Log("\r\n");
    }
    dataLength = 0;
    headersize = 20;
    ACK = false;
    PSH = false;
    RST = true;
    SYN = false;
    FIN = false;
}
static void startConnection()
{
    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;
}
static void establishConnection()
{
    pTcb->state   = TCB_ESTABLISHED;
    pTcb->elapsed = TcbElapsed;
    pTcb->todo     = 0;
}
static void handleEstablishedConnection()
{    
    char* pRequestStream = pData;
    char* pReplyStream   = pOptions;

    HttpHandleRequest(&dataLength, pRequestStream, positionInQuery - 1, pReplyStream, positionInReply - 1, mss, &pTcb->todo);
    
    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
    }
    else
    {
        FIN = false;
    }

    pTcb->elapsed = TcbElapsed;
}
static void closeConnection()
{        
    ACK = true;               //Send ACK
    PSH = false;
    RST = false;
    SYN = false;
    FIN = false;
    
    headersize = 20;
    dataLength = 0;
    
    pTcb->state  = TCB_CLOSED;
}

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;         //Don't reply
    }
    
    //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);
    }

    positionInQuery = seqnum - pTcb->remIsn;
    positionInReply = 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 (!SYN) { resetConnection("received other than a SYN when connection closed"); break; }
            startConnection();
            break;
            
        case TCB_SYN_RECEIVED:
            if (dataLength) { resetConnection("data received before connection established"); break; }
            if (!ACK)       { resetConnection("received other than an ACK before connection established"); break; }
            establishConnection();
            return DO_NOTHING;
            
        case TCB_ESTABLISHED:
            if (!ACK) { resetConnection("received other than an ACK during established conection"); break; } 
            handleEstablishedConnection();
            break;
            
        case TCB_CLOSING:
            if (!FIN) return DO_NOTHING; //Ignore ACK to our FIN. Wait for FIN then close.
            closeConnection();
            break;
    }
    
    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

    positionInQuery = seqnum - pTcb->locIsn;
    positionInReply = acknum - pTcb->remIsn;
    
    srcPort = dstPort;
    dstPort = pTcb->port;
    
    *pSize = dataLength + headersize;
        
    return ActionMakeFromDestAndTrace(UNICAST, doTrace && NetTraceStack);

}