std1.0.0-beta-7 SafeC docs

Source net/tcp.sc

View in the repository

// SafeC Standard Library — TCP Implementation (minimal state machine)
#pragma once
#include <std/net/tcp.h>
#include <std/net/ethernet.h>

// ── Checksum helpers ──────────────────────────────────────────────────────────

namespace std {

unsigned short tcp_checksum(unsigned int src_ip, unsigned int dst_ip,
                             []const unsigned char tcp_seg_s) {
    unsigned long seg_len = tcp_seg_s.len;
    const &unsigned char tcp_seg;
    unsafe { tcp_seg = (const &unsigned char)tcp_seg_s.ptr; }
    unsigned long sum = (unsigned long)0;
    // Pseudo-header: src_ip, dst_ip, 0x00, proto=6, tcp_length
    unsafe {
        sum = sum + (unsigned long)((src_ip >> 16) & (unsigned int)0xFFFF);
        sum = sum + (unsigned long)(src_ip & (unsigned int)0xFFFF);
        sum = sum + (unsigned long)((dst_ip >> 16) & (unsigned int)0xFFFF);
        sum = sum + (unsigned long)(dst_ip & (unsigned int)0xFFFF);
        sum = sum + (unsigned long)6;   // protocol = TCP
        sum = sum + seg_len;
        unsigned long i = (unsigned long)0;
        while (i + (unsigned long)1 < seg_len) {
            unsigned long word = ((unsigned long)tcp_seg[i] << 8)
                               | (unsigned long)tcp_seg[i + (unsigned long)1];
            sum = sum + word;
            i = i + (unsigned long)2;
        }
        if (i < seg_len) {
            sum = sum + ((unsigned long)tcp_seg[i] << 8);
        }
    }
    while ((sum >> 16) != (unsigned long)0) {
        sum = (sum & (unsigned long)0xFFFF) + (sum >> 16);
    }
    return (unsigned short)(~sum & (unsigned long)0xFFFF);
}

int tcp_parse(&PacketBuf pkt, unsigned long offset,
              &TcpHdr hdr_out) {
    if (pkt.len < offset + (unsigned long)TCP_HDR_MIN_LEN) { return -1; }
    unsafe {
        unsigned char* d = (unsigned char*)pkt.data + offset;
        hdr_out.src_port = ((unsigned short)(((unsigned short)d[0] << 8)
                                                     | (unsigned short)d[1]));
        hdr_out.dst_port = ((unsigned short)(((unsigned short)d[2] << 8)
                                                     | (unsigned short)d[3]));
        hdr_out.seq      = (((unsigned int)d[4] << 24) | ((unsigned int)d[5] << 16)
                                   | ((unsigned int)d[6] << 8)  |  (unsigned int)d[7]);
        hdr_out.ack_num  = (((unsigned int)d[8] << 24) | ((unsigned int)d[9] << 16)
                                   | ((unsigned int)d[10] << 8) |  (unsigned int)d[11]);
        hdr_out.data_off = (unsigned char)((d[12] >> 4) & (unsigned char)0xF);
        hdr_out.flags    = d[13];
        hdr_out.window   = ((unsigned short)(((unsigned short)d[14] << 8)
                                                     | (unsigned short)d[15]));
        hdr_out.checksum = (unsigned short)0;
        hdr_out.urgent   = (unsigned short)0;
    }
    // The data offset counts 32-bit words: at least the 5-word fixed header,
    // and the options it implies must lie inside the packet.
    unsigned long hdrBytes = (unsigned long)hdr_out.data_off * 4UL;
    if (hdrBytes < (unsigned long)TCP_HDR_MIN_LEN || pkt.len < offset + hdrBytes) { return -1; }
    return 0;
}

// ── Helper: write TCP header into pkt at offset ───────────────────────────────

static unsigned long tcp_write_hdr_(unsigned char* d,
                                    unsigned short src_port, unsigned short dst_port,
                                    unsigned int seq, unsigned int ack_num,
                                    unsigned char flags, unsigned short window) {
    unsafe {
        unsigned short sp = (src_port);
        unsigned short dp = (dst_port);
        d[0] = (unsigned char)((sp >> 8) & (unsigned short)0xFF);
        d[1] = (unsigned char)(sp & (unsigned short)0xFF);
        d[2] = (unsigned char)((dp >> 8) & (unsigned short)0xFF);
        d[3] = (unsigned char)(dp & (unsigned short)0xFF);
        unsigned int seq_n = (seq);
        d[4] = (unsigned char)((seq_n >> 24) & (unsigned int)0xFF);
        d[5] = (unsigned char)((seq_n >> 16) & (unsigned int)0xFF);
        d[6] = (unsigned char)((seq_n >>  8) & (unsigned int)0xFF);
        d[7] = (unsigned char)(seq_n & (unsigned int)0xFF);
        unsigned int ack_n = (ack_num);
        d[8]  = (unsigned char)((ack_n >> 24) & (unsigned int)0xFF);
        d[9]  = (unsigned char)((ack_n >> 16) & (unsigned int)0xFF);
        d[10] = (unsigned char)((ack_n >>  8) & (unsigned int)0xFF);
        d[11] = (unsigned char)(ack_n & (unsigned int)0xFF);
        d[12] = (unsigned char)0x50;  // data offset = 5 (20 bytes)
        d[13] = flags;
        unsigned short win = (window);
        d[14] = (unsigned char)((win >> 8) & (unsigned short)0xFF);
        d[15] = (unsigned char)(win & (unsigned short)0xFF);
        d[16] = (unsigned char)0; d[17] = (unsigned char)0;  // checksum placeholder
        d[18] = (unsigned char)0; d[19] = (unsigned char)0;  // urgent
    }
    return (unsigned long)TCP_HDR_MIN_LEN;
}

// ── TcpConn methods ───────────────────────────────────────────────────────────

void TcpConn::connect(unsigned int remote_ip, unsigned short remote_port,
                      unsigned int local_ip, unsigned short local_port,
                      unsigned int isn) {
    self.state       = TCP_SYN_SENT;
    self.remote_ip   = remote_ip;
    self.remote_port = remote_port;
    self.local_ip    = local_ip;
    self.local_port  = local_port;
    // The SYN itself carries 'isn'; build_segment() advances snd_nxt past
    // it (a SYN occupies one sequence number).
    self.snd_nxt     = isn;
    self.snd_una     = isn;
    self.rcv_nxt     = (unsigned int)0;
    self.rcv_wnd     = (unsigned int)TCP_RX_BUF;
    self.rx_len      = (unsigned long)0;
    self.tx_len      = (unsigned long)0;
}

void TcpConn::listen(unsigned int local_ip, unsigned short local_port,
                     unsigned int isn) {
    self.state       = TCP_LISTEN;
    self.local_ip    = local_ip;
    self.local_port  = local_port;
    self.remote_ip   = (unsigned int)0;
    self.remote_port = (unsigned short)0;
    self.snd_nxt     = isn;
    self.snd_una     = isn;
    self.rcv_nxt     = (unsigned int)0;
    self.rcv_wnd     = (unsigned int)TCP_RX_BUF;
    self.rx_len      = (unsigned long)0;
    self.tx_len      = (unsigned long)0;
}

inline void TcpConn::close() { self.drop(); }

inline void TcpConn::drop() {
    if (self.state == TCP_ESTABLISHED) {
        self.state = TCP_FIN_WAIT1;
    } else if (self.state == TCP_CLOSE_WAIT) {
        self.state = TCP_LAST_ACK;
    }
}

int TcpConn::recv(&PacketBuf pkt, unsigned long tcp_offset) {
    struct TcpHdr hdr;
    if (tcp_parse(pkt, tcp_offset, &hdr) != 0) { return 0; }
    if (hdr.dst_port != self.local_port) { return 0; }

    unsigned char flags = hdr.flags;
    int prev = self.state;

    if (self.state == TCP_LISTEN) {
        // Passive open: accept a SYN, learn the peer from it.
        if ((flags & (unsigned char)(TCP_SYN | TCP_ACK)) == (unsigned char)TCP_SYN &&
            tcp_offset >= (unsigned long)IPV4_HDR_LEN) {
            struct Ipv4Hdr ih;
            if (ipv4_parse(pkt, tcp_offset - (unsigned long)IPV4_HDR_LEN, &ih) == 0) {
                self.remote_ip   = ih.src;
                self.remote_port = hdr.src_port;
                self.rcv_nxt     = hdr.seq + (unsigned int)1;
                self.state       = TCP_SYN_RECEIVED;
            }
        }
    } else if (self.state == TCP_SYN_RECEIVED) {
        // Final ACK of the handshake (acknowledging our SYN).
        if ((flags & (unsigned char)TCP_ACK) != (unsigned char)0 &&
            hdr.ack_num == self.snd_nxt) {
            self.snd_una = hdr.ack_num;
            self.state   = TCP_ESTABLISHED;
        }
    } else if (self.state == TCP_SYN_SENT) {
        // Expect SYN+ACK
        if ((flags & (unsigned char)(TCP_SYN | TCP_ACK)) == (unsigned char)(TCP_SYN | TCP_ACK)) {
            self.rcv_nxt = hdr.seq + (unsigned int)1;
            self.snd_una = hdr.ack_num;
            self.state   = TCP_ESTABLISHED;
        }
    } else if (self.state == TCP_ESTABLISHED) {
        if ((flags & (unsigned char)TCP_ACK) != (unsigned char)0) {
            self.snd_una = hdr.ack_num;
        }
        // Receive payload — in order only: a segment that doesn't start at
        // rcv_nxt is a retransmit/duplicate or arrived early, and appending
        // it would corrupt the byte stream (the peer retransmits on our ACK).
        unsigned long tcp_hdr_bytes = (unsigned long)hdr.data_off * (unsigned long)4;
        unsigned long data_start = tcp_offset + tcp_hdr_bytes;
        if (hdr.seq != self.rcv_nxt) { return 0; }
        if (pkt.len > data_start) {
            unsigned long data_len = pkt.len - data_start;
            unsigned long space = (unsigned long)TCP_RX_BUF - self.rx_len;
            if (data_len > space) { data_len = space; }
            unsafe {
                unsigned char* src = (unsigned char*)pkt.data + data_start;
                unsigned long i = (unsigned long)0;
                while (i < data_len) {
                    self.rx_buf[self.rx_len + i] = src[i];
                    i = i + (unsigned long)1;
                }
            }
            self.rx_len = self.rx_len + data_len;
            self.rcv_nxt = self.rcv_nxt + (unsigned int)data_len;
        }
        if ((flags & (unsigned char)TCP_FIN) != (unsigned char)0) {
            self.rcv_nxt = self.rcv_nxt + (unsigned int)1;
            self.state   = TCP_CLOSE_WAIT;
        }
    } else if (self.state == TCP_FIN_WAIT1) {
        if ((flags & (unsigned char)TCP_ACK) != (unsigned char)0) {
            self.state = TCP_FIN_WAIT2;
        }
    } else if (self.state == TCP_FIN_WAIT2) {
        if ((flags & (unsigned char)TCP_FIN) != (unsigned char)0) {
            self.rcv_nxt = self.rcv_nxt + (unsigned int)1;
            self.state   = TCP_TIME_WAIT;
        }
    } else if (self.state == TCP_LAST_ACK) {
        if ((flags & (unsigned char)TCP_ACK) != (unsigned char)0) {
            self.state = TCP_CLOSED;
        }
    }

    if (self.state != prev) { return 1; }
    return 0;
}

inline unsigned long TcpConn::send([]const unsigned char data_s) {
    unsigned long len = data_s.len;
    if (len == 0UL) { return 0UL; }
    const &unsigned char data;
    unsafe { data = (const &unsigned char)data_s.ptr; }
    unsigned long space = (unsigned long)TCP_TX_BUF - self.tx_len;
    if (len > space) { len = space; }
    unsafe {
        unsigned long i = (unsigned long)0;
        while (i < len) {
            self.tx_buf[self.tx_len + i] = data[i];
            i = i + (unsigned long)1;
        }
    }
    self.tx_len = self.tx_len + len;
    return len;
}

int TcpConn::build_segment(&PacketBuf pkt,
                            const unsigned char eth_src[NET_MAC_LEN],
                            const unsigned char eth_dst[NET_MAC_LEN]) {
    if (self.state == TCP_CLOSED) { return 0; }

    unsigned char flags = (unsigned char)0;
    unsigned int seq = self.snd_nxt;

    if (self.state == TCP_SYN_SENT) {
        flags = (unsigned char)TCP_SYN;
        seq = self.snd_una;          // a retransmitted SYN keeps the ISN
        self.snd_nxt = self.snd_una;
    } else if (self.state == TCP_SYN_RECEIVED) {
        flags = (unsigned char)(TCP_SYN | TCP_ACK);
        // Re-sending the SYN+ACK (e.g. it was lost) reuses the same seq.
        seq = self.snd_una;
        self.snd_nxt = self.snd_una;
    } else if (self.state == TCP_ESTABLISHED) {
        flags = (unsigned char)TCP_ACK;
        if (self.tx_len > (unsigned long)0) {
            flags = (unsigned char)(flags | (unsigned char)TCP_PSH);
        }
    } else if (self.state == TCP_FIN_WAIT1 || self.state == TCP_LAST_ACK) {
        flags = (unsigned char)(TCP_FIN | TCP_ACK);
    } else if (self.state == TCP_CLOSE_WAIT) {
        flags = (unsigned char)TCP_ACK;
    } else {
        return 0;
    }

    unsigned long payload_len = self.tx_len;
    unsigned long total_tcp = (unsigned long)TCP_HDR_MIN_LEN + payload_len;
    unsigned short ip_payload = (unsigned short)((unsigned short)IPV4_HDR_LEN
                                                + (unsigned short)total_tcp);

    pkt.reset();
    eth_build(pkt, eth_dst, eth_src, (unsigned short)ETH_TYPE_IPV4);
    unsigned long ip_off  = (unsigned long)ETH_HDR_LEN;
    ipv4_build(pkt, ip_off, (unsigned char)IP_PROTO_TCP,
               self.local_ip, self.remote_ip,
               (unsigned short)((unsigned short)TCP_HDR_MIN_LEN
                                + (unsigned short)payload_len));

    unsigned long tcp_off = ip_off + (unsigned long)IPV4_HDR_LEN;
    unsafe {
        unsigned char* d = (unsigned char*)pkt.data + tcp_off;
        tcp_write_hdr_(d, self.local_port, self.remote_port,
                       seq, self.rcv_nxt, flags,
                       (unsigned short)self.rcv_wnd);
        // Copy tx_buf payload.
        unsigned long i = (unsigned long)0;
        while (i < payload_len) {
            d[(unsigned long)TCP_HDR_MIN_LEN + i] = self.tx_buf[i];
            i = i + (unsigned long)1;
        }
        // Fill checksum.
        unsigned short csum = tcp_checksum(self.local_ip, self.remote_ip, ((const unsigned char*)d)[0..total_tcp]);
        unsigned short csum_n = (csum);
        d[16] = (unsigned char)((csum_n >> 8) & (unsigned short)0xFF);
        d[17] = (unsigned char)(csum_n & (unsigned short)0xFF);
    }
    pkt.len = tcp_off + total_tcp;

    // Advance sequence numbers.
    if ((flags & (unsigned char)TCP_SYN) != (unsigned char)0) {
        self.snd_nxt = self.snd_nxt + (unsigned int)1;
    }
    if (payload_len > (unsigned long)0) {
        self.snd_nxt = self.snd_nxt + (unsigned int)payload_len;
        self.tx_len  = (unsigned long)0;
    }
    if ((flags & (unsigned char)TCP_FIN) != (unsigned char)0) {
        self.snd_nxt = self.snd_nxt + (unsigned int)1;
    }

    return 1;
}

inline int TcpConn::rx_ready() const {
    if (self.rx_len > (unsigned long)0) { return 1; }
    return 0;
}

inline unsigned long TcpConn::read([]unsigned char out_s) {
    unsigned long len = out_s.len;
    if (len == 0UL) { return 0UL; }
    &unsigned char out;
    unsafe { out = (&unsigned char)out_s.ptr; }
    if (len > self.rx_len) { len = self.rx_len; }
    unsafe {
        unsigned long i = (unsigned long)0;
        while (i < len) {
            out[i] = self.rx_buf[i];
            i = i + (unsigned long)1;
        }
        // Shift remaining bytes down.
        unsigned long remain = self.rx_len - len;
        i = (unsigned long)0;
        while (i < remain) {
            self.rx_buf[i] = self.rx_buf[len + i];
            i = i + (unsigned long)1;
        }
    }
    self.rx_len = self.rx_len - len;
    return len;
}

} // namespace std