/*
 * ==================================================================================
 * Network Interface - ICMP packet handling and raw socket operations
 *
 * ISA Project - ICMP Covert Channel File Transfer System
 * Author: Roman Necas (xnecasr00)
 * Date: 13. October 2025
 *
 * DESCRIPTION:
 * This module provides low-level network operations for ICMP-based communication.
 * It abstracts raw socket creation, packet construction, and transmission/reception
 * for both IPv4 (ICMP) and IPv6 (ICMPv6) protocols.
 *
 * KEY FUNCTIONS:
 * - network_init() - Initialize network interface with raw sockets
 * - network_send_packet() - Send ICMP Echo Request with payload
 * - network_recv_packet() - Receive ICMP packets (blocking)
 * - network_recv_packet_nonblocking() - Poll for ICMP packets (non-blocking)
 * - network_send_reply() - Send ICMP Echo Reply (server response)
 * - network_cleanup() - Close sockets and release resources
 *
 * DUAL-STACK IPv4/IPv6 SUPPORT:
 * - Client mode: Creates single socket based on destination address family
 * - Server mode: Creates TWO sockets (IPv4 + IPv6) for dual-stack support
 * - Uses select() to poll both sockets simultaneously in server mode
 * - Automatically handles IPv4-mapped IPv6 addresses
 *
 * RAW SOCKET OPERATIONS:
 * - Uses SOCK_RAW with IPPROTO_ICMP (IPv4) or IPPROTO_ICMPV6 (IPv6)
 * - Requires CAP_NET_RAW capability (typically requires root privileges)
 * - Receives IP header + ICMP header + payload (IPv4)
 * - Receives ICMP header + payload only (IPv6, kernel strips IP header)
 * - Must manually calculate ICMP checksum for IPv4
 * - Kernel automatically calculates checksum for IPv6 ICMP Echo Reply
 *
 * ICMP PACKET STRUCTURE:
 * IPv4 ICMP Echo Request/Reply:
 *   [IP Header (20 bytes)] - Received but not sent (kernel adds it)
 *   [ICMP Header (8 bytes)]:
 *     - Type: ICMP_ECHO (8) or ICMP_ECHOREPLY (0)
 *     - Code: 0
 *     - Checksum: Internet checksum (RFC 1071)
 *     - Identifier: Process ID
 *     - Sequence: Packet sequence number
 *   [Payload]: IFTP protocol data
 *
 * CLIENT/SERVER PACKET FILTERING:
 * - Client: Only processes ICMP_ECHOREPLY (0) / ICMP6_ECHO_REPLY (129)
 * - Server: Only processes ICMP_ECHO (8) / ICMP6_ECHO_REQUEST (128)
 * - Filters out ICMP error messages (DEST_UNREACH, TIME_EXCEEDED)
 *
 * ADDRESS RESOLUTION:
 * - Uses getaddrinfo() for hostname → IP address resolution
 * - Supports IPv4/IPv6 addresses and hostnames
 * - Automatically selects appropriate socket type
 *
 * CHECKSUM CALCULATION:
 * - RFC 1071 Internet Checksum algorithm
 * - IPv4: Must calculate checksum before sending
 * - IPv6: Kernel calculates checksum automatically
 *
 * MTU CONSIDERATIONS:
 * - Standard Ethernet MTU: 1500 bytes
 * - IPv4: 1500 - 20 (IP) - 8 (ICMP) = 1472 bytes available
 * - IPv6: 1500 - 40 (IPv6) - 8 (ICMPv6) = 1452 bytes available
 * - IFTP uses MAX_PAYLOAD_SIZE = 1400 bytes (conservative)
 *
 * ERROR HANDLING:
 * - Returns -1 on socket errors (creation, send, receive failures)
 * - Returns -2 on ICMP network errors (DEST_UNREACH, TIME_EXCEEDED)
 * - Returns 0 on timeout or filtered packets
 * - Returns >0 (payload length) on successful packet reception
 * ==================================================================================
 */

#include "secret.h"

/* Resolve hostname to a sockaddr_storage using getaddrinfo()
 * - Supports IPv4 and IPv6
 * - Caller must handle returned family (AF_INET/AF_INET6)
 */
