/* SPDX-License-Identifier: GPL-3.0-only */
#include "htp_packet_bridge.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>

static void ipv4_packet(uint8_t *packet, uint32_t source, uint32_t destination) {
    memset(packet, 0, 20U);
    packet[0] = 0x45U;
    packet[2] = 0U;
    packet[3] = 20U;
    packet[8] = 64U;
    packet[9] = 17U;
    packet[12] = (uint8_t)(source >> 24U);
    packet[13] = (uint8_t)(source >> 16U);
    packet[14] = (uint8_t)(source >> 8U);
    packet[15] = (uint8_t)source;
    packet[16] = (uint8_t)(destination >> 24U);
    packet[17] = (uint8_t)(destination >> 16U);
    packet[18] = (uint8_t)(destination >> 8U);
    packet[19] = (uint8_t)destination;
}

static void test_ipv4_round_trip_and_learning(void) {
    const uint8_t local_mac[6] = {0x02U, 0x48U, 0x54U, 0x00U, 0x00U, 0x02U};
    const uint8_t peer_mac[6] = {0x02U, 0x48U, 0x54U, 0x00U, 0x00U, 0x01U};
    htp_packet_bridge bridge;
    htp_packet_bridge_init(&bridge, 0x0afd0002U, local_mac);

    uint8_t remote_packet[20];
    ipv4_packet(remote_packet, 0x0afd0001U, 0x0afd0002U);
    uint8_t incoming[34] = {0};
    memcpy(incoming, local_mac, 6U);
    memcpy(incoming + 6U, peer_mac, 6U);
    incoming[12] = 0x08U;
    incoming[13] = 0x00U;
    memcpy(incoming + 14U, remote_packet, sizeof(remote_packet));

    uint8_t decoded[64];
    size_t decoded_length = 0U;
    assert(htp_packet_bridge_ethernet_to_ip(&bridge, incoming, sizeof(incoming), decoded, sizeof(decoded),
        &decoded_length, NULL, 0U, NULL) == HTP_BRIDGE_IP_PACKET);
    assert(decoded_length == sizeof(remote_packet));
    assert(memcmp(decoded, remote_packet, sizeof(remote_packet)) == 0);

    uint8_t outbound[20];
    ipv4_packet(outbound, 0x0afd0002U, 0x0afd0001U);
    uint8_t frame[64];
    assert(htp_packet_bridge_ip_to_ethernet(&bridge, outbound, sizeof(outbound), frame, sizeof(frame)) == 34);
    assert(memcmp(frame, peer_mac, 6U) == 0);
    assert(memcmp(frame + 6U, local_mac, 6U) == 0);
}

static void test_strips_ethernet_padding(void) {
    const uint8_t local_mac[6] = {0x02U, 0x48U, 0x54U, 0x00U, 0x00U, 0x02U};
    const uint8_t peer_mac[6] = {0x02U, 0x48U, 0x54U, 0x00U, 0x00U, 0x01U};
    htp_packet_bridge bridge;
    htp_packet_bridge_init(&bridge, 0x0afd0002U, local_mac);

    uint8_t packet[20];
    ipv4_packet(packet, 0x0afd0001U, 0x0afd0002U);
    uint8_t padded_frame[60] = {0};
    memcpy(padded_frame, local_mac, 6U);
    memcpy(padded_frame + 6U, peer_mac, 6U);
    padded_frame[12] = 0x08U;
    padded_frame[13] = 0x00U;
    memcpy(padded_frame + 14U, packet, sizeof(packet));

    uint8_t decoded[64];
    size_t decoded_length = 0U;
    assert(htp_packet_bridge_ethernet_to_ip(&bridge, padded_frame, sizeof(padded_frame), decoded,
        sizeof(decoded), &decoded_length, NULL, 0U, NULL) == HTP_BRIDGE_IP_PACKET);
    assert(decoded_length == sizeof(packet));
    assert(memcmp(decoded, packet, sizeof(packet)) == 0);
}

static void test_arp_reply(void) {
    const uint8_t local_mac[6] = {0x02U, 0x48U, 0x54U, 0U, 0U, 2U};
    const uint8_t peer_mac[6] = {0x02U, 0x48U, 0x54U, 0U, 0U, 1U};
    htp_packet_bridge bridge;
    htp_packet_bridge_init(&bridge, 0x0afd0002U, local_mac);
    uint8_t request[42] = {0};
    memset(request, 0xff, 6U);
    memcpy(request + 6U, peer_mac, 6U);
    request[12] = 0x08U;
    request[13] = 0x06U;
    request[14] = 0U; request[15] = 1U;
    request[16] = 0x08U; request[17] = 0U;
    request[18] = 6U; request[19] = 4U;
    request[20] = 0U; request[21] = 1U;
    memcpy(request + 22U, peer_mac, 6U);
    request[28] = 10U; request[29] = 253U; request[30] = 0U; request[31] = 1U;
    request[38] = 10U; request[39] = 253U; request[40] = 0U; request[41] = 2U;
    uint8_t reply[64];
    size_t reply_length = 0U;
    assert(htp_packet_bridge_ethernet_to_ip(&bridge, request, sizeof(request), NULL, 0U, NULL,
        reply, sizeof(reply), &reply_length) == HTP_BRIDGE_CONTROL_REPLY);
    assert(reply_length == 42U);
    assert(memcmp(reply, peer_mac, 6U) == 0);
    assert(reply[20] == 0U && reply[21] == 2U);
    assert(memcmp(reply + 22U, local_mac, 6U) == 0);
}

