Skip to content

File syn_udp.c

File List > net > syn_udp.c

Go to the documentation of this file

#include "syntropic/net/syn_udp.h"

#include "syntropic/sched/syn_sched.h"
#include "syntropic/sched/syn_task.h"
#include "syntropic/util/syn_pack.h"

#include <string.h>

SYN_Status syn_udp_init(SYN_UDP *udp, SYN_ETH *eth)
{
    if (!udp) {
        return SYN_INVALID_PARAM;
    }
    memset(udp, 0, sizeof(*udp));
    udp->eth = eth;
    return SYN_OK;
}

SYN_UdpSocket *syn_udp_bind(SYN_UDP *udp, uint16_t local_port)
{
    if (!udp || local_port == 0) {
        return NULL;
    }

    /* Check if already bound */
    for (size_t i = 0; i < SYN_UDP_MAX_SOCKETS; i++) {
        if (udp->sockets[i].is_bound && udp->sockets[i].local_port == local_port) {
            return &udp->sockets[i];
        }
    }

    /* Allocate free slot */
    for (size_t i = 0; i < SYN_UDP_MAX_SOCKETS; i++) {
        if (!udp->sockets[i].is_bound) {
            memset(&udp->sockets[i], 0, sizeof(SYN_UdpSocket));
            udp->sockets[i].is_bound = true;
            udp->sockets[i].local_port = local_port;
            return &udp->sockets[i];
        }
    }

    return NULL;
}

void syn_udp_unbind(SYN_UdpSocket *sock)
{
    if (!sock) {
        return;
    }
    if (sock->blocked_task) {
        syn_task_resume(sock->blocked_task);
    }
    memset(sock, 0, sizeof(*sock));
}

uint16_t syn_udp_checksum(uint32_t src_ip, uint32_t dst_ip, const uint8_t *udp_seg, size_t len)
{
    if (!udp_seg || len < 8) {
        return 0;
    }

    uint32_t sum = 0;

    /* Pseudo Header */
    sum += (src_ip >> 16) & 0xFFFF;
    sum += src_ip & 0xFFFF;
    sum += (dst_ip >> 16) & 0xFFFF;
    sum += dst_ip & 0xFFFF;
    sum += 17; /* Protocol UDP = 17 */
    sum += (uint16_t)len;

    /* UDP Header & Payload (checksum field at offset 6 zeroed during calc) */
    for (size_t i = 0; i < len; i += 2) {
        if (i == 6)
            continue; /* Skip checksum field */
        if (i + 1 < len) {
            sum += ((uint16_t)udp_seg[i] << 8) | udp_seg[i + 1];
        } else {
            sum += ((uint16_t)udp_seg[i] << 8);
        }
    }

    while (sum >> 16) {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }

    uint16_t csum = (uint16_t)(~sum);
    return (csum == 0) ? 0xFFFF : csum;
}

SYN_Status syn_udp_process_packet(SYN_UDP *udp, const uint8_t *ip_packet, size_t len)
{
    if (!udp || !ip_packet || len < 14 + 20 + 8) {
        return SYN_INVALID_PARAM;
    }

    /* Check EtherType IPv4 (0x0800) */
    uint16_t ethertype = syn_peek_u16(ip_packet, 12);
    if (ethertype != 0x0800) {
        return SYN_ERROR;
    }

    /* Check Protocol = 17 (UDP) */
    uint8_t proto = ip_packet[23];
    if (proto != 17) {
        return SYN_ERROR;
    }

    uint32_t src_ip = syn_peek_u32(ip_packet, 26);
    const uint8_t *udp_head = &ip_packet[34];
    uint16_t src_port = syn_peek_u16(udp_head, 0);
    uint16_t dst_port = syn_peek_u16(udp_head, 2);
    uint16_t udp_len = syn_peek_u16(udp_head, 4);

    if (udp_len < 8 || (size_t)(14 + 20 + udp_len) > len) {
        return SYN_ERROR;
    }

    size_t payload_len = udp_len - 8;
    const uint8_t *payload = &udp_head[8];

    /* Find matching socket by dst_port */
    for (size_t i = 0; i < SYN_UDP_MAX_SOCKETS; i++) {
        SYN_UdpSocket *sock = &udp->sockets[i];
        if (sock->is_bound && sock->local_port == dst_port) {
            sock->remote_ip = src_ip;
            sock->remote_port = src_port;

            if (payload_len > 0) {
                if (sock->rx_len + payload_len <= SYN_UDP_BUF_SIZE) {
                    memcpy(&sock->rx_buf[sock->rx_len], payload, payload_len);
                    sock->rx_len += (uint16_t)payload_len;
                }
            }

            /* Targeted wake: wake ONLY the task sleeping on this specific UDP port */
            if (sock->blocked_task) {
                syn_task_resume(sock->blocked_task);
            }

            return SYN_OK;
        }
    }

    return SYN_ERROR; /* No socket bound on dst_port */
}

int syn_udp_sendto(SYN_UDP *udp, uint16_t src_port, uint32_t dst_ip, uint16_t dst_port,
                   const uint8_t *data, size_t data_len, uint8_t *tx_out, size_t *tx_len)
{
    if (!udp || !tx_out || !tx_len) {
        return -1;
    }

    size_t total_len = 14 + 20 + 8 + data_len;
    if (total_len > SYN_ETH_MAX_FRAME_LEN) {
        return -1;
    }

    /* Ethernet Header (14) */
    uint8_t resolved_mac[6];
    bool has_mac = false;
    if (udp->eth && syn_eth_arp_lookup(udp->eth, dst_ip, resolved_mac) == SYN_OK) {
        has_mac = true;
    }
    static const uint8_t bcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
    const uint8_t *dst_mac = has_mac ? resolved_mac : bcast_mac;
    const uint8_t *src_mac = udp->eth ? udp->eth->mac_addr : bcast_mac;

    /* LCOV_EXCL_START: Unreachable parameter bounds check */
    if (syn_eth_pack_header(tx_out, total_len, dst_mac, src_mac, SYN_ETHTYPE_IPV4) == 0) {
        return -1;
    }

    /* IPv4 Header (20) */
    uint32_t src_ip = udp->eth ? udp->eth->ip_addr : 0;
    uint16_t udp_payload_len = (uint16_t)(8 + data_len);
    if (syn_ip_pack_header(&tx_out[14], total_len - 14, src_ip, dst_ip, 17, udp_payload_len,
                           0x1234) == 0) {
        return -1;
    }
    /* LCOV_EXCL_STOP */

    /* UDP Header (8) */
    syn_poke_u16(src_port, tx_out, 34);
    syn_poke_u16(dst_port, tx_out, 36);
    syn_poke_u16((uint16_t)(8 + data_len), tx_out, 38);
    syn_poke_u16(0x0000, tx_out, 40);

    if (data && data_len > 0) {
        memcpy(&tx_out[42], data, data_len);
    }

    uint16_t csum = syn_udp_checksum(src_ip, dst_ip, &tx_out[34], 8 + data_len);
    syn_poke_u16(csum, tx_out, 40);

    *tx_len = total_len;
    return (int)data_len;
    return (int)data_len;
}