static int resolve_address(const char *hostname, struct sockaddr_storage *addr, socklen_t *addr_len) {
    struct addrinfo hints, *result;
    int ret;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_RAW;

    ret = getaddrinfo(hostname, NULL, &hints, &result);
    if (ret != 0) {
        fprintf(stderr, "Error resolving hostname '%s': %s\n", hostname, gai_strerror(ret));
        return -1;
    }

    memcpy(addr, result->ai_addr, result->ai_addrlen);
    *addr_len = result->ai_addrlen;

    freeaddrinfo(result);
    return addr->ss_family;
}

/* Initialize network interface:
 * - Client mode: resolve dest_address and create single raw socket
 * - Server mode: create dual-stack sockets (IPv4 + IPv6) for listening
 * - Note: raw sockets require elevated privileges (CAP_NET_RAW/root)
 */
int network_init(network_interface_t *net, const char *dest_address) {
    if (!net) {
        print_error("Invalid network interface structure");
        return -1;
    }

    memset(net, 0, sizeof(network_interface_t));
    net->sockfd = -1;
    net->sockfd_v6 = -1;

    if (dest_address) {
        /* Client mode: Single socket based on destination address family */
        net->addr_family = resolve_address(dest_address, &net->dest_addr, &net->dest_addr_len);
        if (net->addr_family < 0) {
            return -1;
        }

        int protocol = (net->addr_family == AF_INET6) ? IPPROTO_ICMPV6 : IPPROTO_ICMP;
        net->sockfd = socket(net->addr_family, SOCK_RAW, protocol);
        if (net->sockfd < 0) {
            fprintf(stderr, "Error creating raw socket: %s\n", strerror(errno));
            fprintf(stderr, "Note: Raw sockets require root privileges\n");
            return -1;
        }

        struct timeval timeout = {0};
        timeout.tv_sec = INITIAL_TIMEOUT;
        timeout.tv_usec = 0;
        if (setsockopt(net->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
            fprintf(stderr, "Warning: Could not set socket timeout: %s\n", strerror(errno));
        }
    } else {
        /* Server mode: Dual-stack support - create both IPv4 and IPv6 sockets
         * This allows server to receive both ICMP (IPv4) and ICMPv6 (IPv6) packets
         * Uses select() to poll both sockets simultaneously
         */
        net->addr_family = AF_INET;  /* Primary address family for server */

        /* Create IPv4 ICMP socket */
        net->sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
        if (net->sockfd < 0) {
            fprintf(stderr, "Error creating IPv4 ICMP socket: %s\n", strerror(errno));
            fprintf(stderr, "Note: Raw sockets require root privileges\n");
            return -1;
        }

        /* Create IPv6 ICMPv6 socket */
        net->sockfd_v6 = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
        if (net->sockfd_v6 < 0) {
            fprintf(stderr, "Warning: Could not create IPv6 ICMPv6 socket: %s\n", strerror(errno));
            fprintf(stderr, "Server will only support IPv4 clients\n");
            /* Continue with IPv4 only - not fatal */
        }

        /* Set timeouts on both sockets */
        struct timeval timeout = {0};
        timeout.tv_sec = INITIAL_TIMEOUT;
        timeout.tv_usec = 0;

        if (setsockopt(net->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
            fprintf(stderr, "Warning: Could not set IPv4 socket timeout: %s\n", strerror(errno));
        }

        if (net->sockfd_v6 >= 0) {
            if (setsockopt(net->sockfd_v6, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
                fprintf(stderr, "Warning: Could not set IPv6 socket timeout: %s\n", strerror(errno));
            }
        }
    }

    return 0;
}

/* Helper: safely read 16-bit from possibly unaligned memory */
static inline uint16_t read_unaligned_uint16(const void *ptr) {
    uint16_t value;
    memcpy(&value, ptr, sizeof(uint16_t));
    return value;
}

/* Internet checksum (RFC 1071)
 * - Used for IPv4/ICMPv4 checksum calculation
 * - Handles odd-length buffers
 */
static uint16_t calculate_ip_checksum(const void *data, size_t len) {
    const uint16_t *ip = (const uint16_t *)data;
    uint32_t sum = 0;

    while (len > 1) {
        sum += *ip++;
        len -= 2;
    }

    if (len > 0) {
        sum += *(uint8_t*)ip << 8;
    }

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

    return ~sum;
}

/* Construct ICMP/ICMPv6 Echo Request packet into packet_buffer:
 * - Fills header (type/code/id/seq) and appends payload
 * - For IPv4 computes checksum; for IPv6 kernel usually fills checksum
 */
static int construct_icmp_packet(const void *payload, size_t payload_len,
                               uint8_t *packet_buffer, size_t *packet_len,
                               int addr_family) {
    if (addr_family == AF_INET) {
        struct icmphdr *icmp = (struct icmphdr *)packet_buffer;

        icmp->type = ICMP_ECHO;
        icmp->code = 0;
        icmp->checksum = 0;
        icmp->un.echo.id = htons(getpid() & 0xFFFF);
        icmp->un.echo.sequence = htons(1);

        memcpy(packet_buffer + sizeof(struct icmphdr), payload, payload_len);
        *packet_len = sizeof(struct icmphdr) + payload_len;

        icmp->checksum = calculate_ip_checksum(packet_buffer, *packet_len);
    } else {
        struct icmp6_hdr *icmp6 = (struct icmp6_hdr *)packet_buffer;

        icmp6->icmp6_type = ICMP6_ECHO_REQUEST;
        icmp6->icmp6_code = 0;
        icmp6->icmp6_cksum = 0;
        icmp6->icmp6_id = htons(getpid() & 0xFFFF);
        icmp6->icmp6_seq = htons(1);

        memcpy(packet_buffer + sizeof(struct icmp6_hdr), payload, payload_len);
        *packet_len = sizeof(struct icmp6_hdr) + payload_len;
    }

    return 0;
}

/* Send a constructed ICMP packet using the configured socket
 * - Expects packet constructed for net->addr_family and net->dest_addr set
 */
int network_send_packet(network_interface_t *net, const void *payload, size_t payload_len) {
    if (!net || !payload || net->sockfd < 0) {
        print_error("Invalid parameters for packet sending");
        return -1;
    }

    uint8_t packet_buffer[2048];
    size_t packet_len;

    if (construct_icmp_packet(payload, payload_len, packet_buffer, &packet_len, net->addr_family) < 0) {
        print_error("Failed to construct ICMP packet");
        return -1;
    }

    ssize_t sent = sendto(net->sockfd, packet_buffer, packet_len, 0,
                         (struct sockaddr *)&net->dest_addr, net->dest_addr_len);

    if (sent < 0) {
        fprintf(stderr, "Error sending packet: %s\n", strerror(errno));
        return -1;
    }

    if ((size_t)sent != packet_len) {
        fprintf(stderr, "Warning: Partial packet sent (%zd/%zu bytes)\n", sent, packet_len);
        return -1;
    }

    return 0;
}

/* Parse received packet (IPv4 includes IP header; IPv6 does not):
 * - Validates packet length and type
 * - Filters ICMP error messages (DEST_UNREACH, TIME_EXCEEDED)
 * - Applies client/server filtering (ECHO vs ECHOREPLY)
 * - Extracts payload and source address for caller
 */
static int parse_received_packet(const uint8_t *buffer, size_t buffer_len,
                               uint8_t *payload, size_t *payload_len,
                               struct sockaddr_storage *src_addr, socklen_t *src_addr_len,
                               int addr_family, int is_client,
                               const struct sockaddr_storage *recv_addr, socklen_t recv_addr_len) {
    if (addr_family == AF_INET) {
        if (buffer_len < sizeof(struct iphdr)) {
            return -1;
        }

        struct iphdr *ip = (struct iphdr *)buffer;
        size_t ip_header_len = ip->ihl * 4;

        if (buffer_len < ip_header_len + sizeof(struct icmphdr)) {
            return -1;
        }

        if (ip->protocol != IPPROTO_ICMP) {
            return -1;
        }

        struct icmphdr *icmp = (struct icmphdr *)(buffer + ip_header_len);

        // Handle ICMP error messages
        if (icmp->type == ICMP_DEST_UNREACH || icmp->type == ICMP_TIME_EXCEEDED) {
            fprintf(stderr, "ICMP Error: %s\n",
                   icmp->type == ICMP_DEST_UNREACH ? "Destination Unreachable" : "Time Exceeded");
            return -2;
        }

        // Filter based on client/server role
        if (is_client) {
            // Client should only process ECHO REPLY packets
            if (icmp->type != ICMP_ECHOREPLY) {
                return -1;
            }
        } else {
            // Server should only process ECHO REQUEST packets
            if (icmp->type != ICMP_ECHO) {
                return -1;
            }
        }

        size_t icmp_payload_len = buffer_len - ip_header_len - sizeof(struct icmphdr);
        if (icmp_payload_len > 0 && payload && payload_len) {
            memcpy(payload, buffer + ip_header_len + sizeof(struct icmphdr), icmp_payload_len);
            *payload_len = icmp_payload_len;
        } else if (payload_len) {
            *payload_len = 0;
        }

        if (src_addr && src_addr_len) {
            struct sockaddr_in *sin = (struct sockaddr_in *)src_addr;
            sin->sin_family = AF_INET;
            sin->sin_addr.s_addr = ip->saddr;
            *src_addr_len = sizeof(struct sockaddr_in);
        }

    } else {
        if (buffer_len < sizeof(struct icmp6_hdr)) {
            return -1;
        }

        struct icmp6_hdr *icmp6 = (struct icmp6_hdr *)buffer;

        // Handle ICMPv6 error messages
        if (icmp6->icmp6_type == ICMP6_DST_UNREACH || icmp6->icmp6_type == ICMP6_TIME_EXCEEDED) {
            fprintf(stderr, "ICMPv6 Error: %s\n",
                   icmp6->icmp6_type == ICMP6_DST_UNREACH ? "Destination Unreachable" : "Time Exceeded");
            return -2;
        }

        // Filter based on client/server role
        if (is_client) {
            // Client should only process ECHO REPLY packets
            if (icmp6->icmp6_type != ICMP6_ECHO_REPLY) {
                return -1;
            }
        } else {
            // Server should only process ECHO REQUEST packets
            if (icmp6->icmp6_type != ICMP6_ECHO_REQUEST) {
                return -1;
            }
        }

        size_t icmp_payload_len = buffer_len - sizeof(struct icmp6_hdr);
        if (icmp_payload_len > 0 && payload && payload_len) {
            memcpy(payload, buffer + sizeof(struct icmp6_hdr), icmp_payload_len);
            *payload_len = icmp_payload_len;
        } else if (payload_len) {
            *payload_len = 0;
        }

        /* Extract source IPv6 address from recvfrom() result
         * Note: For ICMPv6, kernel doesn't provide IPv6 header, so we use recv_addr */
        if (src_addr && src_addr_len && recv_addr) {
            memcpy(src_addr, recv_addr, recv_addr_len);
            *src_addr_len = recv_addr_len;
        }
    }

    return 0;
}

/* Blocking receive:
 * - recvfrom() on configured socket
 * - Parses and returns payload length (or special error codes)
 */
int network_recv_packet(network_interface_t *net, void *payload, size_t max_payload_len,
                       struct sockaddr_storage *src_addr, socklen_t *src_addr_len) {
    if (!net || !payload || net->sockfd < 0) {
        print_error("Invalid parameters for packet reception");
        return -1;
    }

    uint8_t buffer[2048];
    struct sockaddr_storage recv_addr;
    socklen_t recv_addr_len = sizeof(recv_addr);

    ssize_t received = recvfrom(net->sockfd, buffer, sizeof(buffer), 0,
                               (struct sockaddr *)&recv_addr, &recv_addr_len);

    if (received < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            return 0;
        }
        fprintf(stderr, "Error receiving packet: %s\n", strerror(errno));
        return -1;
    }

    size_t payload_len;
    // Determine if we're client based on whether dest_address was set
    int is_client = (net->dest_addr_len > 0);

    int parse_result = parse_received_packet(buffer, received, payload, &payload_len,
                                            src_addr, src_addr_len, net->addr_family, is_client,
                                            &recv_addr, recv_addr_len);
    if (parse_result < 0) {
        if (parse_result == -2) {
            return -2;  // Network error
        }
        return 0;  // Filtered packet
    }

    if (payload_len > max_payload_len) {
        print_error("Received payload too large for buffer");
        return -1;
    }

    return payload_len;
}

/* Non-blocking receive for server dual-stack:
 * - If IPv6 socket present, use select() to poll both sockets with zero timeout
 * - Otherwise toggle O_NONBLOCK on the single socket and call blocking recv wrapper
 * - Returns 0 for no data, >0 for payload length, -2 for ICMP network error, -1 for errors
 */
int network_recv_packet_nonblocking(network_interface_t *net, void *payload, size_t max_payload_len,
                                    struct sockaddr_storage *src_addr, socklen_t *src_addr_len) {
    if (!net || !payload || net->sockfd < 0) {
        print_error("Invalid parameters for non-blocking packet reception");
        return -1;
    }

    /* Server mode with dual-stack: Use select() to poll both IPv4 and IPv6 sockets */
    if (net->sockfd_v6 >= 0) {
        fd_set readfds;
        struct timeval timeout;
        int max_fd;

        FD_ZERO(&readfds);
        FD_SET(net->sockfd, &readfds);
        FD_SET(net->sockfd_v6, &readfds);

        max_fd = (net->sockfd > net->sockfd_v6) ? net->sockfd : net->sockfd_v6;

        /* Non-blocking: zero timeout */
        timeout.tv_sec = 0;
        timeout.tv_usec = 0;

        int ready = select(max_fd + 1, &readfds, NULL, NULL, &timeout);

        if (ready < 0) {
            if (errno == EINTR) {
                return 0;  /* Interrupted by signal, not an error */
            }
            fprintf(stderr, "select() error: %s\n", strerror(errno));
            return -1;
        }

        if (ready == 0) {
            /* No packets available on either socket */
            errno = EAGAIN;
            return 0;
        }

        /* Check which socket has data available */
        int active_sockfd;
        int active_family;

        if (FD_ISSET(net->sockfd, &readfds)) {
            /* IPv4 packet available */
            active_sockfd = net->sockfd;
            active_family = AF_INET;
        } else if (FD_ISSET(net->sockfd_v6, &readfds)) {
            /* IPv6 packet available */
            active_sockfd = net->sockfd_v6;
            active_family = AF_INET6;
        } else {
            /* Should not happen */
            errno = EAGAIN;
            return 0;
        }

        /* Receive packet from active socket */
        uint8_t buffer[2048];
        struct sockaddr_storage recv_addr;
        socklen_t recv_addr_len = sizeof(recv_addr);

        ssize_t received = recvfrom(active_sockfd, buffer, sizeof(buffer), 0,
                                   (struct sockaddr *)&recv_addr, &recv_addr_len);

        if (received < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                return 0;
            }
            fprintf(stderr, "Error receiving packet: %s\n", strerror(errno));
            return -1;
        }

        size_t payload_len;
        int is_client = 0;  /* Server mode */

        int parse_result = parse_received_packet(buffer, received, payload, &payload_len,
                                                src_addr, src_addr_len, active_family, is_client,
                                                &recv_addr, recv_addr_len);
        if (parse_result < 0) {
            if (parse_result == -2) {
                return -2;  /* Network error */
            }
            return 0;  /* Filtered packet */
        }

        if (payload_len > max_payload_len) {
            print_error("Received payload too large for buffer");
            return -1;
        }

        return payload_len;
    } else {
        /* Client mode or server with IPv4 only: Use original non-blocking method */
        int flags = fcntl(net->sockfd, F_GETFL, 0);
        if (flags == -1) {
            fprintf(stderr, "Error getting socket flags: %s\n", strerror(errno));
            return -1;
        }

        if (fcntl(net->sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
            fprintf(stderr, "Error setting non-blocking mode: %s\n", strerror(errno));
            return -1;
        }

        int result = network_recv_packet(net, payload, max_payload_len, src_addr, src_addr_len);

        if (fcntl(net->sockfd, F_SETFL, flags) == -1) {
            fprintf(stderr, "Warning: Error restoring socket flags: %s\n", strerror(errno));
        }

        return result;
    }
}

/* Send an ICMP Echo Reply to dest_addr:
 * - Constructs appropriate ICMP/ICMPv6 reply headers
 * - Chooses correct socket (IPv4 vs IPv6) for sending
 */
int network_send_reply(network_interface_t *net, const void *payload, size_t payload_len,
                      const struct sockaddr_storage *dest_addr, socklen_t dest_addr_len) {
    if (!net || !payload || !dest_addr || net->sockfd < 0) {
        print_error("Invalid parameters for reply sending");
        return -1;
    }

    uint8_t packet_buffer[2048];
    size_t packet_len;

    if (dest_addr->ss_family == AF_INET) {
        struct icmphdr *icmp = (struct icmphdr *)packet_buffer;

        icmp->type = ICMP_ECHOREPLY;
        icmp->code = 0;
        icmp->checksum = 0;
        icmp->un.echo.id = htons(getpid() & 0xFFFF);
        icmp->un.echo.sequence = htons(1);

        memcpy(packet_buffer + sizeof(struct icmphdr), payload, payload_len);
        packet_len = sizeof(struct icmphdr) + payload_len;

        icmp->checksum = calculate_ip_checksum(packet_buffer, packet_len);
    } else {
        struct icmp6_hdr *icmp6 = (struct icmp6_hdr *)packet_buffer;

        icmp6->icmp6_type = ICMP6_ECHO_REPLY;
        icmp6->icmp6_code = 0;
        icmp6->icmp6_cksum = 0;
        icmp6->icmp6_id = htons(getpid() & 0xFFFF);
        icmp6->icmp6_seq = htons(1);

        memcpy(packet_buffer + sizeof(struct icmp6_hdr), payload, payload_len);
        packet_len = sizeof(struct icmp6_hdr) + payload_len;

        /* For ICMPv6 checksum, kernel calculates it automatically for ICMP6_ECHO_REPLY
         * on raw sockets when checksum is 0.*/
        icmp6->icmp6_cksum = 0;  /* Let kernel calculate checksum */
    }

    /* Select appropriate socket based on destination address family */
    int send_sockfd;
    if (dest_addr->ss_family == AF_INET) {
        send_sockfd = net->sockfd;
    } else if (dest_addr->ss_family == AF_INET6) {
        send_sockfd = net->sockfd_v6;
        if (send_sockfd < 0) {
            fprintf(stderr, "Error: IPv6 socket not available for reply\n");
            return -1;
        }
    } else {
        fprintf(stderr, "Error: Unsupported address family for reply\n");
        return -1;
    }

    ssize_t sent = sendto(send_sockfd, packet_buffer, packet_len, 0,
                         (struct sockaddr *)dest_addr, dest_addr_len);

    if (sent < 0) {
        fprintf(stderr, "Error sending reply: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}

/* Close sockets and cleanup network interface resources */
void network_cleanup(network_interface_t *net) {
    if (!net) {
        return;
    }

    /* Close IPv4 socket */
    if (net->sockfd >= 0) {
        close(net->sockfd);
        net->sockfd = -1;
    }

    /* Close IPv6 socket (server mode dual-stack) */
    if (net->sockfd_v6 >= 0) {
        close(net->sockfd_v6);
        net->sockfd_v6 = -1;
    }
}

/* Configure send/recv timeouts on primary socket:
 * - Updates net->send_timeout and net->recv_timeout on success
 */
int network_set_timeout(network_interface_t *net, int send_timeout, int recv_timeout) {
    ERROR_CHECK(net != NULL, ERR_INVALID_ARGS, "Network interface cannot be NULL");
    ERROR_CHECK(net->sockfd >= 0, ERR_NETWORK_INIT, "Socket not initialized");

    struct timeval send_tv = {send_timeout, 0};
    struct timeval recv_tv = {recv_timeout, 0};

    if (setsockopt(net->sockfd, SOL_SOCKET, SO_SNDTIMEO, &send_tv, sizeof(send_tv)) < 0) {
        ERROR_SET(ERR_NETWORK_INIT, "Failed to set send timeout");
        return -1;
    }

    if (setsockopt(net->sockfd, SOL_SOCKET, SO_RCVTIMEO, &recv_tv, sizeof(recv_tv)) < 0) {
        ERROR_SET(ERR_NETWORK_INIT, "Failed to set receive timeout");
        return -1;
    }

    net->send_timeout = send_timeout;
    net->recv_timeout = recv_timeout;

    return 0;
}

/* Estimate MTU for ICMP payloads:
 * - Uses conservative defaults subtracting IP/ICMP headers and IFTP header
 */
int network_get_mtu(network_interface_t *net, const char *dest_address) {
    ERROR_CHECK(net != NULL, ERR_INVALID_ARGS, "Network interface cannot be NULL");
    ERROR_CHECK(dest_address != NULL, ERR_INVALID_ARGS, "Destination address cannot be NULL");

    /* For ICMP, we use a conservative default MTU minus headers */
    int mtu = 1500;  /* Standard Ethernet MTU */

    if (net->addr_family == AF_INET) {
        mtu -= sizeof(struct iphdr) + sizeof(struct icmphdr);
    } else {
        mtu -= sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr);
    }

    /* Leave some margin for protocol headers */
    mtu -= sizeof(iftp_header_t);

    return mtu;
}

/* Convert sockaddr_storage to printable string:
 * - Supports IPv4 and IPv6 via inet_ntop
 */
const char *network_addr_to_string(const struct sockaddr_storage *addr, char *buffer, size_t buffer_size) {
    if (addr == NULL) {
        ERROR_SET(ERR_INVALID_ARGS, "Address cannot be NULL");
        return NULL;
    }
    if (buffer == NULL) {
        ERROR_SET(ERR_INVALID_ARGS, "Buffer cannot be NULL");
        return NULL;
    }
    if (buffer_size == 0) {
        ERROR_SET(ERR_INVALID_ARGS, "Buffer size must be positive");
        return NULL;
    }

    if (addr->ss_family == AF_INET) {
        struct sockaddr_in *sin = (struct sockaddr_in *)addr;
        if (inet_ntop(AF_INET, &sin->sin_addr, buffer, buffer_size) == NULL) {
            ERROR_SET(ERR_NETWORK_INIT, "Failed to convert IPv4 address to string");
            return NULL;
        }
    } else if (addr->ss_family == AF_INET6) {
        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
        if (inet_ntop(AF_INET6, &sin6->sin6_addr, buffer, buffer_size) == NULL) {
            ERROR_SET(ERR_NETWORK_INIT, "Failed to convert IPv6 address to string");
            return NULL;
        }
    } else {
        ERROR_SET(ERR_INVALID_ARGS, "Unknown address family");
        return NULL;
    }

    return buffer;
}

/* Compare two socket addresses (IPv4 or IPv6) by their address bytes */
int network_compare_addresses(const struct sockaddr_storage *addr1, const struct sockaddr_storage *addr2) {
    ERROR_CHECK(addr1 != NULL, ERR_INVALID_ARGS, "First address cannot be NULL");
    ERROR_CHECK(addr2 != NULL, ERR_INVALID_ARGS, "Second address cannot be NULL");

    if (addr1->ss_family != addr2->ss_family) {
        return -1;  /* Different address families */
    }

    if (addr1->ss_family == AF_INET) {
        struct sockaddr_in *sin1 = (struct sockaddr_in *)addr1;
        struct sockaddr_in *sin2 = (struct sockaddr_in *)addr2;
        return memcmp(&sin1->sin_addr, &sin2->sin_addr, sizeof(sin1->sin_addr));
    } else if (addr1->ss_family == AF_INET6) {
        struct sockaddr_in6 *sin6_1 = (struct sockaddr_in6 *)addr1;
        struct sockaddr_in6 *sin6_2 = (struct sockaddr_in6 *)addr2;
        return memcmp(&sin6_1->sin6_addr, &sin6_2->sin6_addr, sizeof(sin6_1->sin6_addr));
    }

    return -1;  /* Unknown address family */
}

/* Network statistics helpers:
 * - Initialize, update (sent/received), and print stats
 */
void network_init_stats(network_stats_t *stats) {
    if (stats) {
        memset(stats, 0, sizeof(network_stats_t));
    }
}

void network_update_stats(network_stats_t *stats, int sent, size_t bytes, int retransmit) {
    if (!stats) return;

    if (sent) {
        stats->packets_sent++;
        stats->bytes_sent += bytes;
        if (retransmit) {
            stats->retransmissions++;
        }
    } else {
        stats->packets_received++;
        stats->bytes_received += bytes;
    }
}

void network_print_stats(const network_stats_t *stats) {
    if (!stats) return;

    printf("\n=== Network Statistics ===\n");
    printf("Packets sent:     %lu\n", stats->packets_sent);
    printf("Packets received: %lu\n", stats->packets_received);
    printf("Bytes sent:       %lu\n", stats->bytes_sent);
    printf("Bytes received:   %lu\n", stats->bytes_received);
    printf("Retransmissions:  %lu\n", stats->retransmissions);
    printf("Checksum errors:  %lu\n", stats->checksum_errors);
    printf("Timeouts:         %lu\n", stats->timeouts);
    printf("========================\n");
}

/* ICMP checksum wrapper (same RFC 1071 algorithm)
 * - Provided as public helper for other modules if needed
 */
uint16_t network_calculate_icmp_checksum(const void *data, size_t len) {
    const uint16_t *ip = (const uint16_t *)data;
    uint32_t sum = 0;

    ERROR_CHECK(data != NULL, ERR_INVALID_ARGS, "Data cannot be NULL");

    while (len > 1) {
        sum += *ip++;
        len -= 2;
    }

    if (len > 0) {
        sum += *(uint8_t*)ip << 8;
    }

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

    return ~sum;
}