diff --git a/tests/Makefile.am b/tests/Makefile.am index 8c977f02..fa1fbedc 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -10,6 +10,7 @@ check_PROGRAMS = \ test_etcp_100_packets \ test_pkt_normalizer_etcp \ test_pkt_normalizer_standalone \ + test_etcp_api \ test_ll_queue \ test_ecc_encrypt \ test_intensive_memory_pool \ @@ -118,6 +119,10 @@ test_pkt_normalizer_standalone_SOURCES = test_pkt_normalizer_standalone.c test_pkt_normalizer_standalone_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_pkt_normalizer_standalone_LDADD = $(top_builddir)/src/utun-pkt_normalizer.o $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-etcp_api.o $(TINYCRYPT_OBJS) $(COMMON_LIBS) +test_etcp_api_SOURCES = test_etcp_api.c +test_etcp_api_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source +test_etcp_api_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) + test_ll_queue_SOURCES = test_ll_queue.c test_ll_queue_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib test_ll_queue_LDADD = $(COMMON_LIBS) diff --git a/tests/test_etcp_api.c b/tests/test_etcp_api.c new file mode 100644 index 00000000..405a5552 --- /dev/null +++ b/tests/test_etcp_api.c @@ -0,0 +1,572 @@ +/** + * @file test_etcp_api.c + * @brief Тест ETCP API (etcp_send, etcp_bind) + * + * Аналогичен test_pkt_normalizer_etcp.c, но использует высокоуровневый API: + * - etcp_send() для отправки пакетов + * - etcp_bind() для регистрации обработчика полученных пакетов + */ + +#include +#include +#include +#include +#include + +#include "../src/etcp.h" +#include "../src/etcp_connections.h" +#include "../src/etcp_api.h" +#include "../src/config_parser.h" +#include "../src/utun_instance.h" +#include "../src/routing.h" +#include "../src/tun_if.h" +#include "../src/secure_channel.h" +#include "../src/pkt_normalizer.h" +#include "../lib/u_async.h" +#include "../lib/ll_queue.h" +#include "../lib/debug_config.h" + +#define TEST_TIMEOUT_MS 3000 // 3 second timeout +/* + * NOTE: This test demonstrates ETCP API usage (etcp_send/etcp_bind). + * + * CURRENT LIMITATION: The test may fail because pkt_normalizer packer aggregates + * packets into ~1536 byte chunks, which exceeds ETCP's max payload of 1480 bytes. + * This causes encrypt_send to fail with "error 1" (len > 1480). + * + * The test is functional for demonstrating the API pattern, but full packet + * transfer requires fixing the packer to respect frag_size=mtu-100=1400. + */ + +#define TOTAL_PACKETS 100 // Total packets to send +#define MIN_PACKET_SIZE 10 // Minimum packet size +#define MAX_TEST_PACKET_SIZE 500 // Maximum packet size - packer aggregates packets + +// Packet header size: seq(2) + size(2) + checksum(2) = 6 bytes +#define PACKET_HEADER_SIZE 6 + +// ID пакетов данных (0 = обычные данные по умолчанию) +#define DATA_PACKET_ID 0 + +static struct UTUN_INSTANCE* server_instance = NULL; +static struct UTUN_INSTANCE* client_instance = NULL; +static struct UASYNC* ua = NULL; +static int test_completed = 0; +static void* packet_timeout_id = NULL; + +// ETCP connections +static struct ETCP_CONN* client_conn = NULL; +static struct ETCP_CONN* server_conn = NULL; + +// Test statistics - forward direction (client -> server) +static int packets_sent_fwd = 0; +static int packets_received_fwd = 0; +static int current_packet_seq_fwd = 0; + +// Test statistics - backward direction (server -> client) +static int packets_sent_back = 0; +static int packets_received_back = 0; +static int current_packet_seq_back = 0; + +// Packet sizes for each packet (random) +static int packet_sizes[TOTAL_PACKETS]; + +// Timing variables +static struct timespec start_time_fwd, end_time_fwd; +static struct timespec start_time_back, end_time_back; +static int phase = 0; // 0 = connecting, 1 = forward transfer, 2 = backward transfer + +// Calculate simple checksum (sum of all bytes modulo 65536) +static uint16_t calculate_checksum(const uint8_t* data, int len) { + uint32_t sum = 0; + for (int i = 0; i < len; i++) { + sum += data[i]; + } + return (uint16_t)(sum & 0xFFFF); +} + +// Function to generate packet data with seq, size, checksum and random payload +// Packet format: [id:1][seq:2][size:2][checksum:2][payload:N] +static void generate_packet_data(int seq, uint8_t* buffer, int size) { + // Ensure minimum size for header + if (size < PACKET_HEADER_SIZE + 1) size = PACKET_HEADER_SIZE + 1; + + // Write header: [id][seq_low][seq_high][size_low][size_high][cs_low][cs_high] + buffer[0] = DATA_PACKET_ID; // ID пакета (1 byte) + buffer[1] = (uint8_t)(seq & 0xFF); + buffer[2] = (uint8_t)((seq >> 8) & 0xFF); + buffer[3] = (uint8_t)(size & 0xFF); + buffer[4] = (uint8_t)((size >> 8) & 0xFF); + + // Generate random payload (after header) + int payload_size = size - PACKET_HEADER_SIZE - 1; // -1 для ID + for (int i = 0; i < payload_size; i++) { + buffer[PACKET_HEADER_SIZE + 1 + i] = (uint8_t)(rand() % 256); + } + + // Calculate and write checksum of payload + uint16_t checksum = calculate_checksum(buffer + PACKET_HEADER_SIZE + 1, payload_size); + buffer[5] = (uint8_t)(checksum & 0xFF); + buffer[6] = (uint8_t)((checksum >> 8) & 0xFF); +} + +// Verify packet data integrity +// Packet format: [id:1][seq:2][size:2][checksum:2][payload:N] +static int verify_packet_data(uint8_t* buffer, int size, int expected_seq) { + if (size < PACKET_HEADER_SIZE + 1) return 0; + + // Parse header + uint8_t id = buffer[0]; + int seq = buffer[1] | (buffer[2] << 8); + int pkt_size = buffer[3] | (buffer[4] << 8); + int stored_checksum = buffer[5] | (buffer[6] << 8); + + // Check ID + if (id != DATA_PACKET_ID) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Packet ID mismatch: expected=%d, got=%d", + DATA_PACKET_ID, id); + return 0; + } + + // Check sequence number (strict order) + if (seq != expected_seq) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Packet seq mismatch: expected=%d, got=%d", + expected_seq, seq); + return 0; + } + + // Check size + if (pkt_size != size) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Packet size mismatch: expected=%d, got=%d", + size, pkt_size); + return 0; + } + + // Verify checksum of payload + int payload_size = size - PACKET_HEADER_SIZE - 1; + uint16_t calculated_checksum = calculate_checksum(buffer + PACKET_HEADER_SIZE + 1, payload_size); + if (calculated_checksum != stored_checksum) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Packet checksum mismatch: stored=%d, calculated=%d", + stored_checksum, calculated_checksum); + return 0; + } + + return 1; +} + +// Callback для получения пакетов на сервере (forward direction) +static void server_recv_callback(struct ETCP_CONN* conn, struct ll_entry* entry) { + (void)conn; + + if (!entry || !entry->dgram || entry->len < PACKET_HEADER_SIZE + 1) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Server received invalid packet"); + if (entry) { + queue_entry_free(entry); + queue_dgram_free(entry); + } + return; + } + + // Verify packet with strict sequence checking + if (verify_packet_data(entry->dgram, entry->len, packets_received_fwd)) { + packets_received_fwd++; + } else { + int seq = entry->dgram[1] | (entry->dgram[2] << 8); + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Server packet verification failed, seq=%d, expected=%d", + seq, packets_received_fwd); + } + + // Освобождаем entry и dgram + queue_entry_free(entry); + queue_dgram_free(entry); +} + +// Callback для получения пакетов на клиенте (backward direction) +static void client_recv_callback(struct ETCP_CONN* conn, struct ll_entry* entry) { + (void)conn; + + if (!entry || !entry->dgram || entry->len < PACKET_HEADER_SIZE + 1) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Client received invalid packet"); + if (entry) { + queue_entry_free(entry); + queue_dgram_free(entry); + } + return; + } + + // Verify packet with strict sequence checking + if (verify_packet_data(entry->dgram, entry->len, packets_received_back)) { + packets_received_back++; + } else { + int seq = entry->dgram[1] | (entry->dgram[2] << 8); + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Client packet verification failed, seq=%d, expected=%d", + seq, packets_received_back); + } + + // Освобождаем entry и dgram + queue_entry_free(entry); + queue_dgram_free(entry); +} + +// Check if connection is established and crypto session key is ready +static int is_connection_established(struct UTUN_INSTANCE* inst) { + if (!inst) return 0; + struct ETCP_CONN* conn = inst->connections; + while (conn) { + struct ETCP_LINK* link = conn->links; + while (link) { + // Проверяем что линк инициализирован И session_key установлен (не нулевой) + if (link->initialized && conn->crypto_ctx.initialized) { + // Проверяем что session_key не нулевой (первый байт не 0 или все не 0) + int has_session_key = 0; + for (int i = 0; i < SC_SESSION_KEY_SIZE; i++) { + if (conn->crypto_ctx.session_key[i] != 0) { + has_session_key = 1; + break; + } + } + if (has_session_key) { + return 1; + } + } + link = link->next; + } + conn = conn->next; + } + return 0; +} + +// Get first connection from instance +static struct ETCP_CONN* get_first_connection(struct UTUN_INSTANCE* inst) { + if (!inst) return NULL; + return inst->connections; +} + +// Send packets from client to server (forward direction) via etcp_send +static void send_packets_fwd(void) { + if (!client_conn || packets_sent_fwd >= TOTAL_PACKETS) { + return; + } + + // Start timing on first packet + if (packets_sent_fwd == 0) { + clock_gettime(CLOCK_MONOTONIC, &start_time_fwd); + phase = 1; + printf("Starting forward transfer (client -> server) via etcp_send...\n"); + fflush(stdout); + } + + // Send while we have packets + while (packets_sent_fwd < TOTAL_PACKETS) { + int size = packet_sizes[packets_sent_fwd]; + + // Выделяем entry с dgram через ll_alloc_lldgram + struct ll_entry* entry = ll_alloc_lldgram(size); + if (!entry) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to allocate entry for packet %d", + packets_sent_fwd); + break; + } + + // Заполняем данные пакета + generate_packet_data(current_packet_seq_fwd, entry->dgram, size); + entry->len = size; + + // Отправляем через etcp_send (передаем ownership entry) + int result = etcp_send(client_conn, entry); + if (result != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_send failed for packet %d", + packets_sent_fwd); + // При ошибке освобождаем entry сами + queue_entry_free(entry); + queue_dgram_free(entry); + break; + } + + packets_sent_fwd++; + current_packet_seq_fwd++; + } + + if (packets_sent_fwd >= TOTAL_PACKETS) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "All %d forward packets sent via etcp_send", TOTAL_PACKETS); + } +} + +// Send packets from server to client (backward direction) via etcp_send +static void send_packets_back(void) { + if (!server_conn || packets_sent_back >= TOTAL_PACKETS) { + return; + } + + // Start timing on first packet + if (packets_sent_back == 0) { + clock_gettime(CLOCK_MONOTONIC, &start_time_back); + phase = 2; + printf("Starting backward transfer (server -> client) via etcp_send...\n"); + fflush(stdout); + } + + // Send while we have packets + while (packets_sent_back < TOTAL_PACKETS) { + int size = packet_sizes[packets_sent_back]; + + // Выделяем entry с dgram через ll_alloc_lldgram + struct ll_entry* entry = ll_alloc_lldgram(size); + if (!entry) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to allocate entry for packet %d", + packets_sent_back); + break; + } + + // Заполняем данные пакета + generate_packet_data(current_packet_seq_back, entry->dgram, size); + entry->len = size; + + // Отправляем через etcp_send (передаем ownership entry) + int result = etcp_send(server_conn, entry); + if (result != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_send failed for packet %d", + packets_sent_back); + // При ошибке освобождаем entry сами + queue_entry_free(entry); + queue_dgram_free(entry); + break; + } + + packets_sent_back++; + current_packet_seq_back++; + } + + if (packets_sent_back >= TOTAL_PACKETS) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "All %d backward packets sent via etcp_send", TOTAL_PACKETS); + } +} + +// Calculate time difference in milliseconds +static double time_diff_ms(struct timespec* start, struct timespec* end) { + double seconds = end->tv_sec - start->tv_sec; + double nanoseconds = end->tv_nsec - start->tv_nsec; + return (seconds * 1000.0) + (nanoseconds / 1000000.0); +} + +// Monitor function +static void monitor_and_send(void* arg) { + (void)arg; + + if (test_completed) { + packet_timeout_id = NULL; + return; + } + + static int connection_checked = 0; + static int connection_delay = 0; + + if (!connection_checked) { + int client_established = is_connection_established(client_instance); + int server_established = is_connection_established(server_instance); + printf("Connection check: client=%d, server=%d\n", client_established, server_established); + + if (client_established && server_established) { + // Дополнительная задержка для полной инициализации crypto (как в оригинальном тесте) + connection_delay++; + if (connection_delay < 50) { // Ждем 50 циклов (~500ms) + if (!test_completed) { + packet_timeout_id = uasync_set_timeout(ua, 10, NULL, monitor_and_send); + } + return; + } + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Connection established, starting transmission via etcp_api"); + connection_checked = 1; + + // Получаем соединения + client_conn = get_first_connection(client_instance); + server_conn = get_first_connection(server_instance); + + if (!client_conn || !server_conn) { + printf("Failed to get connections\n"); + test_completed = 2; + return; + } + + printf("Connections ready: client=%p, server=%p\n", + (void*)client_conn, (void*)server_conn); + fflush(stdout); + } + } + + if (connection_checked) { + // Phase 1: Forward transfer (client -> server) + if (packets_sent_fwd < TOTAL_PACKETS || packets_received_fwd < TOTAL_PACKETS) { + send_packets_fwd(); + + // Check if forward phase completed + if (packets_sent_fwd >= TOTAL_PACKETS && packets_received_fwd >= TOTAL_PACKETS) { + if (end_time_fwd.tv_sec == 0) { + clock_gettime(CLOCK_MONOTONIC, &end_time_fwd); + double duration = time_diff_ms(&start_time_fwd, &end_time_fwd); + printf("Forward transfer completed: %d/%d packets in %.2f ms\n", + packets_received_fwd, TOTAL_PACKETS, duration); + fflush(stdout); + } + } + } + // Phase 2: Backward transfer (server -> client) + else if (packets_sent_back < TOTAL_PACKETS || packets_received_back < TOTAL_PACKETS) { + send_packets_back(); + } + // Check completion + else { + clock_gettime(CLOCK_MONOTONIC, &end_time_back); + double duration_back = time_diff_ms(&start_time_back, &end_time_back); + double duration_total = time_diff_ms(&start_time_fwd, &end_time_back); + + printf("Backward transfer completed: %d/%d packets in %.2f ms\n", + packets_received_back, TOTAL_PACKETS, duration_back); + + test_completed = 1; + printf("\n=== SUCCESS: Bidirectional transfer via etcp_api completed! ===\n"); + printf("Forward (client->server): %d/%d packets in %.2f ms\n", + packets_received_fwd, TOTAL_PACKETS, time_diff_ms(&start_time_fwd, &end_time_fwd)); + printf("Backward (server->client): %d/%d packets in %.2f ms\n", + packets_received_back, TOTAL_PACKETS, duration_back); + printf("Total time: %.2f ms\n", duration_total); + + if (packet_timeout_id) { + uasync_cancel_timeout(ua, packet_timeout_id); + packet_timeout_id = NULL; + } + return; + } + } + + if (!test_completed) { + packet_timeout_id = uasync_set_timeout(ua, 10, NULL, monitor_and_send); + } +} + +// Timeout handler +static void test_timeout(void* arg) { + (void)arg; + if (!test_completed) { + printf("\n=== TIMEOUT ===\n"); + printf("Forward: Sent: %d/%d, Received: %d/%d\n", + packets_sent_fwd, TOTAL_PACKETS, packets_received_fwd, TOTAL_PACKETS); + printf("Backward: Sent: %d/%d, Received: %d/%d\n", + packets_sent_back, TOTAL_PACKETS, packets_received_back, TOTAL_PACKETS); + test_completed = 2; + if (packet_timeout_id) { + uasync_cancel_timeout(server_instance->ua, packet_timeout_id); + packet_timeout_id = NULL; + } + } +} + +int main() { + printf("=== ETCP API Test (etcp_send, etcp_bind) ===\n"); + printf("Testing with %d packets of random sizes (%d-%d bytes)\n\n", + TOTAL_PACKETS, MIN_PACKET_SIZE, MAX_TEST_PACKET_SIZE); + + // Generate random packet sizes + srand((unsigned)time(NULL)); + int total_bytes = 0; + for (int i = 0; i < TOTAL_PACKETS; i++) { + packet_sizes[i] = MIN_PACKET_SIZE + rand() % (MAX_TEST_PACKET_SIZE - MIN_PACKET_SIZE + 1); + total_bytes += packet_sizes[i]; + } + printf("Total data to transfer: %d bytes (%.2f KB average per packet)\n\n", + total_bytes, (float)total_bytes / TOTAL_PACKETS / 1024); + + debug_config_init(); +// debug_set_level(DEBUG_LEVEL_WARN); + debug_set_level(DEBUG_LEVEL_TRACE); + debug_set_categories(DEBUG_CATEGORY_ALL); + + utun_instance_set_tun_init_enabled(0); + + // Инициализация ETCP API + etcp_api_init(); + + // Регистрируем callbacks для получения пакетов + if (etcp_bind(DATA_PACKET_ID, server_recv_callback) != 0) { + printf("Failed to bind server callback\n"); + etcp_api_deinit(); + return 1; + } + + if (etcp_bind(DATA_PACKET_ID, client_recv_callback) != 0) { + printf("Failed to bind client callback\n"); + etcp_unbind(DATA_PACKET_ID); + etcp_api_deinit(); + return 1; + } + + printf("Callbacks registered for ID=%d\n", DATA_PACKET_ID); + + printf("Creating server...\n"); + ua = uasync_create(); + server_instance = utun_instance_create(ua, "test_pkt_normalizer_etcp_server.conf"); + if (!server_instance || init_connections(server_instance) < 0) { + printf("Failed to create server\n"); + etcp_unbind(DATA_PACKET_ID); + etcp_api_deinit(); + return 1; + } + printf("Server created, waiting for connection...\n\n"); + + printf("Creating client...\n"); + client_instance = utun_instance_create(ua, "test_pkt_normalizer_etcp_client.conf"); + if (!client_instance || init_connections(client_instance) < 0) { + printf("Failed to create client\n"); + etcp_unbind(DATA_PACKET_ID); + etcp_api_deinit(); + return 1; + } + printf("Client created\n\n"); + + printf("Sending %d packets in each direction via etcp_send...\n", TOTAL_PACKETS); + packet_timeout_id = uasync_set_timeout(ua, 1000, NULL, monitor_and_send); + void* global_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS*10, NULL, test_timeout); + + while (!test_completed) { + uasync_poll(ua, 100); // 100ms timeout to allow timer processing + } + + printf("\nCleaning up...\n"); + + if (packet_timeout_id) uasync_cancel_timeout(ua, packet_timeout_id); + if (global_timeout_id) uasync_cancel_timeout(ua, global_timeout_id); + + // Отписываемся от callbacks + etcp_unbind(DATA_PACKET_ID); + + // Деинициализация ETCP API + etcp_api_deinit(); + + if (server_instance) { + server_instance->running = 0; + utun_instance_destroy(server_instance); + } + if (client_instance) { + client_instance->running = 0; + utun_instance_destroy(client_instance); + } + + // Destroy shared uasync instance after both instances are destroyed + if (ua) { + uasync_destroy(ua, 0); + ua = NULL; + } + + if (test_completed == 1) { + printf("\n=== TEST PASSED ===\n"); + printf("All %d packets transmitted in each direction via etcp_api\n", TOTAL_PACKETS); + return 0; + } else { + printf("\n=== TEST FAILED ===\n"); + printf("Forward: Sent: %d/%d, Received: %d/%d\n", + packets_sent_fwd, TOTAL_PACKETS, packets_received_fwd, TOTAL_PACKETS); + printf("Backward: Sent: %d/%d, Received: %d/%d\n", + packets_sent_back, TOTAL_PACKETS, packets_received_back, TOTAL_PACKETS); + return 1; + } +}