static void test_arp_resolution_before_first_ip_packet(void) {
    const uint32_t local_ip = 0x0afd0002U;
    const uint32_t peer_ip = 0x0afd0001U;
    const uint8_t local_mac[6] = {0x02U, 0x48U, 0x54U, 0U, 0U, 2U};
    const uint8_t peer_mac[6] = {0x02U, 0x48U, 0x54U, 0U, 0U, 1U};
    htp_packet_bridge bridge;
    htp_packet_bridge_init(&bridge, local_ip, local_mac);

    uint8_t request[42];
    assert(htp_packet_bridge_build_arp_request(
        &bridge, peer_ip, request, sizeof(request)) == (int)sizeof(request));
    const uint8_t broadcast[6] = {0xffU, 0xffU, 0xffU, 0xffU, 0xffU, 0xffU};
    assert(memcmp(request, broadcast, sizeof(broadcast)) == 0);
    assert(memcmp(request + 6U, local_mac, sizeof(local_mac)) == 0);
    assert(request[12] == 0x08U && request[13] == 0x06U);
    assert(request[20] == 0U && request[21] == 1U);
    assert(memcmp(request + 22U, local_mac, sizeof(local_mac)) == 0);
    assert(request[28] == 10U && request[29] == 253U && request[30] == 0U && request[31] == 2U);
    assert(request[38] == 10U && request[39] == 253U && request[40] == 0U && request[41] == 1U);
    assert(htp_packet_bridge_has_peer(&bridge, peer_ip) == 0);

    uint8_t reply[42] = {0};
    memcpy(reply, local_mac, sizeof(local_mac));
    memcpy(reply + 6U, peer_mac, sizeof(peer_mac));
    reply[12] = 0x08U; reply[13] = 0x06U;
    reply[14] = 0U; reply[15] = 1U;
    reply[16] = 0x08U; reply[17] = 0U;
    reply[18] = 6U; reply[19] = 4U;
    reply[20] = 0U; reply[21] = 2U;
    memcpy(reply + 22U, peer_mac, sizeof(peer_mac));
    reply[28] = 10U; reply[29] = 253U; reply[30] = 0U; reply[31] = 1U;
    memcpy(reply + 32U, local_mac, sizeof(local_mac));
    reply[38] = 10U; reply[39] = 253U; reply[40] = 0U; reply[41] = 2U;
    assert(htp_packet_bridge_ethernet_to_ip(
        &bridge, reply, sizeof(reply), NULL, 0U, NULL, NULL, 0U, NULL) == HTP_BRIDGE_IGNORED);
    assert(htp_packet_bridge_has_peer(&bridge, peer_ip) == 1);

    uint8_t packet[20];
    ipv4_packet(packet, local_ip, peer_ip);
    uint8_t frame[64];
    assert(htp_packet_bridge_ip_to_ethernet(
        &bridge, packet, sizeof(packet), frame, sizeof(frame)) == 34);
    assert(memcmp(frame, peer_mac, sizeof(peer_mac)) == 0);
}

static void test_rejects_invalid_packets(void) {
    htp_packet_bridge bridge;
    const uint8_t mac[6] = {2U, 0U, 0U, 0U, 0U, 1U};
    htp_packet_bridge_init(&bridge, 1U, mac);
    uint8_t tiny[8] = {0};
    uint8_t out[64];
    assert(htp_packet_bridge_ip_to_ethernet(&bridge, tiny, sizeof(tiny), out, sizeof(out)) == -1);
    assert(htp_packet_bridge_ethernet_to_ip(&bridge, tiny, sizeof(tiny), out, sizeof(out), NULL,
        NULL, 0U, NULL) == HTP_BRIDGE_ERROR);
}

int main(void) {
    test_ipv4_round_trip_and_learning();
    test_strips_ethernet_padding();
    test_arp_reply();
    test_arp_resolution_before_first_ip_packet();
    test_rejects_invalid_packets();
    puts("packet bridge tests passed");
    return 0;
}
