Andrew Boyson / net

Dependents:   oldheating gps motorhome heating

tcp/tcp.c

Committer:
andrewboyson
Date:
2018-01-11
Revision:
61:aad055f1b0d1
Parent:
tcp/tcp.cpp@ 59:e0e556c8bd46
Child:
62:9b8c1e1761b6

File content as of revision 61:aad055f1b0d1:

#include <stdint.h>
#include <stdbool.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"

static int maxMss = 0;

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;

//Payload variables
static char*    pOptionsRx;
static char*    pOptionsTx;
static int      optionLength;
static char*    pDataRx;
static char*    pDataTx;
static int      dataLength;


static void readHeader(void* pPacket, uint16_t size)
{
    struct header* pHeader = (struct 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);
}

static int positionInQuery;
static int positionInReply;

static uint16_t mss;

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");
        Log("  Source port      "); LogF("%hu",     srcPort                ); Log("\r\n");
        Log("  Destination port "); LogF("%hu",     dstPort                ); Log("\r\n");
        Log("  Seq number       "); LogF("%u (%u)", positionInQuery, seqnum); Log("\r\n");
        Log("  Ack number       "); LogF("%u (%u)", positionInReply, acknum); Log("\r\n");
        Log("  Header size      "); LogF("%u",      headersize             ); Log("\r\n");
        Log("  Flags           " ); logFlags(                              ); Log("\r\n");
        Log("  Window           "); LogF("%hu",     window                 ); Log("\r\n");
        Log("  Checksum (hex)   "); LogF("%04hX",   checksum               ); Log("\r\n");
        Log("  Calculated (hex) "); LogF("%04hX",   calculatedChecksum     ); Log("\r\n");
        Log("  Urgent pointer   "); LogF("%hu",     urgent                 ); Log("\r\n");
        Log("  Option length    "); LogF("%d",      optionLength           ); Log("\r\n");
        Log("  Data length      "); LogF("%u",      dataLength             ); Log("\r\n");
        Log("  sendIsn          "); LogF("%u",      pTcb->sendIsn          ); Log("\r\n");
        Log("  recvIsn          "); LogF("%u",      pTcb->recvIsn          ); Log("\r\n");
        Log("  sentBytes        "); LogF("%u",      pTcb->sentBytes        ); Log("\r\n");
        Log("  recdBytes        "); LogF("%u",      pTcb->recdBytes        ); Log("\r\n");

    }
    else
    {
        LogF("TCP   header %hu >>> %hu", srcPort, dstPort);
        logFlags();
        LogF(", query %u, reply %u", positionInQuery, positionInReply);
        Log("\r\n");
    }
}
void TcpAddChecksum(void* pPacket, uint16_t checksum)
{
    struct header* pHeader = (struct header*)pPacket;
    pHeader->checksum    = checksum;
}
static void readOptions()
{
    mss = 536; //default MSS for IPv4 [576 - 20(TCP) - 20(IP)];
    for (char* p = pOptionsRx; p < pOptionsRx + 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 > maxMss) mss = maxMss;
}
static void writeOptions()
{
    pOptionsTx[0]  = 2;
    pOptionsTx[1]  = 4;
    pOptionsTx[2]  = mss >> 8;
    pOptionsTx[3]  = mss & 0xFF;
    optionLength = 4;
}

