File syn_tcp.c¶
Go to the documentation of this file
#include "syntropic/net/syn_tcp.h"
#include "syntropic/port/syn_port_system.h"
#include "syntropic/sched/syn_sched.h"
#include "syntropic/util/syn_assert.h"
#include "syntropic/util/syn_pack.h"
#include <string.h>
uint16_t syn_tcp_checksum(uint32_t src_ip, uint32_t dst_ip, const uint8_t *tcp_seg, size_t len)
{
uint32_t sum = 0;
/* Pseudo-header: Src IP (4), Dst IP (4), Reserved (1), Proto (1), TCP Len (2) */
sum += (src_ip >> 16) + (src_ip & 0xFFFF);
sum += (dst_ip >> 16) + (dst_ip & 0xFFFF);
sum += 0x0006; /* Protocol 6 (TCP) */
sum += (uint16_t)len;
/* Segment bytes */
const uint8_t *ptr = tcp_seg;
size_t left = len;
while (left > 1) {
sum += ((uint16_t)ptr[0] << 8) | ptr[1];
ptr += 2;
left -= 2;
}
if (left == 1) {
sum += (uint16_t)ptr[0] << 8;
}
while (sum >> 16) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
return (uint16_t)(~sum);
}
SYN_Status syn_tcp_init(SYN_TCP *tcp, SYN_ETH *eth)
{
if (!tcp || !eth) {
return SYN_INVALID_PARAM;
}
memset(tcp, 0, sizeof(*tcp));
tcp->eth = eth;
return SYN_OK;
}
SYN_Status syn_tcp_listen(SYN_TCP *tcp, uint16_t port)
{
if (!tcp || port == 0) {
return SYN_INVALID_PARAM;
}
/* Find first closed slot */
for (size_t i = 0; i < SYN_TCP_MAX_CONNS; i++) {
if (tcp->conns[i].state == SYN_TCP_CLOSED) {
tcp->conns[i].state = SYN_TCP_LISTEN;
tcp->conns[i].local_port = port;
if (tcp->eth != NULL) {
tcp->conns[i].local_ip = tcp->eth->ip_addr;
}
return SYN_OK;
}
}
return SYN_ERROR;
}
static void build_tcp_segment(SYN_TCP *tcp, SYN_TcpConn *conn, uint8_t flags,
const uint8_t *payload, size_t payload_len, uint8_t *tx_out,
size_t *tx_len)
{
/* Ethernet (14) + IPv4 (20) + TCP (20) + Payload */
uint16_t total_ip_len = 20 + 20 + payload_len;
uint16_t total_frame_len = 14 + total_ip_len;
/* Ethernet Header */
memcpy(&tx_out[0], conn->remote_mac, 6);
/* Local MAC: read from ETH instance; zeroed if unavailable (should not happen in practice) */
if (tcp && tcp->eth) {
memcpy(&tx_out[6], tcp->eth->mac_addr, 6);
} else {
/* LCOV_EXCL_START: Defensive MAC fallback */
memset(&tx_out[6], 0, 6);
/* LCOV_EXCL_STOP */
}
tx_out[12] = 0x08;
tx_out[13] = 0x00; /* IPv4 */
/* IPv4 Header */
tx_out[14] = 0x45;
tx_out[15] = 0x00;
syn_poke_u16(total_ip_len, tx_out, 16);
syn_poke_u16(0x1234, tx_out, 18);
syn_poke_u16(0x0000, tx_out, 20);
tx_out[22] = 64; /* TTL */
tx_out[23] = 6; /* TCP */
tx_out[24] = 0;
tx_out[25] = 0; /* IP Checksum */
syn_poke_u32(conn->local_ip, tx_out, 26);
syn_poke_u32(conn->remote_ip, tx_out, 30);
/* IP Checksum */
uint32_t ip_sum = 0;
for (int i = 14; i < 34; i += 2) {
ip_sum += ((uint16_t)tx_out[i] << 8) | tx_out[i + 1];
}
while (ip_sum >> 16) {
ip_sum = (ip_sum & 0xFFFF) + (ip_sum >> 16);
}
syn_poke_u16((uint16_t)(~ip_sum), tx_out, 24);
/* TCP Header */
syn_poke_u16(conn->local_port, tx_out, 34);
syn_poke_u16(conn->remote_port, tx_out, 36);
syn_poke_u32(conn->seq_nxt, tx_out, 38);
syn_poke_u32(conn->ack_nxt, tx_out, 42);
tx_out[46] = 0x50; /* 20 bytes (5 words) */
tx_out[47] = flags;
syn_poke_u16(SYN_TCP_BUF_SIZE, tx_out, 48); /* Window */
tx_out[50] = 0;
tx_out[51] = 0; /* Checksum */
tx_out[52] = 0;
tx_out[53] = 0; /* Urgent Pointer */
if (payload_len > 0 && payload != NULL) {
/* LCOV_EXCL_START: TCP payload copy */
memcpy(&tx_out[54], payload, payload_len);
/* LCOV_EXCL_STOP */
}
/* TCP Checksum */
uint16_t tcp_csum =
syn_tcp_checksum(conn->local_ip, conn->remote_ip, &tx_out[34], 20 + payload_len);
syn_poke_u16(tcp_csum, tx_out, 50);
*tx_len = total_frame_len;
}
SYN_Status syn_tcp_process_packet(SYN_TCP *tcp, const uint8_t *frame, size_t len, uint8_t *tx_out,
size_t *tx_len)
{
if (!tcp || !frame || len < 54 || !tx_out || !tx_len) {
return SYN_INVALID_PARAM;
}
*tx_len = 0;
/* Parse IP header */
uint8_t proto = frame[23];
if (proto != 6) { /* Not TCP */
return SYN_ERROR;
}
uint32_t src_ip = syn_peek_u32(frame, 26);
uint16_t src_port = syn_peek_u16(frame, 34);
uint16_t dst_port = syn_peek_u16(frame, 36);
uint32_t seq_num = syn_peek_u32(frame, 38);
uint32_t ack_num = syn_peek_u32(frame, 42);
(void)ack_num;
uint8_t data_offset = (frame[46] >> 4) * 4;
uint8_t flags = frame[47];
size_t payload_len = 0;
uint16_t ip_len = syn_peek_u16(frame, 16);
if ((size_t)ip_len >= 20 + (size_t)data_offset) {
payload_len = (size_t)ip_len - 20 - (size_t)data_offset;
}
/* Find matching connection slot (SYN_RCVD or ESTABLISHED) */
SYN_TcpConn *conn = NULL;
for (size_t i = 0; i < SYN_TCP_MAX_CONNS; i++) {
if ((tcp->conns[i].state == SYN_TCP_ESTABLISHED ||
tcp->conns[i].state == SYN_TCP_SYN_RCVD) &&
tcp->conns[i].remote_ip == src_ip && tcp->conns[i].remote_port == src_port &&
tcp->conns[i].local_port == dst_port) {
conn = &tcp->conns[i];
break;
}
}
if (!conn) {
/* Search for LISTEN slot */
for (size_t i = 0; i < SYN_TCP_MAX_CONNS; i++) {
if (tcp->conns[i].state == SYN_TCP_LISTEN && tcp->conns[i].local_port == dst_port) {
conn = &tcp->conns[i];
break;
}
}
}
if (!conn) {
return SYN_ERROR;
}
/* State Machine */
if (conn->state == SYN_TCP_LISTEN) {
if (flags & SYN_TCP_FLAG_SYN) {
conn->state = SYN_TCP_SYN_RCVD;
conn->remote_ip = src_ip;
conn->remote_port = src_port;
memcpy(conn->remote_mac, &frame[6], 6);
conn->ack_nxt = seq_num + 1;
/* Use tick-based ISN instead of a fixed constant to avoid
* predictable sequence numbers and stale connection reuse. */
conn->seq_nxt = syn_port_get_tick_ms();
build_tcp_segment(tcp, conn, SYN_TCP_FLAG_SYN | SYN_TCP_FLAG_ACK, NULL, 0, tx_out,
tx_len);
conn->seq_nxt++;
return SYN_OK;
}
} else if (conn->state == SYN_TCP_SYN_RCVD) {
if (flags & SYN_TCP_FLAG_ACK) {
conn->state = SYN_TCP_ESTABLISHED;
return SYN_OK;
}
} else if (conn->state == SYN_TCP_ESTABLISHED) {
if (flags & SYN_TCP_FLAG_FIN) {
conn->ack_nxt = seq_num + 1;
conn->state = SYN_TCP_CLOSED;
build_tcp_segment(tcp, conn, SYN_TCP_FLAG_ACK | SYN_TCP_FLAG_FIN, NULL, 0, tx_out,
tx_len);
if (conn->blocked_task) {
syn_task_resume(conn->blocked_task);
}
return SYN_OK;
}
if (payload_len > 0) {
const uint8_t *payload = &frame[14 + 20 + data_offset];
size_t accepted = 0;
if (conn->rx_len < SYN_TCP_BUF_SIZE) {
size_t space = SYN_TCP_BUF_SIZE - conn->rx_len;
accepted = (payload_len <= space) ? payload_len : space;
memcpy(&conn->rx_buf[conn->rx_len], payload, accepted);
conn->rx_len += (uint16_t)accepted;
}
/* Only advance ack by bytes actually accepted — never false-ACK dropped data */
conn->ack_nxt = seq_num + (uint32_t)accepted;
build_tcp_segment(tcp, conn, SYN_TCP_FLAG_ACK, NULL, 0, tx_out, tx_len);
/* Resume task blocked in SYN_TASK_BLOCKED state */
if (conn->blocked_task) {
syn_task_resume(conn->blocked_task);
}
return SYN_OK;
}
}
return SYN_OK; /* LCOV_EXCL_LINE: Defensive NULL check or invalid parameter fallback */
}