Skip to content

File syn_eth.c

File List > net > syn_eth.c

Go to the documentation of this file

#include "syntropic/net/syn_eth.h"

#include "syntropic/net/syn_icmp.h"
#include "syntropic/net/syn_tcp.h"
#include "syntropic/net/syn_transport_udp.h"
#include "syntropic/net/syn_udp.h"
#include "syntropic/port/syn_port_system.h"
#include "syntropic/util/syn_crc.h"

#include <string.h>

/* LCOV_EXCL_START: Defensive bounds check / hardware port fallback */
SYN_WEAK SYN_UDP *syn_transport_udp_get_instance(void)
{
    return NULL;
}

SYN_WEAK struct SYN_ICMP *syn_eth_get_icmp_instance(void)
{
    return NULL;
}

SYN_WEAK struct SYN_TCP *syn_eth_get_tcp_instance(void)
{
    return NULL;
}
/* LCOV_EXCL_STOP */

static const uint8_t MAC_BROADCAST[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

SYN_Status syn_eth_generate_mac(const void *uid_bytes, size_t uid_len, uint8_t mac_out[6])
{
    if (!uid_bytes || uid_len == 0 || !mac_out) {
        return SYN_INVALID_PARAM;
    }

    uint32_t hash = syn_crc32(uid_bytes, uid_len);

    mac_out[0] = 0x02; /* Locally Administered Unicast MAC */
    mac_out[1] = (uint8_t)(hash >> 24);
    mac_out[2] = (uint8_t)(hash >> 16);
    mac_out[3] = (uint8_t)(hash >> 8);
    mac_out[4] = (uint8_t)(hash);
    mac_out[5] = (uint8_t)((hash >> 24) ^ (hash & 0xFF));

    return SYN_OK;
}

SYN_Status syn_eth_init(SYN_ETH *eth, const uint8_t mac_addr[6], uint32_t ip_addr)
{
    if (!eth || !mac_addr) {
        return SYN_INVALID_PARAM;
    }
    memset(eth, 0, sizeof(*eth));
    memcpy(eth->mac_addr, mac_addr, 6);
    eth->ip_addr = ip_addr;
    return SYN_OK;
}

SYN_Status syn_eth_arp_update(SYN_ETH *eth, uint32_t ip, const uint8_t mac[6])
{
    if (!eth || !mac || ip == 0) {
        return SYN_INVALID_PARAM;
    }

    uint32_t now = syn_port_get_tick_ms();

    /* Search for existing entry to update */
    for (size_t i = 0; i < SYN_ETH_ARP_CACHE_SIZE; i++) {
        if (eth->arp_cache[i].valid && eth->arp_cache[i].ip == ip) {
            memcpy(eth->arp_cache[i].mac, mac, 6);
            eth->arp_cache[i].last_seen_ms = now;
            return SYN_OK;
        }
    }

    /* Insert into first empty entry */
    for (size_t i = 0; i < SYN_ETH_ARP_CACHE_SIZE; i++) {
        if (!eth->arp_cache[i].valid) {
            eth->arp_cache[i].ip = ip;
            memcpy(eth->arp_cache[i].mac, mac, 6);
            eth->arp_cache[i].valid = true;
            eth->arp_cache[i].last_seen_ms = now;
            return SYN_OK;
        }
    }

    /* Cache full: evict the least-recently-used entry */
    size_t lru_idx = 0;
    for (size_t i = 1; i < SYN_ETH_ARP_CACHE_SIZE; i++) {
        if ((int32_t)(eth->arp_cache[i].last_seen_ms - eth->arp_cache[lru_idx].last_seen_ms) < 0) {
            lru_idx = i;
        }
    }
    eth->arp_cache[lru_idx].ip = ip;
    memcpy(eth->arp_cache[lru_idx].mac, mac, 6);
    eth->arp_cache[lru_idx].valid = true;
    eth->arp_cache[lru_idx].last_seen_ms = now;

    return SYN_OK;
}

SYN_Status syn_eth_arp_lookup(SYN_ETH *eth, uint32_t ip, uint8_t mac_out[6])
{
    if (!eth || !mac_out) {
        return SYN_INVALID_PARAM;
    }

    for (size_t i = 0; i < SYN_ETH_ARP_CACHE_SIZE; i++) {
        if (eth->arp_cache[i].valid && eth->arp_cache[i].ip == ip) {
            memcpy(mac_out, eth->arp_cache[i].mac, 6);
            /* Refresh LRU timestamp on each successful lookup */
            eth->arp_cache[i].last_seen_ms = syn_port_get_tick_ms();
            return SYN_OK;
        }
    }

    return SYN_NOT_FOUND;
}

SYN_Status syn_eth_build_frame(SYN_ETH *eth, const uint8_t dst_mac[6], uint16_t ethertype,
                               const uint8_t *payload, size_t payload_len, uint8_t *frame_out,
                               size_t *frame_len)
{
    if (!eth || !dst_mac || !frame_out || !frame_len) {
        return SYN_INVALID_PARAM;
    }

    size_t total_len = SYN_ETH_HEADER_LEN + payload_len;
    if (total_len < SYN_ETH_MIN_FRAME_LEN) {
        total_len = SYN_ETH_MIN_FRAME_LEN;
    }
    if (total_len > SYN_ETH_MAX_FRAME_LEN) {
        return SYN_INVALID_PARAM;
    }

    memcpy(&frame_out[0], dst_mac, 6);
    memcpy(&frame_out[6], eth->mac_addr, 6);
    frame_out[12] = (uint8_t)((ethertype >> 8) & 0xFF);
    frame_out[13] = (uint8_t)(ethertype & 0xFF);

    if (payload && payload_len > 0) {
        memcpy(&frame_out[14], payload, payload_len);
    }

    /* Zero pad frame to 60 bytes minimum */
    if (total_len > SYN_ETH_HEADER_LEN + payload_len) {
        memset(&frame_out[SYN_ETH_HEADER_LEN + payload_len], 0,
               total_len - (SYN_ETH_HEADER_LEN + payload_len));
    }

    eth->frames_tx++;
    *frame_len = total_len;

    return SYN_OK;
}

SYN_Status syn_eth_process_frame(SYN_ETH *eth, const uint8_t *frame, size_t len, uint8_t *tx_buf,
                                 size_t *tx_len)
{
    if (!eth || !frame || len < SYN_ETH_HEADER_LEN) {
        return SYN_INVALID_PARAM;
    }

    if (tx_len) {
        *tx_len = 0;
    }

    eth->frames_rx++;

    const uint8_t *dst_mac = &frame[0];
    uint16_t ethertype = ((uint16_t)frame[12] << 8) | frame[13];

    /* MAC Filter: drop frames not addressed to us or broadcast */
    bool is_unicast = (memcmp(dst_mac, eth->mac_addr, 6) == 0);
    bool is_bcast = (memcmp(dst_mac, MAC_BROADCAST, 6) == 0);
    if (!is_unicast && !is_bcast) {
        return SYN_OK;
    }

    /* Process ARP */
    if (ethertype == SYN_ETHTYPE_ARP && len >= 42) {
        uint16_t htype = ((uint16_t)frame[14] << 8) | frame[15];
        uint16_t ptype = ((uint16_t)frame[16] << 8) | frame[17];
        uint16_t oper = ((uint16_t)frame[20] << 8) | frame[21];

        if (htype == 1 && ptype == SYN_ETHTYPE_IPV4) {
            const uint8_t *sender_mac = &frame[22];
            uint32_t sender_ip = ((uint32_t)frame[28] << 24) | ((uint32_t)frame[29] << 16) |
                                 ((uint32_t)frame[30] << 8) | frame[31];
            uint32_t target_ip = ((uint32_t)frame[38] << 24) | ((uint32_t)frame[39] << 16) |
                                 ((uint32_t)frame[40] << 8) | frame[41];

            syn_eth_arp_update(eth, sender_ip, sender_mac);

            if (oper == SYN_ARP_OP_REQUEST) {
                eth->arp_requests++;
                if (target_ip == eth->ip_addr && tx_buf && tx_len) {
                    /* Build ARP Reply Payload (28 bytes) */
                    uint8_t arp_reply_payload[28];
                    arp_reply_payload[0] = 0;
                    arp_reply_payload[1] = 1; /* HW = Ethernet */
                    arp_reply_payload[2] = 0x08;
                    arp_reply_payload[3] = 0x00; /* Proto = IPv4 */
                    arp_reply_payload[4] = 6;
                    arp_reply_payload[5] = 4; /* HW len, Proto len */
                    arp_reply_payload[6] = 0;
                    arp_reply_payload[7] = 2; /* Oper = Reply */

                    memcpy(&arp_reply_payload[8], eth->mac_addr, 6);
                    arp_reply_payload[14] = (uint8_t)(eth->ip_addr >> 24);
                    arp_reply_payload[15] = (uint8_t)(eth->ip_addr >> 16);
                    arp_reply_payload[16] = (uint8_t)(eth->ip_addr >> 8);
                    arp_reply_payload[17] = (uint8_t)(eth->ip_addr);

                    memcpy(&arp_reply_payload[18], sender_mac, 6);
                    arp_reply_payload[24] = (uint8_t)(sender_ip >> 24);
                    arp_reply_payload[25] = (uint8_t)(sender_ip >> 16);
                    arp_reply_payload[26] = (uint8_t)(sender_ip >> 8);
                    arp_reply_payload[27] = (uint8_t)(sender_ip);

                    syn_eth_build_frame(eth, sender_mac, SYN_ETHTYPE_ARP, arp_reply_payload, 28,
                                        tx_buf, tx_len);
                }
            } else if (oper == SYN_ARP_OP_REPLY) {
                eth->arp_replies++;
            }
        }
    } else if (ethertype == SYN_ETHTYPE_IPV4 && len >= 34) {
        uint8_t ip_proto = frame[23];

        if (ip_proto == 17) { /* UDP */
            SYN_UDP *udp_inst = syn_transport_udp_get_instance();
            if (udp_inst) {
                syn_udp_process_packet(udp_inst, frame, len);
            }
        } else if (ip_proto == 1 && tx_buf && tx_len) { /* ICMP */
            SYN_ICMP *icmp_inst = (SYN_ICMP *)syn_eth_get_icmp_instance();
            if (icmp_inst) {
                syn_icmp_process_packet(icmp_inst, frame, len, tx_buf, tx_len);
            }
        } else if (ip_proto == 6 && tx_buf && tx_len) { /* TCP */
            SYN_TCP *tcp_inst = (SYN_TCP *)syn_eth_get_tcp_instance();
            if (tcp_inst) {
                syn_tcp_process_packet(tcp_inst, frame, len, tx_buf, tx_len);
            }
        }
    }

    return SYN_OK;
}

uint16_t syn_ip_checksum(const void *buf, size_t len)
{
    if (!buf || len == 0) {
        /* LCOV_EXCL_START: Invalid param guard */
        return 0;
        /* LCOV_EXCL_STOP */
    }
    const uint8_t *ptr = (const uint8_t *)buf;
    uint32_t sum = 0;

    while (len > 1) {
        sum += ((uint16_t)ptr[0] << 8) | ptr[1];
        ptr += 2;
        len -= 2;
    }

    if (len == 1) {
        sum += (uint16_t)ptr[0] << 8;
    }

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

    return (uint16_t)(~sum);
}

size_t syn_eth_pack_header(uint8_t *buf, size_t buf_size, const uint8_t dst_mac[6],
                           const uint8_t src_mac[6], uint16_t ethertype)
{
    if (!buf || buf_size < SYN_ETH_HEADER_LEN || !dst_mac || !src_mac) {
        /* LCOV_EXCL_START: Invalid param guard */
        return 0;
        /* LCOV_EXCL_STOP */
    }

    memcpy(&buf[0], dst_mac, 6);
    memcpy(&buf[6], src_mac, 6);
    buf[12] = (uint8_t)(ethertype >> 8);
    buf[13] = (uint8_t)(ethertype & 0xFF);

    return SYN_ETH_HEADER_LEN;
}

size_t syn_ip_pack_header(uint8_t *buf, size_t buf_size, uint32_t src_ip, uint32_t dst_ip,
                          uint8_t proto, uint16_t payload_len, uint16_t id)
{
    if (!buf || buf_size < 20U) {
        /* LCOV_EXCL_START: Invalid param guard */
        return 0;
        /* LCOV_EXCL_STOP */
    }

    uint16_t total_ip_len = (uint16_t)(20U + payload_len);

    buf[0] = 0x45; /* Version 4, IHL 5 */
    buf[1] = 0x00; /* TOS */
    buf[2] = (uint8_t)(total_ip_len >> 8);
    buf[3] = (uint8_t)(total_ip_len & 0xFF);
    buf[4] = (uint8_t)(id >> 8);
    buf[5] = (uint8_t)(id & 0xFF);
    buf[6] = 0x40; /* Flags: Don't Fragment */
    buf[7] = 0x00; /* Fragment Offset */
    buf[8] = 64;   /* TTL = 64 */
    buf[9] = proto;
    buf[10] = 0; /* IP Checksum placeholder */
    buf[11] = 0;

    buf[12] = (uint8_t)(src_ip >> 24);
    buf[13] = (uint8_t)(src_ip >> 16);
    buf[14] = (uint8_t)(src_ip >> 8);
    buf[15] = (uint8_t)(src_ip);

    buf[16] = (uint8_t)(dst_ip >> 24);
    buf[17] = (uint8_t)(dst_ip >> 16);
    buf[18] = (uint8_t)(dst_ip >> 8);
    buf[19] = (uint8_t)(dst_ip);

    uint16_t csum = syn_ip_checksum(buf, 20);
    buf[10] = (uint8_t)(csum >> 8);
    buf[11] = (uint8_t)(csum & 0xFF);

    return 20U;
}