void TcpMakeHeader(int size, void* pPacket)
{
    struct header* pHeader = (struct 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 = sizeof(struct header);
    ACK = false;
    PSH = false;
    RST = true;
    SYN = false;
    FIN = false;
    
    pTcb->state = TCB_EMPTY;
}
static void startConnection()
{
    readOptions();                 //Get the MSS
    pTcb->mss     = mss;
    pTcb->state   = TCB_SYN_RECEIVED;
    pTcb->elapsed = TcbElapsed;
    pTcb->port    = srcPort;
    pTcb->hadFin  = false;
        
    mss = maxMss;                 //Ethernet 1500 - 20 - 20; or, in our case 768 - 20 - 20
    writeOptions();
    headersize = sizeof(struct header) + optionLength;
    dataLength = 0;
   
    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()
{    
    pDataTx   = pOptionsTx; //No options
    headersize = sizeof(struct header);

    HttpHandleRequest(&dataLength, pDataRx, positionInQuery - 1, pDataTx, positionInReply - 1, mss, &pTcb->todo);
    
    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
        if (pTcb->hadFin) pTcb->state = TCB_ACK_WAIT; //Passive close
        else              pTcb->state = TCB_FIN_WAIT; //Active close
    }
    else
    {
        FIN = false;
    }

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

int TcpHandleReceivedPacket(void (*traceback)(void), int sizeRx, void* pPacketRx, int* pSizeTx, void* pPacketTx)
{
    readHeader(pPacketRx, sizeRx);
    
     pDataRx    = (char*)pPacketRx + headersize;
     dataLength =           sizeRx - headersize;
     maxMss     =         *pSizeTx - sizeof(struct header);
    
     pOptionsRx = (char*)pPacketRx + sizeof(struct header);
     pOptionsTx = (char*)pPacketTx + sizeof(struct header);
   optionLength =       headersize - sizeof(struct header);
    
    doTrace = false;
    
    //Filter out unwanted links
    switch (dstPort)
    {
        case 80:
            if (HttpTrace)
            {
                if (NetTraceNewLine) Log("\r\n");
                LogTime("HTTP server request\r\n");
                doTrace = true;
            }
            break;
            
        default:
            if (TcpTrace)
            {
                if (NetTraceNewLine) Log("\r\n");
                LogTimeF("TCP unknown port %d\r\n", dstPort);
                if (NetTraceStack) traceback();
            }
            return DO_NOTHING; //Ignore unknown ports
    }
    
    //Get the Transmission Control Block
    pTcb = TcbGetExisting(srcPort);
    if (!pTcb)  pTcb = TcbGetEmpty();
    if (!pTcb)
    {
        if (TcpTrace)
        {
            if (NetTraceNewLine) Log("\r\n");
            LogTime("TCP no more tcbs are available\r\n");
            if (NetTraceStack) traceback();
        }
        return DO_NOTHING; //Bomb out if no more tcbs are available
    }

    //Handle request to reset
    if (RST)
    {
        if (TcpTrace)
        {
            if (NetTraceNewLine) Log("\r\n");
            LogTime("TCP received reset - resetting TCB\r\n");
            if (NetTraceStack) traceback();
        }
        pTcb->state  = TCB_EMPTY; //Reset connection
        return DO_NOTHING;        //Don't reply
    }
    
    //Handle request to synchronise
    if (SYN)
    {
        pTcb->recvIsn   = seqnum;
        pTcb->recdBytes = 0;
        pTcb->sendIsn   = TcbGetIsn();
        pTcb->sentBytes = 0;
        acknum          = pTcb->sendIsn;
    }
    
    //Calculate positions
    positionInQuery = seqnum - pTcb->recvIsn;
    positionInReply = acknum - pTcb->sendIsn;
    
    //Check packet sequences
    if (positionInQuery != pTcb->recdBytes || positionInReply != pTcb->sentBytes)
    {
        if (TcpTrace)
        {
            if (NetTraceNewLine) Log("\r\n");
            LogTime("TCP received packet");      
            if (positionInQuery != pTcb->recdBytes) LogF(": starting at byte %u rather than %u so data reread"  , positionInQuery, pTcb->recdBytes);        
            if (positionInReply != pTcb->sentBytes) LogF(": acknowledging byte %u rather than %u so data resent", positionInReply, pTcb->sentBytes);
            Log("\r\n");
            
            doTrace = true;
        }
        pTcb->recdBytes = positionInQuery;
        pTcb->sentBytes = positionInReply;
    }
    if (doTrace && NetTraceStack) traceback(); //This will already include the TCP header
    
    if (SYN) pTcb->recdBytes += 1;            //Add one to acknowledge the SYN
             pTcb->recdBytes += dataLength;   //Add the number of bytes received
    if (FIN) pTcb->recdBytes += 1;            //Add one to acknowledge the FIN

    switch (pTcb->state)
    {
        case TCB_EMPTY:
            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; }
            if (FIN) pTcb->hadFin = true; //When reply is all sent only a passive close is needed
            handleEstablishedConnection();
            break;
            
        case TCB_FIN_WAIT: //End of active close
            if (!FIN) return DO_NOTHING; //Ignore ACK to our FIN. Wait for FIN then close.
            closeConnection();
            break;
            
        case TCB_ACK_WAIT: //End of passive close
            if (!ACK) { resetConnection("received other than an ACK when closing half open connection"); break; } 
            pTcb->state = TCB_EMPTY;
            return DO_NOTHING;
    }
    
    positionInReply = pTcb->recdBytes;       //Set up the acknowledgement field ready to send
    positionInQuery = pTcb->sentBytes;       //Record the start of the query before adding the bytes sent
    
    //Keep a record of where we expect the next packet send to start
    if (SYN) pTcb->sentBytes += 1;           //Add one to acknowledge the SYN
             pTcb->sentBytes += dataLength;  //Record the next sequence number
    if (FIN) pTcb->sentBytes += 1;           //Add one to acknowledge the FIN

    //Specify the start of the data being sent and acknowledge the data received
    seqnum = positionInQuery + pTcb->sendIsn;
    acknum = positionInReply + pTcb->recvIsn;
    
    //Swap the ports for the reply
    srcPort = dstPort;
    dstPort = pTcb->port;
    
    //Calculate the size of the reply
    *pSizeTx = dataLength + headersize;
        
    return ActionMakeFromDestAndTrace(UNICAST, doTrace && NetTraceStack);

}