From d74f76a5a09c6ba62dd7e0f18e61f28e73deae50 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 25 Jan 2026 10:47:02 +0300 Subject: [PATCH] Fix: Resolved double free in timeout_heap_pop() and test_etcp_simple_traffic bind conflict - Fixed incorrect init_connections() call in test_etcp_simple_traffic.c (was calling server_instance instead of client_instance) - Fixed double free in timeout_heap_pop() when handling deleted elements - Enhanced NULL pointer safety in uasync_print_resources() by removing complex heap manipulation - Added debug logging to timeout_heap_pop() for better error tracking Test results: test_etcp_simple_traffic now passes without double free errors --- !/Makefile.am | 45 + !/config_parser.c | 586 ++++++++ !/config_parser.h | 89 ++ !/config_updater.c | 427 ++++++ !/config_updater.h | 23 + !/crc32.c | 66 + !/crc32.h | 19 + !/etcp.c | 1112 ++++++++++++++ !/etcp.c1 | 1147 +++++++++++++++ !/etcp.c2 | 1161 +++++++++++++++ {src => !}/etcp.c_ | 0 !/etcp.h | 148 ++ !/etcp_connections.c | 846 +++++++++++ {src => !}/etcp_connections.c1 | 0 !/etcp_connections.h | 100 ++ !/etcp_loadbalancer.c | 164 +++ !/etcp_loadbalancer.h | 20 + {src => !}/fix.patch | 0 !/packet_dump.h | 42 + !/pkt_normalizer.c | 604 ++++++++ !/pkt_normalizer.h | 75 + !/routing.c | 372 +++++ !/routing.h | 87 ++ !/routing.txt | 7 + !/secure_channel.c | 388 +++++ !/secure_channel.h | 68 + !/test_udp_socket.c | 342 +++++ !/test_udp_socket.h | 101 ++ !/test_virtual_tun.c | 190 +++ !/test_virtual_tun.h | 55 + !/tun_if.c | 339 +++++ !/tun_if.h | 107 ++ !/utun.c | 321 +++++ !/utun.conf | 42 + !/utun_instance.c | 388 +++++ !/utun_instance.c.backup | 314 ++++ !/utun_instance.c1 | 230 +++ !/utun_instance.h | 72 + !/utun_test_framework.c | 586 ++++++++ !/utun_test_framework.h | 114 ++ !/utun_test_hooks.c | 30 + !/utun_test_hooks.h | 126 ++ !/utun_test_socket_api.c | 117 ++ !/utun_test_socket_api.h | 52 + AGENTS.md | 1 + doc/etcp_config.txt | 10 + doc/etcp_protocol.txt | 2 +- lib/debug_config.c | 330 +++-- lib/debug_config.c1 | 390 +++++ lib/debug_config.h | 281 ++-- lib/debug_config.h1 | 132 ++ lib/ll_queue.c | 837 +++-------- lib/ll_queue.c2 | 829 ----------- lib/ll_queue.c3 | 847 ----------- lib/ll_queue.c_ | 744 ---------- lib/ll_queue.h | 152 +- lib/ll_queue.h2 | 224 --- lib/ll_queue.h3 | 224 --- lib/ll_queue.h_ | 210 --- lib/memory_pool.h | 6 +- lib/timeout_heap.c | 2 + lib/u_async.c | 1591 ++++++++++----------- lib/u_async.c1 | 830 +++++++++++ lib/u_async.h | 138 +- lib/u_async.h1 | 69 + src/config_parser.c | 76 + src/config_parser.h | 9 + src/etcp.c | 1214 +++------------- src/etcp.c1 | 556 ++----- src/etcp.c2 | 275 ++++ src/etcp.h | 73 +- src/etcp.h1 | 155 ++ src/etcp.h2 | 142 ++ src/etcp_connections.c | 64 +- src/etcp_connections.h | 7 +- src/etcp_loadbalancer.c | 229 +-- src/etcp_loadbalancer.h | 18 +- src/pkt_normalizer.c | 107 +- src/utun.c | 16 + src/utun_instance.c | 25 +- src/utun_instance.h | 5 +- tests/Makefile.am | 91 +- tests/buffer_overflow_fix.patch | 38 + tests/debug_full_test | Bin 0 -> 45456 bytes tests/debug_full_test.c | 234 +++ tests/debug_test | Bin 31024 -> 75904 bytes tests/detailed_test | Bin 0 -> 41216 bytes tests/detailed_test.c | 86 ++ tests/key_fix.patch | 24 + tests/test_config_debug | Bin 0 -> 95808 bytes tests/test_config_debug.c | 75 + tests/test_config_with_debug.conf | 18 + tests/test_debug | Bin 0 -> 41192 bytes tests/test_debug.c | 49 + tests/test_debug_categories | Bin 0 -> 49408 bytes tests/test_debug_categories.c | 117 ++ tests/test_debug_new | Bin 0 -> 46104 bytes tests/test_debug_new.c | 89 ++ tests/test_ecc_encrypt | Bin 0 -> 208968 bytes tests/test_etcp_minimal | Bin 474680 -> 392912 bytes tests/test_etcp_simple_traffic | Bin 631856 -> 550784 bytes tests/test_etcp_simple_traffic.c | 700 ++++----- tests/test_etcp_simple_traffic.c.backup | 491 +++++++ tests/test_etcp_simple_traffic.c.orig | 491 +++++++ tests/test_etcp_traffic_flow | Bin 632624 -> 0 bytes tests/test_etcp_traffic_flow.c | 544 ------- tests/test_etcp_two_instances | Bin 622808 -> 547592 bytes tests/test_etcp_two_instances.c.backup | 323 +++++ tests/test_fix.patch | 26 + tests/test_intensive_memory_pool | Bin 0 -> 118496 bytes tests/test_intensive_memory_pool.c | 104 +- tests/test_intensive_memory_pool_backup.c | 157 ++ tests/test_intensive_memory_pool_new.c | 153 ++ tests/test_ll_queue | Bin 0 -> 176768 bytes tests/test_ll_queue.c | 1126 +++++++++++++++ tests/test_ll_queue_comprehensive.c | 767 ---------- tests/test_ll_queue_pos | Bin 148144 -> 0 bytes tests/test_ll_queue_pos.c | 407 ------ tests/test_ll_queue_unified | Bin 164520 -> 0 bytes tests/test_ll_queue_unified.c | 752 ---------- tests/test_memory_pool_and_config | Bin 0 -> 118944 bytes tests/test_memory_pool_and_config.c | 52 +- tests/test_packet_dump | Bin 16136 -> 19248 bytes tests/test_u_async_comprehensive | Bin 0 -> 109672 bytes tests/test_u_async_comprehensive.c | 43 +- tests/test_u_async_performance | Bin 0 -> 99032 bytes tests/test_u_async_performance.c | 10 +- tests/track_test | Bin 0 -> 41304 bytes tests/track_test.c | 154 ++ tinycrypt/tests/test_aes | Bin 0 -> 29624 bytes tinycrypt/tests/test_cbc_mode | Bin 0 -> 21368 bytes tinycrypt/tests/test_ccm_mode | Bin 0 -> 21608 bytes tinycrypt/tests/test_cmac_mode | Bin 0 -> 21144 bytes tinycrypt/tests/test_ctr_mode | Bin 0 -> 16840 bytes tinycrypt/tests/test_ctr_prng | Bin 0 -> 26072 bytes tinycrypt/tests/test_ecc_dh | Bin 0 -> 49624 bytes tinycrypt/tests/test_ecc_dsa | Bin 0 -> 62536 bytes tinycrypt/tests/test_hmac | Bin 0 -> 21376 bytes tinycrypt/tests/test_hmac_prng | Bin 0 -> 98952 bytes tinycrypt/tests/test_sha256 | Bin 0 -> 21624 bytes 140 files changed, 19477 insertions(+), 9586 deletions(-) create mode 100755 !/Makefile.am create mode 100755 !/config_parser.c create mode 100755 !/config_parser.h create mode 100755 !/config_updater.c create mode 100755 !/config_updater.h create mode 100755 !/crc32.c create mode 100755 !/crc32.h create mode 100755 !/etcp.c create mode 100755 !/etcp.c1 create mode 100755 !/etcp.c2 rename {src => !}/etcp.c_ (100%) create mode 100755 !/etcp.h create mode 100755 !/etcp_connections.c rename {src => !}/etcp_connections.c1 (100%) create mode 100755 !/etcp_connections.h create mode 100755 !/etcp_loadbalancer.c create mode 100755 !/etcp_loadbalancer.h rename {src => !}/fix.patch (100%) mode change 100644 => 100755 create mode 100755 !/packet_dump.h create mode 100755 !/pkt_normalizer.c create mode 100755 !/pkt_normalizer.h create mode 100755 !/routing.c create mode 100755 !/routing.h create mode 100755 !/routing.txt create mode 100755 !/secure_channel.c create mode 100755 !/secure_channel.h create mode 100755 !/test_udp_socket.c create mode 100755 !/test_udp_socket.h create mode 100755 !/test_virtual_tun.c create mode 100755 !/test_virtual_tun.h create mode 100755 !/tun_if.c create mode 100755 !/tun_if.h create mode 100755 !/utun.c create mode 100755 !/utun.conf create mode 100755 !/utun_instance.c create mode 100755 !/utun_instance.c.backup create mode 100755 !/utun_instance.c1 create mode 100755 !/utun_instance.h create mode 100755 !/utun_test_framework.c create mode 100755 !/utun_test_framework.h create mode 100755 !/utun_test_hooks.c create mode 100755 !/utun_test_hooks.h create mode 100644 !/utun_test_socket_api.c create mode 100644 !/utun_test_socket_api.h create mode 100755 doc/etcp_config.txt create mode 100755 lib/debug_config.c1 create mode 100755 lib/debug_config.h1 delete mode 100755 lib/ll_queue.c2 delete mode 100755 lib/ll_queue.c3 delete mode 100755 lib/ll_queue.c_ delete mode 100755 lib/ll_queue.h2 delete mode 100755 lib/ll_queue.h3 delete mode 100755 lib/ll_queue.h_ create mode 100755 lib/u_async.c1 create mode 100755 lib/u_async.h1 create mode 100755 src/etcp.c2 create mode 100755 src/etcp.h1 create mode 100755 src/etcp.h2 create mode 100644 tests/buffer_overflow_fix.patch create mode 100755 tests/debug_full_test create mode 100644 tests/debug_full_test.c create mode 100755 tests/detailed_test create mode 100644 tests/detailed_test.c create mode 100644 tests/key_fix.patch create mode 100755 tests/test_config_debug create mode 100644 tests/test_config_debug.c create mode 100644 tests/test_config_with_debug.conf create mode 100755 tests/test_debug create mode 100644 tests/test_debug.c create mode 100755 tests/test_debug_categories create mode 100644 tests/test_debug_categories.c create mode 100755 tests/test_debug_new create mode 100644 tests/test_debug_new.c create mode 100755 tests/test_ecc_encrypt create mode 100644 tests/test_etcp_simple_traffic.c.backup create mode 100644 tests/test_etcp_simple_traffic.c.orig delete mode 100755 tests/test_etcp_traffic_flow delete mode 100644 tests/test_etcp_traffic_flow.c create mode 100644 tests/test_etcp_two_instances.c.backup create mode 100644 tests/test_fix.patch create mode 100755 tests/test_intensive_memory_pool create mode 100644 tests/test_intensive_memory_pool_backup.c create mode 100644 tests/test_intensive_memory_pool_new.c create mode 100755 tests/test_ll_queue create mode 100644 tests/test_ll_queue.c delete mode 100644 tests/test_ll_queue_comprehensive.c delete mode 100755 tests/test_ll_queue_pos delete mode 100644 tests/test_ll_queue_pos.c delete mode 100755 tests/test_ll_queue_unified delete mode 100644 tests/test_ll_queue_unified.c create mode 100755 tests/test_memory_pool_and_config create mode 100755 tests/test_u_async_comprehensive create mode 100755 tests/test_u_async_performance create mode 100755 tests/track_test create mode 100644 tests/track_test.c create mode 100755 tinycrypt/tests/test_aes create mode 100755 tinycrypt/tests/test_cbc_mode create mode 100755 tinycrypt/tests/test_ccm_mode create mode 100755 tinycrypt/tests/test_cmac_mode create mode 100755 tinycrypt/tests/test_ctr_mode create mode 100755 tinycrypt/tests/test_ctr_prng create mode 100755 tinycrypt/tests/test_ecc_dh create mode 100755 tinycrypt/tests/test_ecc_dsa create mode 100755 tinycrypt/tests/test_hmac create mode 100755 tinycrypt/tests/test_hmac_prng create mode 100755 tinycrypt/tests/test_sha256 diff --git a/!/Makefile.am b/!/Makefile.am new file mode 100755 index 00000000..a04a4f73 --- /dev/null +++ b/!/Makefile.am @@ -0,0 +1,45 @@ +bin_PROGRAMS = utun + +utun_SOURCES = \ + utun.c \ + utun_instance.c \ + config_parser.c \ + config_updater.c \ + routing.c \ + tun_if.c \ + etcp.c \ + etcp_connections.c \ + etcp_loadbalancer.c \ + secure_channel.c \ + crc32.c \ + pkt_normalizer.c \ + utun_test_hooks.c \ + $(top_srcdir)/tinycrypt/lib/source/aes_encrypt.c \ + $(top_srcdir)/tinycrypt/lib/source/aes_decrypt.c \ + $(top_srcdir)/tinycrypt/lib/source/cbc_mode.c \ + $(top_srcdir)/tinycrypt/lib/source/ccm_mode.c \ + $(top_srcdir)/tinycrypt/lib/source/cmac_mode.c \ + $(top_srcdir)/tinycrypt/lib/source/ctr_mode.c \ + $(top_srcdir)/tinycrypt/lib/source/ecc.c \ + $(top_srcdir)/tinycrypt/lib/source/ecc_dh.c \ + $(top_srcdir)/tinycrypt/lib/source/ecc_dsa.c \ + $(top_srcdir)/tinycrypt/lib/source/ecc_platform_specific.c \ + $(top_srcdir)/tinycrypt/lib/source/hmac.c \ + $(top_srcdir)/tinycrypt/lib/source/sha256.c \ + $(top_srcdir)/tinycrypt/lib/source/utils.c + +utun_CFLAGS = \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/tinycrypt/lib/include \ + -I$(top_srcdir)/tinycrypt/lib/source \ + $(DEBUG_FLAGS) + +utun_LDADD = \ + $(top_builddir)/lib/libuasync.a \ + -lpthread \ + -lm + +# Install directories +install-exec-hook: + $(MKDIR_P) $(DESTDIR)$(bindir) + $(MKDIR_P) $(DESTDIR)$(sysconfdir)/utun diff --git a/!/config_parser.c b/!/config_parser.c new file mode 100755 index 00000000..1e1ad4b4 --- /dev/null +++ b/!/config_parser.c @@ -0,0 +1,586 @@ +// config_parser.c - Configuration parser for utun application (updated for new structures) +#define _POSIX_C_SOURCE 200809L +#include "config_parser.h" +#include +#include +#include +#include +#include "../lib/debug_config.h" +#include +#include +#include +#include +#include +#include + +#define MAX_LINE_LEN 1024 +#define INITIAL_ARRAY_CAPACITY 8 + +typedef enum { + SECTION_UNKNOWN, + SECTION_GLOBAL, + SECTION_SERVER, + SECTION_CLIENT, + SECTION_ROUTING +} section_type_t; + +static char* trim(char *str) { + if (!str) return NULL; + while (isspace((unsigned char)*str)) str++; + char *end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + *(end + 1) = '\0'; + return str; +} + +static int parse_key_value(const char *line, char *key, size_t key_len, char *value, size_t value_len) { + char *equal = strchr(line, '='); + if (!equal) return -1; + + size_t key_size = equal - line; + if (key_size >= key_len) return -1; + strncpy(key, line, key_size); + key[key_size] = '\0'; + trim(key); + + const char *val_start = equal + 1; + size_t val_len = strlen(val_start); + if (val_len >= value_len) return -1; + strcpy(value, val_start); + trim(value); + + return 0; +} + +static int assign_string(char *dest, size_t dest_size, const char *src) { + if (!dest || !src || strlen(src) >= dest_size) return -1; + strcpy(dest, src); + return 0; +} + +static int parse_ip_with_netmask(const char *str, struct IP *ip, uint8_t *netmask) { + char ip_str[64]; + strncpy(ip_str, str, sizeof(ip_str) - 1); + ip_str[sizeof(ip_str) - 1] = '\0'; + + char *slash = strchr(ip_str, '/'); + if (slash) { + *slash = '\0'; + *netmask = atoi(slash + 1); + } else { + *netmask = 32; // Default IPv4 netmask + } + + // Try IPv4 first + struct in_addr addr4; + if (inet_pton(AF_INET, ip_str, &addr4) == 1) { + ip->family = AF_INET; + ip->addr.v4 = addr4; + return 0; + } + + // Try IPv6 + struct in6_addr addr6; + if (inet_pton(AF_INET6, ip_str, &addr6) == 1) { + ip->family = AF_INET6; + ip->addr.v6 = addr6; + if (*netmask == 32) *netmask = 128; // Default IPv6 netmask + return 0; + } + + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_ip_with_netmask: invalid IP address format: %s", str); + return -1; +} + +static int parse_sockaddr(const char *addr_str, const char *port_str, struct sockaddr_storage *sockaddr) { + struct addrinfo hints = {0}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + struct addrinfo *result; + if (getaddrinfo(addr_str, port_str, &hints, &result) != 0) { + return -1; + } + + memcpy(sockaddr, result->ai_addr, result->ai_addrlen); + freeaddrinfo(result); + return 0; +} + +static int parse_address_and_port(const char *str, struct sockaddr_storage *sockaddr) { + char addr_copy[MAX_ADDR_LEN]; + if (strlen(str) >= sizeof(addr_copy)) return -1; + strcpy(addr_copy, str); + + char *port_str = strrchr(addr_copy, ':'); + if (!port_str) return -1; + + *port_str = '\0'; + port_str++; + + return parse_sockaddr(addr_copy, port_str, sockaddr); +} + +static uint32_t get_netif_index(const char *ifname) { + if (!ifname || strlen(ifname) == 0) return 0; + return if_nametoindex(ifname); +} + +static struct CFG_CLIENT_LINK* create_client_link(struct CFG_SERVER* local_srv, const char *remote_addr) { + struct CFG_CLIENT_LINK *link = calloc(1, sizeof(struct CFG_CLIENT_LINK)); + if (!link) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "create_client_link: failed to allocate memory for client link"); + return NULL; + } + + link->local_srv = local_srv; + + if (parse_address_and_port(remote_addr, &link->remote_addr) < 0) { + free(link); + return NULL; + } + + link->next = NULL; + return link; +} + +static void free_cfg_client_links(struct CFG_CLIENT_LINK *links) { + while (links) { + struct CFG_CLIENT_LINK *next = links->next; + free(links); + links = next; + } +} + +static struct CFG_ROUTE_ENTRY* create_route_entry(const char *subnet_str) { + struct CFG_ROUTE_ENTRY *entry = calloc(1, sizeof(struct CFG_ROUTE_ENTRY)); + if (!entry) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "create_route_entry: failed to allocate memory for route entry"); + return NULL; + } + + if (parse_ip_with_netmask(subnet_str, &entry->ip, &entry->netmask) < 0) { + free(entry); + return NULL; + } + + entry->next = NULL; + return entry; +} + +static void free_route_entries(struct CFG_ROUTE_ENTRY *entries) { + while (entries) { + struct CFG_ROUTE_ENTRY *next = entries->next; + free(entries); + entries = next; + } +} + +static int add_route_entry(struct CFG_ROUTE_ENTRY **list, const char *subnet_str) { + struct CFG_ROUTE_ENTRY *new_entry = create_route_entry(subnet_str); + if (!new_entry) return -1; + + // Add to head of list + new_entry->next = *list; + *list = new_entry; + return 0; +} + +static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) { + struct CFG_SERVER *srv = servers; + while (srv) { + if (strcmp(srv->name, name) == 0) { + return srv; + } + srv = srv->next; + } + return NULL; +} + +static int parse_global(const char *key, const char *value, struct global_config *global) { + if (strcmp(key, "my_private_key") == 0) { + return assign_string(global->my_private_key_hex, MAX_KEY_LEN, value); + } + if (strcmp(key, "my_public_key") == 0) { + return assign_string(global->my_public_key_hex, MAX_KEY_LEN, value); + } + if (strcmp(key, "my_node_id") == 0) { + global->my_node_id = strtoull(value, NULL, 16); + return 0; + } + if (strcmp(key, "tun_ifname") == 0) { + snprintf(global->tun_ifname, sizeof(global->tun_ifname), "%s", value); + return 0; + } + if (strcmp(key, "tun_ip") == 0) { + uint8_t netmask; + return parse_ip_with_netmask(value, &global->tun_ip, &netmask); + } + if (strcmp(key, "mtu") == 0) { + global->mtu = atoi(value); + return 0; + } + if (strcmp(key, "control_ip") == 0) { + // Store for later processing with control_port + return 0; // We'll handle this when we see control_port + } + if (strcmp(key, "control_port") == 0) { + // This is tricky - we need to get control_ip from previous parsing + // For now, we'll use a simple approach + struct global_config temp_global = *global; + // Assume we stored control_ip somewhere or use default + char control_ip[MAX_ADDR_LEN] = "127.0.0.1"; // Default + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%s", value); + parse_sockaddr(control_ip, port_str, &global->control_sock); + return 0; + } + if (strcmp(key, "net_debug") == 0) { + global->net_debug = atoi(value); + return 0; + } + return 0; +} + +static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv) { + if (strcmp(key, "addr") == 0) { + return parse_address_and_port(value, &srv->ip); + } + if (strcmp(key, "so_mark") == 0) { + srv->so_mark = atoi(value); + return 0; + } + if (strcmp(key, "netif") == 0) { + srv->netif_index = get_netif_index(value); + return 0; + } + if (strcmp(key, "type") == 0) { + if (strcmp(value, "public") == 0) { + srv->type = CFG_SERVER_TYPE_PUBLIC; + } else if (strcmp(value, "nat") == 0) { + srv->type = CFG_SERVER_TYPE_NAT; + } else if (strcmp(value, "private") == 0) { + srv->type = CFG_SERVER_TYPE_PRIVATE; + } else { + srv->type = CFG_SERVER_TYPE_UNKNOWN; + } + return 0; + } + return 0; +} + +static int parse_client(const char *key, const char *value, struct CFG_CLIENT *cli, struct CFG_SERVER *servers) { + if (strcmp(key, "link") == 0) { + char link_copy[MAX_CONN_NAME_LEN + MAX_ADDR_LEN]; + if (strlen(value) >= sizeof(link_copy)) return -1; + strcpy(link_copy, value); + + // Find first colon (separator between server and ip:port) + char *first_colon = strchr(link_copy, ':'); + if (!first_colon) return -1; + + *first_colon = '\0'; + + // Find server by name + struct CFG_SERVER *local_srv = find_server_by_name(servers, link_copy); + if (!local_srv) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_client: server '%s' not found for client link", link_copy); + return -1; + } + + struct CFG_CLIENT_LINK *new_link = create_client_link(local_srv, first_colon + 1); + if (!new_link) return -1; + + // Add to linked list (prepend) + new_link->next = cli->links; + cli->links = new_link; + + return 0; + } + + if (strcmp(key, "peer_public_key") == 0) { + return assign_string(cli->peer_public_key_hex, MAX_KEY_LEN, value); + } + + if (strcmp(key, "keepalive") == 0) { + cli->keepalive = atoi(value); + return 0; + } + + return 0; +} + +static section_type_t parse_section_header(const char *line, char *name, size_t name_len) { + if (line[0] != '[') return SECTION_UNKNOWN; + + size_t line_len = strlen(line); + if (line[line_len - 1] != ']') return SECTION_UNKNOWN; + + char section[128]; + if (line_len - 2 >= sizeof(section)) return SECTION_UNKNOWN; + + strncpy(section, line + 1, line_len - 2); + section[line_len - 2] = '\0'; + trim(section); + + if (strcasecmp(section, "global") == 0) return SECTION_GLOBAL; + if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING; + + char *colon = strchr(section, ':'); + if (!colon) return SECTION_UNKNOWN; + + *colon = '\0'; + char *type = trim(section); + char *n = trim(colon + 1); + + if (strlen(n) >= name_len) return SECTION_UNKNOWN; + strcpy(name, n); + + if (strcasecmp(type, "server") == 0) return SECTION_SERVER; + if (strcasecmp(type, "client") == 0) return SECTION_CLIENT; + + return SECTION_UNKNOWN; +} + +static struct utun_config* parse_config_internal(FILE *fp, const char *filename) { + struct utun_config *cfg = calloc(1, sizeof(struct utun_config)); + if (!cfg) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for config structure"); + return NULL; + } + + section_type_t cur_section = SECTION_UNKNOWN; + struct CFG_SERVER *cur_server = NULL; + struct CFG_CLIENT *cur_client = NULL; + char line[MAX_LINE_LEN]; + int line_num = 0; + + while (fgets(line, sizeof(line), fp)) { + line_num++; + char *trimmed = trim(line); + + if (trimmed[0] == '\0' || trimmed[0] == '#') continue; + + if (trimmed[0] == '[') { + // Handle previous section + if (cur_section == SECTION_SERVER && cur_server) { + // Add server to linked list + cur_server->next = cfg->servers; + cfg->servers = cur_server; + cur_server = NULL; + } + if (cur_section == SECTION_CLIENT && cur_client) { + // Add client to linked list + cur_client->next = cfg->clients; + cfg->clients = cur_client; + cur_client = NULL; + } + + char name[MAX_CONN_NAME_LEN]; + cur_section = parse_section_header(trimmed, name, sizeof(name)); + + if (cur_section == SECTION_SERVER) { + cur_server = calloc(1, sizeof(struct CFG_SERVER)); + if (!cur_server) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for server %s", name); + goto error; + } + strcpy(cur_server->name, name); + } else if (cur_section == SECTION_CLIENT) { + cur_client = calloc(1, sizeof(struct CFG_CLIENT)); + if (!cur_client) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for client %s", name); + goto error; + } + strcpy(cur_client->name, name); + } + continue; + } + + char key[MAX_LINE_LEN], value[MAX_LINE_LEN]; + if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) < 0) { + printf( "%s:%d: Invalid key=value format", filename, line_num); + continue; + } + + switch (cur_section) { + case SECTION_GLOBAL: + if (parse_global(key, value, &cfg->global) < 0) { + printf( "%s:%d: Invalid global key '%s'", filename, line_num, key); + } + break; + case SECTION_SERVER: + if (cur_server && parse_server(key, value, cur_server) < 0) { + printf( "%s:%d: Invalid server key '%s'", filename, line_num, key); + } + break; + case SECTION_CLIENT: + if (cur_client && parse_client(key, value, cur_client, cfg->servers) < 0) { + printf( "%s:%d: Invalid client key '%s'", filename, line_num, key); + } + break; + case SECTION_ROUTING: + if (strcmp(key, "allowed_subnet") == 0) { + add_route_entry(&cfg->allowed_subnets, value); + } else if (strcmp(key, "my_subnet") == 0) { + add_route_entry(&cfg->my_subnets, value); + } + break; + default: + printf( "%s:%d: Key outside section: %s", filename, line_num, key); + break; + } + } + + // Handle final sections + if (cur_section == SECTION_SERVER && cur_server) { + cur_server->next = cfg->servers; + cfg->servers = cur_server; + } + if (cur_section == SECTION_CLIENT && cur_client) { + cur_client->next = cfg->clients; + cfg->clients = cur_client; + } + + // Set default TUN interface name if not provided + if (cfg->global.tun_ifname[0] == '\0') { + snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0"); + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname); + } + + return cfg; + +error: + if (cur_server) free(cur_server); + if (cur_client) { + free_cfg_client_links(cur_client->links); + free(cur_client); + } + free_config(cfg); + return NULL; +} + +struct utun_config* parse_config(const char *filename) { + printf("[CONFIG DEBUG] Opening config file: %s\n", filename); + FILE *fp = fopen(filename, "r"); + if (!fp) { + printf("[CONFIG ERROR] Failed to open config file: %s (errno=%d)\n", filename, errno); + return NULL; + } + + printf("[CONFIG DEBUG] Successfully opened config file\n"); + struct utun_config *config = parse_config_internal(fp, filename); + fclose(fp); + return config; +} + +void free_config(struct utun_config *config) { + if (!config) return; + + // Free servers + struct CFG_SERVER *server = config->servers; + while (server) { + struct CFG_SERVER *next = server->next; + free(server); + server = next; + } + + // Free clients and their links + struct CFG_CLIENT *client = config->clients; + while (client) { + struct CFG_CLIENT *next = client->next; + free_cfg_client_links(client->links); + free(client); + client = next; + } + + // Free route entries + free_route_entries(config->allowed_subnets); + free_route_entries(config->my_subnets); + + free(config); +} + +static const char* ip_to_string(const struct IP *ip, char *buffer, size_t buffer_size) { + if (ip->family == AF_INET) { + inet_ntop(AF_INET, &ip->addr.v4, buffer, buffer_size); + } else if (ip->family == AF_INET6) { + inet_ntop(AF_INET6, &ip->addr.v6, buffer, buffer_size); + } else { + snprintf(buffer, buffer_size, "invalid"); + } + return buffer; +} + +void print_config(const struct utun_config *cfg) { + if (!cfg) return; + + const struct global_config *g = &cfg->global; + char ip_buffer[64]; + + printf("Global:\n"); + printf(" Private key: %s\n", g->my_private_key_hex); + printf(" Public key: %s\n", g->my_public_key_hex); + printf(" Node ID: %llx\n", (unsigned long long)g->my_node_id); + printf(" TUN interface: %s\n", g->tun_ifname[0] ? g->tun_ifname : "auto"); + printf(" TUN IP: %s\n", ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer))); + printf(" MTU: %d\n", g->mtu); + printf(" Net debug: %d\n", g->net_debug); + + printf("\nServers:\n"); + struct CFG_SERVER *s = cfg->servers; + while (s) { + char addr_buffer[64]; + struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip; + inet_ntop(AF_INET, &sin->sin_addr, addr_buffer, sizeof(addr_buffer)); + printf(" %s: %s:%d (mark=%d, netif=%u, type=%u)\n", + s->name, addr_buffer, ntohs(sin->sin_port), + s->so_mark, s->netif_index, s->type); + s = s->next; + } + + printf("\nClients:\n"); + struct CFG_CLIENT *c = cfg->clients; + while (c) { + printf(" %s:\n", c->name); + struct CFG_CLIENT_LINK *link = c->links; + int link_num = 1; + while (link) { + char addr_buffer[64]; + struct sockaddr_in *sin = (struct sockaddr_in *)&link->remote_addr; + inet_ntop(AF_INET, &sin->sin_addr, addr_buffer, sizeof(addr_buffer)); + printf(" Link %d: %s:%d (via %s)\n", link_num++, addr_buffer, ntohs(sin->sin_port), link->local_srv->name); + link = link->next; + } + printf(" Peer key: %s\n", c->peer_public_key_hex); + printf(" Keepalive: %d\n", c->keepalive); + c = c->next; + } + + printf("\nAllowed Subnets:\n"); + struct CFG_ROUTE_ENTRY *allowed = cfg->allowed_subnets; + while (allowed) { + printf(" %s/%d\n", ip_to_string(&allowed->ip, ip_buffer, sizeof(ip_buffer)), allowed->netmask); + allowed = allowed->next; + } + + printf("\nMy Subnets:\n"); + struct CFG_ROUTE_ENTRY *my = cfg->my_subnets; + while (my) { + printf(" %s/%d\n", ip_to_string(&my->ip, ip_buffer, sizeof(ip_buffer)), my->netmask); + my = my->next; + } +} + +int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) { + // Minimal implementation: just append to file + FILE *fp = fopen(filename, "a"); + if (!fp) return -1; + + fprintf(fp, "\n[global]\n"); + fprintf(fp, "my_private_key=%s\n", priv_key); + fprintf(fp, "my_public_key=%s\n", pub_key); + + fclose(fp); + return 0; +} \ No newline at end of file diff --git a/!/config_parser.h b/!/config_parser.h new file mode 100755 index 00000000..93861f89 --- /dev/null +++ b/!/config_parser.h @@ -0,0 +1,89 @@ +// config_parser.h - Configuration parser for utun application +#ifndef CONFIG_PARSER_H +#define CONFIG_PARSER_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_CONN_NAME_LEN 64 +#define MAX_KEY_LEN 256 +#define MAX_ADDR_LEN 64 + +struct IP { + sa_family_t family; + union { + struct in_addr v4; + struct in6_addr v6; + } addr; +}; + +#define CFG_SERVER_TYPE_UNKNOWN 0 +#define CFG_SERVER_TYPE_PUBLIC 1 +#define CFG_SERVER_TYPE_NAT 2 +#define CFG_SERVER_TYPE_PRIVATE 3 + +struct CFG_SERVER { + struct CFG_SERVER* next; + char name[MAX_CONN_NAME_LEN]; + struct sockaddr_storage ip; // ip:port + uint32_t netif_index;// if_nameindex, 0 - no interface specified + int so_mark; + uint8_t type; // public/nat/private +}; + +struct CFG_CLIENT_LINK { + struct CFG_CLIENT_LINK *next; // Next link in linked list + struct CFG_SERVER* local_srv; + struct sockaddr_storage remote_addr; // ip:port +}; + +struct CFG_CLIENT { + char name[MAX_CONN_NAME_LEN]; + char peer_public_key_hex[MAX_KEY_LEN]; + int keepalive; + struct CFG_CLIENT_LINK *links; // Linked list of links + struct CFG_CLIENT *next; // Next client in linked list +}; + +struct CFG_ROUTE_ENTRY { + struct CFG_ROUTE_ENTRY* next; + struct IP ip; + uint8_t netmask; +}; + +struct global_config { + char my_private_key_hex[MAX_KEY_LEN]; + char my_public_key_hex[MAX_KEY_LEN]; + uint64_t my_node_id; + char tun_ifname[16]; // TUN interface name (e.g., "tun12") + struct IP tun_ip; + int mtu; + struct sockaddr_storage control_sock; + int net_debug; +}; + +struct utun_config { + struct global_config global; + struct CFG_SERVER* servers; + struct CFG_CLIENT* clients; + struct CFG_ROUTE_ENTRY* allowed_subnets; + struct CFG_ROUTE_ENTRY* my_subnets; +}; + +struct utun_config* parse_config(const char *filename); +void free_config(struct utun_config *config); +void print_config(const struct utun_config *config); +int update_config_keys(const char *filename, const char *priv_key, const char *pub_key); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/!/config_updater.c b/!/config_updater.c new file mode 100755 index 00000000..8fa3d797 --- /dev/null +++ b/!/config_updater.c @@ -0,0 +1,427 @@ +// config_updater.c - Configuration file updater implementation +#define _POSIX_C_SOURCE 200809L +#include "config_updater.h" +#include "config_parser.h" +#include "secure_channel.h" +#include "debug_config.h" +#include +#include +#include +#include +#include +#include +#include + +#define PRIV_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null +#define PUB_HEXKEY_LEN 129 // 64 bytes * 2 hex chars + null +#define HEXNODEID_LEN 17 // 8 bytes * 2 hex chars + null +#define MAX_LINE_LEN 1024 + +void bytes_to_hex(const uint8_t *bytes, size_t len, char *hex_str, size_t hex_len) { + if (!bytes || !hex_str || hex_len < len * 2 + 1) { + if (hex_str && hex_len > 0) hex_str[0] = '\0'; + return; + } + for (size_t i = 0; i < len; i++) { + snprintf(hex_str + i * 2, hex_len - i * 2, "%02x", bytes[i]); + + } + hex_str[len * 2] = '\0'; +} + + +static int is_valid_priv_key(const char *key) { + if (!key || strlen(key) != 64) return 0; + for (int i = 0; i < 64; i++) { + if (!isxdigit((unsigned char)key[i])) return 0; + } + return 1; +} + +static int is_valid_pub_key(const char *key) { + if (!key || strlen(key) != 128) return 0; + for (int i = 0; i < 128; i++) { + if (!isxdigit((unsigned char)key[i])) return 0; + } + return 1; +} + +static int is_valid_node_id(uint64_t node_id) { + return node_id != 0; +} + +static int read_file_to_mem(const char *filename, char **buffer, size_t *size) { + FILE *fp = fopen(filename, "r"); + + if (!fp) return -1; + + fseek(fp, 0, SEEK_END); + + long file_size = ftell(fp); + + if (file_size < 0) { + fclose(fp); + + return -1; + } + fseek(fp, 0, SEEK_SET); + + + *buffer = malloc(file_size + 1); + + if (!*buffer) { + fclose(fp); + + return -1; + } + + size_t read_size = fread(*buffer, 1, file_size, fp); + + if (read_size != (size_t)file_size) { + free(*buffer); + + fclose(fp); + + return -1; + } + + (*buffer)[file_size] = '\0'; + *size = file_size; + fclose(fp); + + return 0; +} + +static int write_mem_to_file(const char *filename, const char *buffer, size_t size) { + FILE *fp = fopen(filename, "w"); + + if (!fp) return -1; + + size_t written = fwrite(buffer, 1, size, fp); + + if (written != size) { + fclose(fp); + + return -1; + } + + fclose(fp); + + return 0; +} + +static char* find_section_global(char *buf, size_t buf_len) { + char *p = buf; + while (p < buf + buf_len) { + if (p[0] == '[' && strncmp(p + 1, "global", 6) == 0) { + char *end = strchr(p, ']'); + + if (end) return end + 1; + } + p = strchr(p, '\n'); + + if (!p) break; + p++; // skip '\n' + } + return NULL; +} + +static char* find_option(char *buf, size_t buf_len, const char *option) { + char *p = buf; + while (p < buf + buf_len) { + if (strncmp(p, option, strlen(option)) == 0) { + char *after_key = p + strlen(option); + + if (after_key[0] == '=') return p; + } + p = strchr(p, '\n'); + + if (!p) break; + p++; + } + return NULL; +} + +static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_capacity, const char *option, const char *value) { + if (!buf || !*buf || !buf_len || !buf_capacity || !option || !value) return -1; + + // Check if option already exists + char *opt_pos = find_option(*buf, *buf_len, option); + + if (opt_pos) { + // Find option line end + char *line_end = strchr(opt_pos, '\n'); + + if (!line_end) line_end = *buf + *buf_len; + + // Prepare new line + char new_line[MAX_LINE_LEN]; + int new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value); + + if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1; + + // Calculate length difference + size_t old_line_len = line_end - opt_pos + 1; + long len_diff = new_len - old_line_len; + + // Ensure buffer capacity + if (*buf_len + len_diff + 1 > *buf_capacity) { + *buf_capacity = *buf_len + len_diff + 1024; + char *new_buf = realloc(*buf, *buf_capacity); + + if (!new_buf) return -1; + *buf = new_buf; + // Recalculate positions after realloc + opt_pos = find_option(*buf, *buf_len, option); + + if (!opt_pos) return -1; + line_end = strchr(opt_pos, '\n'); + + if (!line_end) line_end = *buf + *buf_len; + } + + // Move content and insert new line + memmove(opt_pos + new_len, line_end, *buf_len - (line_end - *buf) + 1); + + memcpy(opt_pos, new_line, new_len); + + *buf_len += len_diff; + + } else { + // Insert after [global] section + char *section_end = find_section_global(*buf, *buf_len); + + if (!section_end) return -1; + + // Find end of global section (next [ or end of buffer) + char *insert_pos = strchr(section_end, '['); + + if (!insert_pos) insert_pos = *buf + *buf_len; + else { + // Move to beginning of next line + char *prev_nl = insert_pos; + while (prev_nl > *buf && *prev_nl != '\n') prev_nl--; + if (*prev_nl == '\n') insert_pos = prev_nl + 1; + } + + // Prepare new line + char new_line[MAX_LINE_LEN]; + int new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value); + + if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1; + + // Ensure buffer capacity + if (*buf_len + new_len + 1 > *buf_capacity) { + *buf_capacity = *buf_len + new_len + 1024; + char *new_buf = realloc(*buf, *buf_capacity); + + if (!new_buf) return -1; + *buf = new_buf; + // Recalculate insert position after realloc + section_end = find_section_global(*buf, *buf_len); + + if (!section_end) return -1; + insert_pos = strchr(section_end, '['); + + if (!insert_pos) insert_pos = *buf + *buf_len; + else { + char *prev_nl = insert_pos; + while (prev_nl > *buf && *prev_nl != '\n') prev_nl--; + if (*prev_nl == '\n') insert_pos = prev_nl + 1; + } + } + + // Move content and insert new line + memmove(insert_pos + new_len, insert_pos, *buf_len - (insert_pos - *buf) + 1); + + memcpy(insert_pos, new_line, new_len); + + *buf_len += new_len; + } + + return 0; +} + +int config_ensure_keys_and_node_id(const char *filename) { + struct utun_config *config = parse_config(filename); + + if (!config) { + printf("[CONFIG ERROR] Failed to parse config: %s\n", filename); + + return -1; + } + + struct global_config *global = &config->global; + + // Debug: print what we found + printf("[CONFIG DEBUG] Checking config - priv_key='%s' (len=%zu), pub_key='%s' (len=%zu), node_id=%llu\n", + global->my_private_key_hex ? global->my_private_key_hex : "NULL", + global->my_private_key_hex ? strlen(global->my_private_key_hex) : 0, + global->my_public_key_hex ? global->my_public_key_hex : "NULL", + global->my_public_key_hex ? strlen(global->my_public_key_hex) : 0, + (unsigned long long)global->my_node_id); + + + // Check if we need to generate anything + int need_priv_key = !is_valid_priv_key(global->my_private_key_hex); + + int need_pub_key = !is_valid_pub_key(global->my_public_key_hex); + + int need_node_id = !is_valid_node_id(global->my_node_id); + + + printf("[CONFIG DEBUG] Validation results - need_priv_key=%d, need_pub_key=%d, need_node_id=%d\n", + need_priv_key, need_pub_key, need_node_id); + + + if (!need_priv_key && !need_pub_key && !need_node_id) { + free_config(config); + + return 0; + } + + // Generate keys if needed + char new_priv_key[PRIV_HEXKEY_LEN] = {0}; + char new_pub_key[PUB_HEXKEY_LEN] = {0}; + uint64_t new_node_id = 0; + + if (need_priv_key) { + // Generate new keypair if private key is invalid + struct SC_MYKEYS mykeys; + if (sc_generate_keypair(&mykeys) != SC_OK) { + printf( "Failed to generate keypair"); + + free_config(config); + + return -1; + } + bytes_to_hex(mykeys.private_key, SC_PRIVKEY_SIZE, new_priv_key, sizeof(new_priv_key)); + + bytes_to_hex(mykeys.public_key, SC_PUBKEY_SIZE, new_pub_key, sizeof(new_pub_key)); + + } else if (need_pub_key) { + // Compute public key from existing private key + uint8_t priv_bin[SC_PRIVKEY_SIZE]; + uint8_t pub_bin[SC_PUBKEY_SIZE]; + + // Convert private key from hex to binary + for (int i = 0; i < SC_PRIVKEY_SIZE; i++) { + unsigned int byte; + if (sscanf(global->my_private_key_hex + i * 2, "%2x", &byte) != 1) { + printf( "Invalid private key hex format"); + + free_config(config); + + return -1; + } + priv_bin[i] = (uint8_t)byte; + } + + // Compute public key + if (sc_compute_public_key_from_private(priv_bin, pub_bin) != SC_OK) { + printf( "Failed to compute public key from private key"); + + free_config(config); + + return -1; + } + + // Convert to hex + bytes_to_hex(priv_bin, SC_PRIVKEY_SIZE, new_priv_key, sizeof(new_priv_key)); + + bytes_to_hex(pub_bin, SC_PUBKEY_SIZE, new_pub_key, sizeof(new_pub_key)); + + } + + if (need_node_id) { + int fd = open("/dev/urandom", O_RDONLY); + + if (fd < 0) { + printf( "Failed to open /dev/urandom"); + + free_config(config); + + return -1; + } + if (read(fd, &new_node_id, sizeof(new_node_id)) != sizeof(new_node_id)) { + close(fd); + + printf( "Failed to read random bytes for node_id"); + + free_config(config); + + return -1; + } + close(fd); + + new_node_id &= 0x7FFFFFFFFFFFFFFF; + } + + free_config(config); + + + // Read entire config file to memory + char *file_buf = NULL; + size_t file_size = 0; + if (read_file_to_mem(filename, &file_buf, &file_size) < 0) { + printf( "Failed to read config file: %s", filename); + + return -1; + } + + // Update config file + size_t buf_capacity = file_size + 1024; + char *work_buf = malloc(buf_capacity); + + if (!work_buf) { + free(file_buf); + + return -1; + } + memcpy(work_buf, file_buf, file_size); + + size_t work_len = file_size; + + int ret = 0; + + if (need_node_id) { + char node_id_hex[HEXNODEID_LEN + 1]; + snprintf(node_id_hex, sizeof(node_id_hex), "%llx", (unsigned long long)new_node_id); + + if (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_node_id", node_id_hex) < 0) { + ret = -1; + } + } + + if (need_priv_key && ret == 0) { + if (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_private_key", new_priv_key) < 0) { + ret = -1; + } + } + + if (need_pub_key && ret == 0) { + if (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_public_key", new_pub_key) < 0) { + ret = -1; + } + } + + if (ret == 0) { + printf("[CONFIG DEBUG] Writing updated config file, work_len=%zu\n", work_len); + + if (write_mem_to_file(filename, work_buf, work_len) < 0) { + printf( "Failed to write updated config file: %s", filename); + + ret = -1; + } else { + printf("[CONFIG DEBUG] Successfully updated config file: %s\n", filename); + + } + } + + free(file_buf); + + free(work_buf); + + + return ret; +} diff --git a/!/config_updater.h b/!/config_updater.h new file mode 100755 index 00000000..283f434f --- /dev/null +++ b/!/config_updater.h @@ -0,0 +1,23 @@ +// config_updater.h - Configuration file updater +#ifndef CONFIG_UPDATER_H +#define CONFIG_UPDATER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Ensures config file has valid my_private_key, my_public_key, and my_node_id +// If any are missing or invalid, generates and updates them +// Returns 0 on success, -1 on error +int config_ensure_keys_and_node_id(const char *filename); + +void bytes_to_hex(const uint8_t *bytes, size_t len, char *hex_str, size_t hex_len); + +#ifdef __cplusplus +} +#endif + +#endif // CONFIG_UPDATER_H diff --git a/!/crc32.c b/!/crc32.c new file mode 100755 index 00000000..8ed4db45 --- /dev/null +++ b/!/crc32.c @@ -0,0 +1,66 @@ +// crc32.c - CRC32 checksum implementation +#include "crc32.h" +#include + +static uint32_t crc32_table[256]; +static int crc32_table_initialized = 0; + +static void init_crc32_table(void) { + if (crc32_table_initialized) return; + + for (uint32_t i = 0; i < 256; i++) { + uint32_t crc = i; + for (int j = 0; j < 8; j++) { + if (crc & 1) { + crc = (crc >> 1) ^ 0xEDB88320; + } else { + crc >>= 1; + } + } + crc32_table[i] = crc; + } + + crc32_table_initialized = 1; +} + +void crc32_init(void) { + init_crc32_table(); +} + +uint32_t crc32_calc(const uint8_t *data, size_t len) { + if (!crc32_table_initialized) { + init_crc32_table(); + } + + if (!data || len == 0) return 0xFFFFFFFF; + + uint32_t crc = 0xFFFFFFFF; + for (size_t i = 0; i < len; i++) { + uint8_t byte = data[i]; + uint32_t table_index = (crc ^ byte) & 0xFF; + crc = (crc >> 8) ^ crc32_table[table_index]; + } + + return ~crc; +} + +uint32_t crc32_calc_ex(const uint8_t *data, size_t len, uint32_t initial_crc) { + if (!crc32_table_initialized) { + init_crc32_table(); + } + + if (!data || len == 0) return initial_crc; + + uint32_t crc = initial_crc; + for (size_t i = 0; i < len; i++) { + uint8_t byte = data[i]; + uint32_t table_index = (crc ^ byte) & 0xFF; + crc = (crc >> 8) ^ crc32_table[table_index]; + } + + return crc; +} + +uint32_t crc32_update(uint32_t crc, const uint8_t *data, size_t len) { + return crc32_calc_ex(data, len, crc); +} \ No newline at end of file diff --git a/!/crc32.h b/!/crc32.h new file mode 100755 index 00000000..ea1a334b --- /dev/null +++ b/!/crc32.h @@ -0,0 +1,19 @@ +#ifndef CRC32_H +#define CRC32_H + +#include +#include + +// Initialize CRC32 table (called automatically on first use) +void crc32_init(void); + +// Calculate CRC32 checksum +uint32_t crc32_calc(const uint8_t *data, size_t len); + +// Calculate CRC32 with initial value +uint32_t crc32_calc_ex(const uint8_t *data, size_t len, uint32_t initial_crc); + +// Update CRC32 incrementally +uint32_t crc32_update(uint32_t crc, const uint8_t *data, size_t len); + +#endif // CRC32_H \ No newline at end of file diff --git a/!/etcp.c b/!/etcp.c new file mode 100755 index 00000000..b4b0da02 --- /dev/null +++ b/!/etcp.c @@ -0,0 +1,1112 @@ +// etcp.c - ETCP Protocol Implementation (refactored and expanded based on etcp_protocol.txt) +#include "etcp.h" +#include "etcp_loadbalancer.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include "crc32.h" +#include +#include +#include +#include // For bandwidth calcs + +// Enable comprehensive debug output for ETCP module +#define DEBUG_CATEGORY_ETCP_DETAILED 1 + +// Constants from spec (adjusted for completeness) +#define MAX_INFLIGHT_BYTES 65536 // Initial window +#define RETRANS_K1 2.0f // RTT multiplier for retrans timeout +#define RETRANS_K2 1.5f // Jitter multiplier +#define ACK_DELAY_TB 20 // ACK timer delay (2ms in 0.1ms units) +#define BURST_DELAY_FACTOR 4 // Delay before burst +#define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) +#define RTT_HISTORY_SIZE 10 // For jitter calc +#define MAX_PENDING 32 // For ACKs/retrans +#define SECTION_HEADER_SIZE 3 // type(1) + len(2) + +// Forward declaration for callback function +static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg); + +// Timestamp diff (with wrap-around) +static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2); + if (t1 >= t2) { + uint16_t diff = t1 - t2; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: normal case, diff=%u", diff); + return diff; + } + uint16_t diff = (0xFFFF - t2) + t1 + 1; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: wraparound case, diff=%u", diff); + return diff; +} + +// Get current time in 0.1ms units +uint64_t get_current_time_units() { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t time_units = ((uint64_t)tv.tv_sec * 10000ULL) + (tv.tv_usec / 100); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_time_units: tv_sec=%ld, tv_usec=%ld, result=%llu", + tv.tv_sec, tv.tv_usec, (unsigned long long)time_units); + return time_units; +} + +uint16_t get_current_timestamp() { + uint16_t timestamp = (uint16_t)(get_current_time_units() & 0xFFFF); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_timestamp: result=%u", timestamp); + return timestamp; +} + +// Internal functions (expanded) +static void retrans_timer_cb(void* arg); +static void ack_timer_cb(void* arg); +static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt); +static void calculate_jitter(struct ETCP_CONN* etcp); +static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp); +static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link); +static void deliver_contiguous_packets(struct ETCP_CONN* etcp); +static void retrans_check(struct ETCP_CONN* etcp); +static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state); +static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt); +static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt); +static void free_inflight_packet(inflight_packet_t* pkt); +static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link); +static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw); + +// Add RTT history to struct (for jitter) +/// In etcp.h, add to ETCP_CONN: +/// uint16_t rtt_history[RTT_HISTORY_SIZE]; +/// uint8_t rtt_history_idx; + +// Create ETCP connection (unchanged) +struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: instance=%p", instance); + + struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: failed to allocate ETCP_CONN"); + return NULL; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: allocated etcp=%p", etcp); + + etcp->instance = instance; + etcp->input_queue = queue_new(instance->ua, instance->pkt_pool); + etcp->output_queue = queue_new(instance->ua, instance->pkt_pool); + etcp->mtu = 1500; // Default + etcp->window_size = MAX_INFLIGHT_BYTES; + etcp->next_tx_id = 1; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, mtu=%d, window_size=%u, next_tx_id=%u", + etcp->mtu, etcp->window_size, etcp->next_tx_id); + + // Init crypto (as per original) + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: initializing crypto context"); + if (sc_init_ctx(&etcp->crypto_ctx, &instance->my_keys) != SC_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: crypto initialization failed"); + etcp_connection_close(etcp); + return NULL; + } + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_create: successfully created ETCP connection %p", etcp); + return etcp; +} + +// Close connection (expanded free) +void etcp_connection_close(struct ETCP_CONN* etcp) { + if (!etcp) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_connection_close: called with NULL etcp"); + return; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: closing connection %p", etcp); + + // Cancel timers + if (etcp->retrans_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling retrans_timer"); + uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); + } + if (etcp->ack_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling ack_timer"); + uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); + } + + // Free inflight + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_ack_list"); + inflight_packet_t* pkt = etcp->wait_ack_list; + int wait_ack_count = 0; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + wait_ack_count++; + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_ack_list", wait_ack_count); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_send_list"); + pkt = etcp->wait_send_list; + int wait_send_count = 0; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + wait_send_count++; + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_send_list", wait_send_count); + + // Free rx_list + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing rx_list"); + rx_packet_t* rx = etcp->rx_list; + int rx_count = 0; + while (rx) { + rx_packet_t* next = rx->next; + free(rx->data); + free(rx); + rx = next; + rx_count++; + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from rx_list", rx_count); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing queues"); + queue_free(etcp->input_queue); + queue_free(etcp->output_queue); + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_close: connection %p closed successfully", etcp); + free(etcp); +} + +static void free_inflight_packet(inflight_packet_t* pkt) { + if (pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "free_inflight_packet: freeing packet %p (id=%u, data_len=%u)", + pkt, pkt->id, pkt->data_len); + free(pkt->data); + free(pkt); + } +} + +// Reset connection (unchanged) +void etcp_conn_reset(struct ETCP_CONN* etcp) { + if (!etcp) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: called with NULL etcp"); + return; + } + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: resetting connection %p", etcp); + + // Cancel timers + if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); + if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); + + // Free lists + inflight_packet_t* pkt = etcp->wait_ack_list; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + } + etcp->wait_ack_list = NULL; + pkt = etcp->wait_send_list; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + } + etcp->wait_send_list = NULL; + + rx_packet_t* rx = etcp->rx_list; + while (rx) { + rx_packet_t* next = rx->next; + free(rx->data); + free(rx); + rx = next; + } + etcp->rx_list = NULL; + + // Reset state + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: clearing packet lists and resetting state"); + etcp->next_tx_id = 1; + etcp->last_rx_id = 0; + etcp->last_delivered_id = 0; + etcp->unacked_bytes = 0; + etcp->pending_ack_count = 0; + etcp->pending_retrans_count = 0; + etcp->rtt_last = 0; + etcp->rtt_avg_10 = 0; + etcp->rtt_avg_100 = 0; + etcp->jitter = 0; + etcp->burst_in_progress = 0; + etcp->rtt_history_idx = 0; + memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); + // Queues cleared externally if needed + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: connection %p reset completed", etcp); +} + +// Input decrypted packet (unchanged structure, but calls expanded processes) +void etcp_conn_input(struct ETCP_DGRAM* pkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p, link=%p, etcp=%p", + pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); + + if (!pkt || !pkt->link || !pkt->link->etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: invalid parameters (pkt=%p, link=%p, etcp=%p)", + pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); + return; + } + + struct ETCP_CONN* etcp = pkt->link->etcp; + uint8_t* data = pkt->data; + uint16_t len = pkt->data_len; + uint16_t ts = pkt->timestamp; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: processing packet - etcp=%p, data_len=%u, timestamp=%u, link=%p", + etcp, len, ts, pkt->link); + + // Update link activity + pkt->link->last_activity = get_current_time_units(); + pkt->link->last_recv_local_time = pkt->link->last_activity; + pkt->link->last_recv_timestamp = ts; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_conn_input: updated link activity - last_activity=%llu, last_recv_timestamp=%u", + (unsigned long long)pkt->link->last_activity, pkt->link->last_recv_timestamp); + + // Parse sections + uint16_t pos = 0; + while (pos < len) { + if (pos + SECTION_HEADER_SIZE > len) break; + + uint8_t type = data[pos]; + uint16_t sec_len = (data[pos+1] << 8) | data[pos+2]; + pos += SECTION_HEADER_SIZE; + + if (pos + sec_len > len) break; + + uint8_t* sec_data = data + pos; + switch (type) { + case ETCP_SECTION_ACK: + process_section_ack(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_RETRANS: + process_section_retrans(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_TIMESTAMP: + process_section_timestamp(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_PAYLOAD: + process_section_payload(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_MEAS_TS: + process_section_meas_ts(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_MEAS_RESP: + process_section_meas_resp(etcp, sec_data, sec_len); + break; + default: + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Unknown section type: 0x%02x", type); + break; + } + pos += sec_len; + } + + // Post-processing + detect_gaps_and_request_retrans(etcp); + deliver_contiguous_packets(etcp); + + // Trigger if waiting + if (etcp->wait_timeout_active) { + etcp->wait_timeout_active = 0; + // Assume load balancer polls again + } +} + +// Request next packet (expanded) +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: etcp=%p", etcp); + + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: called with NULL etcp"); + return NULL; + } + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: input_queue_count=%d, wait_send_list=%p, unacked_bytes=%u, window_size=%u", + queue_entry_count(etcp->input_queue), etcp->wait_send_list, + etcp->unacked_bytes, etcp->window_size); + + // Check waiting conditions + if (queue_entry_count(etcp->input_queue) == 0 && etcp->wait_send_list == NULL) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no packets available, setting callback and returning NULL"); + // Wait for input or retrans + // Set callback for resume + queue_set_callback(etcp->input_queue, input_queue_cb, etcp); // Define cb to resume + return NULL; + } + + if (etcp->unacked_bytes >= etcp->window_size) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: window full (unacked_bytes=%u >= window_size=%u), waiting", + etcp->unacked_bytes, etcp->window_size); + // Wait ACK or timeout + etcp->wait_timeout_active = 1; + return NULL; + } + + if (etcp->unacked_bytes >= etcp->window_size) { + // Wait ACK or timeout + etcp->wait_timeout_active = 1; + return NULL; + } + + // Get packet (prefer retrans) + inflight_packet_t* ipkt = etcp->wait_send_list; + if (ipkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: using retransmission packet id=%u", ipkt->id); + remove_inflight(etcp, ipkt); + } else { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: getting new packet from input queue"); + struct ll_entry* entry = queue_entry_get(etcp->input_queue); + if (!entry) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no entry available from input queue"); + return NULL; + } + + ipkt = calloc(1, sizeof(inflight_packet_t)); + if (!ipkt) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate inflight_packet_t"); + queue_entry_put_first(etcp->input_queue, entry); + return NULL; + } + + ipkt->id = etcp->next_tx_id++; + ipkt->data_len = ll_entry_size(entry); + ipkt->data = malloc(ipkt->data_len); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: created new packet id=%u, data_len=%u", + ipkt->id, ipkt->data_len); + + if (!ipkt->data) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate packet data buffer"); + free(ipkt); + queue_entry_put_first(etcp->input_queue, entry); + return NULL; + } + memcpy(ipkt->data, ll_entry_data(entry), ipkt->data_len); + ipkt->payload_len = ipkt->data_len; // Assume full is payload for window + queue_entry_free(entry); + } + + // Select link + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selecting link via loadbalancer"); + struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); + if (!link) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no suitable link available"); + // Put back if new + if (ipkt->send_count == 0) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: putting new packet back to input queue"); + struct ll_entry* entry = queue_entry_new(ipkt->data_len); + memcpy(ll_entry_data(entry), ipkt->data, ipkt->data_len); + queue_entry_put_first(etcp->input_queue, entry); + } + free_inflight_packet(ipkt); + return NULL; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selected link %p", link); + + // Build packet buf + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: building packet with optional sections"); + uint8_t buf[1600]; + uint16_t len = append_optional_sections(etcp, buf, link); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: optional sections added, len=%u", len); + + // Add payload (assume payload includes ID as first 2 bytes) + uint16_t pay_len = ipkt->payload_len + 2; // ID + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload section - pay_len=%u (payload_len=%u + 2 for ID)", + pay_len, ipkt->payload_len); + + if (len + SECTION_HEADER_SIZE + pay_len > sizeof(buf)) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: packet too large (%u + %u + %u > %u), dropping", + len, SECTION_HEADER_SIZE, pay_len, (unsigned)sizeof(buf)); + free_inflight_packet(ipkt); + return NULL; + } + buf[len++] = ETCP_SECTION_PAYLOAD; + buf[len++] = (pay_len >> 8) & 0xFF; + buf[len++] = pay_len & 0xFF; + buf[len++] = (ipkt->id >> 8) & 0xFF; + buf[len++] = ipkt->id & 0xFF; + memcpy(buf + len, ipkt->data, ipkt->payload_len); + len += ipkt->payload_len; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload added, total len=%u", len); + + // Alloc dgram + struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); + if (!dgram) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate dgram from memory pool"); + free_inflight_packet(ipkt); + return NULL; + } + dgram->link = link; + dgram->data_len = len; + dgram->noencrypt_len = 0; + dgram->timestamp = get_current_timestamp(); + memcpy(dgram->data, buf, len); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: allocated dgram %p (link=%p, data_len=%u, timestamp=%u)", + dgram, link, len, dgram->timestamp); + + // Update inflight + ipkt->last_link = link; + ipkt->last_timestamp = dgram->timestamp; + ipkt->send_count++; + etcp->unacked_bytes += ipkt->payload_len; + etcp->bytes_sent_total += len; // Approx + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: updated inflight - send_count=%u, unacked_bytes=%u, bytes_sent_total=%u", + ipkt->send_count, etcp->unacked_bytes, etcp->bytes_sent_total); + + // Move to wait_ack + ipkt->state = INFLIGHT_STATE_WAIT_ACK; + ipkt->next = etcp->wait_ack_list; + etcp->wait_ack_list = ipkt; + + // Add to wait_ack list + ipkt->next = etcp->wait_ack_list; + etcp->wait_ack_list = ipkt; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: added packet %u to wait_ack_list", ipkt->id); + + // Start retrans timer if empty before + if (etcp->wait_ack_list->next == NULL && !etcp->retrans_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: starting retrans_timer (wait_ack_list was empty)"); + etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); + } + + // Update load balancer after send (call from connections after actual send) + // etcp_loadbalancer_update_after_send(link, len + headers); // Assume called externally + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: successfully created dgram %p for packet id=%u", + dgram, ipkt->id); + return dgram; +} + +// Input queue callback (placeholder to resume load balancer) +static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg) { + (void)q; + (void)entry; + struct ETCP_CONN* etcp = arg; + // Resume polling etcp_request_pkt + queue_resume_callback(q); +} + +// Append optional (expanded with debug) +static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: etcp=%p, link=%p", etcp, link); + + uint16_t len = 0; + int sections_added = 0; + + // Timestamp (assume 2 bytes TS) + uint16_t ts = get_current_timestamp(); + buf[len++] = ETCP_SECTION_TIMESTAMP; + buf[len++] = 0; // len high + buf[len++] = 2; // len low + buf[len++] = (ts >> 8) & 0xFF; + buf[len++] = ts & 0xFF; + sections_added++; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: added TIMESTAMP section (ts=%u, len=%u)", ts, len); + + // ACK (assume count(1) + [id(2)+ts(2)]*count) + if (etcp->pending_ack_count > 0) { + uint16_t ack_len = 1 + etcp->pending_ack_count * 4; + buf[len++] = ETCP_SECTION_ACK; + buf[len++] = (ack_len >> 8) & 0xFF; + buf[len++] = ack_len & 0xFF; + buf[len++] = etcp->pending_ack_count; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding ACK section with %u ACKs", etcp->pending_ack_count); + + for (uint8_t i = 0; i < etcp->pending_ack_count; i++) { + uint16_t id = etcp->pending_ack_ids[i]; + uint16_t ats = etcp->pending_ack_ts[i]; + buf[len++] = (id >> 8) & 0xFF; + buf[len++] = id & 0xFF; + buf[len++] = (ats >> 8) & 0xFF; + buf[len++] = ats & 0xFF; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: ACK[%u] - id=%u, ts=%u", i, id, ats); + } + + etcp->pending_ack_count = 0; + etcp->ack_packets_count++; + sections_added++; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added ACK section (total_len=%u)", len); + } + + // RETRANS (similar: count(1) + id(2)*count) + if (etcp->pending_retrans_count > 0) { + uint16_t ret_len = 1 + etcp->pending_retrans_count * 2; + buf[len++] = ETCP_SECTION_RETRANS; + buf[len++] = (ret_len >> 8) & 0xFF; + buf[len++] = ret_len & 0xFF; + buf[len++] = etcp->pending_retrans_count; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding RETRANS section with %u requests", etcp->pending_retrans_count); + + for (uint8_t i = 0; i < etcp->pending_retrans_count; i++) { + uint16_t id = etcp->pending_retrans_ids[i]; + buf[len++] = (id >> 8) & 0xFF; + buf[len++] = id & 0xFF; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: RETRANS[%u] - id=%u", i, id); + } + + etcp->pending_retrans_count = 0; + sections_added++; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added RETRANS section (total_len=%u)", len); + } + + // MEAS_TS if burst (assume [start_id(2)][n(1)]) + if (etcp->burst_in_progress) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: burst measurement in progress, would add MEAS_TS"); + // Logic for adding to burst packets + // Placeholder + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: completed - added %u sections, total_len=%u", + sections_added, len); + + return len; +} + +// Process ACK (expanded) +static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 1) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid length %u", len); + return; + } + uint8_t count = data[0]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: count=%u", count); + + if (1 + count * 4 > len) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid count %u for len %u", count, len); + return; + } + + uint16_t lp_ts = 0; + int acked_count = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: processing %u ACK entries", count); + + for (uint8_t i = 0; i < count; i++) { + uint16_t id = (data[1 + i*4] << 8) | data[2 + i*4]; + uint16_t ts = (data[3 + i*4] << 8) | data[4 + i*4]; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: entry %u - id=%u, ts=%u", i, id, ts); + + inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); + if (pkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: found inflight packet %u, removing (payload_len=%u)", + id, pkt->payload_len); + etcp->unacked_bytes -= pkt->payload_len; + remove_inflight(etcp, pkt); + free_inflight_packet(pkt); + lp_ts = ts; // Update last positive ts + acked_count++; + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: no inflight packet found for id=%u", id); + } + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: successfully ACKed %u/%u packets", acked_count, count); + // Use lp_ts in retrans_check if needed + etcp->last_rx_ack_id = (data[1 + (count-1)*4] << 8) | data[2 + (count-1)*4]; // Last ACK ID +} + +// Process RETRANS (expanded) +static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 1) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid length %u", len); + return; + } + uint8_t count = data[0]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: count=%u", count); + + if (1 + count * 2 > len) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid count %u for len %u", count, len); + return; + } + + int retrans_count = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: processing %u retrans requests", count); + + for (uint8_t i = 0; i < count; i++) { + uint16_t id = (data[1 + i*2] << 8) | data[2 + i*2]; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: requesting retrans for id=%u", id); + + inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); + if (pkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: found packet %u, marking for retransmission", id); + pkt->retrans_req_count++; + pkt->need_retrans = 1; + move_to_wait_send(etcp, pkt); + retrans_count++; + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: no inflight packet found for id=%u", id); + } + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: marked %u/%u packets for retransmission", retrans_count, count); +} + +// Process TIMESTAMP (expanded) +static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len != 2) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_timestamp: invalid length %u (expected 2)", len); + return; + } + uint16_t recv_ts = (data[0] << 8) | data[1]; + uint16_t current_ts = get_current_timestamp(); + uint16_t rtt = timestamp_diff(current_ts, recv_ts); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: recv_ts=%u, current_ts=%u, calculated_rtt=%u", + recv_ts, current_ts, rtt); + update_rtt(etcp, rtt); +} + +// Process PAYLOAD (expanded) +static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 2) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: invalid length %u (minimum 2)", len); + return; + } + uint16_t id = (data[0] << 8) | data[1]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u, last_rx_id=%u", id, etcp->last_rx_id); + + if (id <= etcp->last_rx_id) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u (last_rx_id=%u), ignoring", + id, etcp->last_rx_id); + return; // Dup + } + + rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t)); + if (!new_rx) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx_packet_t"); + return; + } + + new_rx->id = id; + new_rx->timestamp = get_current_timestamp(); + new_rx->data_len = len - 2; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: allocated rx_packet %p (id=%u, data_len=%u, timestamp=%u)", + new_rx, new_rx->id, new_rx->data_len, new_rx->timestamp); + + new_rx->data = malloc(new_rx->data_len); + if (!new_rx->data) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx data buffer (size=%u)", new_rx->data_len); + free(new_rx); + return; + } + memcpy(new_rx->data, data + 2, new_rx->data_len); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: copied %u bytes of payload data", new_rx->data_len); + + // Insert sorted (assume IDs sequential, no wrap for simplicity) + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: inserting packet into rx_list (id=%u)", id); + rx_packet_t** ptr = &etcp->rx_list; + while (*ptr && (*ptr)->id < id) ptr = &(*ptr)->next; + if (*ptr && (*ptr)->id == id) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u found in rx_list, discarding", id); + free(new_rx->data); + free(new_rx); + return; + } + new_rx->next = *ptr; + *ptr = new_rx; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: successfully inserted packet id=%u into rx_list", id); + + if (id > etcp->last_rx_id) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: updating last_rx_id from %u to %u", + etcp->last_rx_id, id); + etcp->last_rx_id = id; + } + + // Pending ACK + if (etcp->pending_ack_count < MAX_PENDING) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: adding packet id=%u to pending ACKs (count=%u)", + id, etcp->pending_ack_count + 1); + etcp->pending_ack_ids[etcp->pending_ack_count] = id; + etcp->pending_ack_ts[etcp->pending_ack_count++] = new_rx->timestamp; + } else { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: pending ACK buffer full (%u), cannot add id=%u", + MAX_PENDING, id); + } + + // Schedule ACK + if (!etcp->ack_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: scheduling ACK timer (delay=%u tb)", ACK_DELAY_TB); + etcp->ack_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_timer_cb); + } + + etcp->bytes_received_total += len; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u processed successfully, bytes_received_total=%u", + id, etcp->bytes_received_total); +} + +// Process MEAS_TS (expanded: send resp) +static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 3) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: invalid length %u (minimum 3)", len); + return; + } + uint16_t start_id = (data[0] << 8) | data[1]; + uint8_t n = data[2]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: start_id=%u, n=%u", start_id, n); + + // Send MEAS_RESP (add to pending or direct) + // Placeholder: assume added to next packet + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "MEAS_TS: start_id=%u n=%u", start_id, n); + // Build resp [start_id(2)][n(1)][recv_ts(2)] + // But since appended in optional, set state +} + +// Process MEAS_RESP (expanded: calc bw) +static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: etcp=%p, data=%p, len=%u", etcp, data, len); + + // Assume format [start_id(2)][n(1)][recv_ts(2)] + if (len < 5) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: invalid length %u (minimum 5)", len); + return; + } + uint16_t start_id = (data[0] << 8) | data[1]; + uint8_t n = data[2]; + uint16_t recv_ts = (data[3] << 8) | data[4]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: start_id=%u, n=%u, recv_ts=%u", start_id, n, recv_ts); + + if (etcp->burst_start_id != start_id || !etcp->burst_in_progress) return; + + // Calc bw based on burst (placeholder logic) + uint16_t delta = timestamp_diff(get_current_timestamp(), recv_ts); + uint32_t bw = (BURST_SIZE * etcp->mtu * 8) / (delta / 1000.0); // bits/sec approx + update_link_bandwidth(etcp->links, bw); // Assume primary link + etcp->burst_in_progress = 0; +} + +// Detect gaps (expanded) +static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: etcp=%p, last_delivered_id=%u", + etcp, etcp->last_delivered_id); + + rx_packet_t* rx = etcp->rx_list; + uint16_t expected = etcp->last_delivered_id + 1; + int gaps_found = 0; + int retrans_requested = 0; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: starting gap detection from expected_id=%u", expected); + + while (rx) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: checking rx packet id=%u (expected=%u)", + rx->id, expected); + + if (rx->id > expected) { + // Gap: add retrans for expected to rx->id -1 + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: gap detected - expected=%u, found=%u", + expected, rx->id); + gaps_found++; + + for (uint16_t gid = expected; gid < rx->id && etcp->pending_retrans_count < MAX_PENDING; gid++) { + etcp->pending_retrans_ids[etcp->pending_retrans_count++] = gid; + retrans_requested++; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: queued retrans request for id=%u", gid); + } + expected = rx->id + 1; + } else if (rx->id == expected) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: found expected packet id=%u", expected); + expected++; + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: packet id=%u < expected=%u, already processed", + rx->id, expected); + } + rx = rx->next; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: completed - gaps_found=%d, retrans_requested=%u", + gaps_found, retrans_requested); +} + +// Deliver contiguous (expanded) +static void deliver_contiguous_packets(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: etcp=%p, last_delivered_id=%u", + etcp, etcp->last_delivered_id); + + rx_packet_t** ptr = &etcp->rx_list; + int delivered_count = 0; + uint32_t delivered_bytes = 0; + + while (*ptr && (*ptr)->id == etcp->last_delivered_id + 1) { + rx_packet_t* rx = *ptr; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivering packet id=%u (data_len=%u)", + rx->id, rx->data_len); + + struct ll_entry* entry = queue_entry_new(rx->data_len); + if (entry) { + memcpy(ll_entry_data(entry), rx->data, rx->data_len); + queue_entry_put(etcp->output_queue, entry); + delivered_bytes += rx->data_len; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: queued packet id=%u to output_queue", rx->id); + } else { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: failed to allocate queue entry for packet id=%u", rx->id); + } + + etcp->last_delivered_id = rx->id; + *ptr = rx->next; + free(rx->data); + free(rx); + delivered_count++; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivered %u contiguous packets (%u bytes), last_delivered_id=%u", + delivered_count, delivered_bytes, etcp->last_delivered_id); +} + +// Retrans check (expanded with lp_ts) +static void retrans_check(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: etcp=%p, wait_ack_list=%p, rtt_avg_10=%u, jitter=%u", + etcp, etcp->wait_ack_list, etcp->rtt_avg_10, etcp->jitter); + + uint64_t now = get_current_time_units(); + inflight_packet_t** ptr = &etcp->wait_ack_list; + uint16_t lp_ts = 0; + int checked_packets = 0; + int retrans_packets = 0; + uint16_t timeout_calc = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: timeout calculation: rtt_avg_10=%u * %.1f + jitter=%u * %.1f = %u", + etcp->rtt_avg_10, RETRANS_K1, etcp->jitter, RETRANS_K2, timeout_calc); + + while (*ptr) { + inflight_packet_t* pkt = *ptr; + checked_packets++; + uint16_t timeout = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); + uint16_t time_since_send = timestamp_diff(now, pkt->last_timestamp); + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "retrans_check: checking packet id=%u (time_since_send=%u, timeout=%u, need_retrans=%u)", + pkt->id, time_since_send, timeout, pkt->need_retrans); + + if (time_since_send > timeout) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: packet id=%u timed out, marking for retransmission", pkt->id); + pkt->need_retrans = 1; + move_to_wait_send(etcp, pkt); + etcp->retransmissions_count++; + retrans_packets++; + ptr = &etcp->wait_ack_list; // Restart scan after move + continue; + } + // lp_ts logic (per spec: if acked, remember ts; if later unacked with ts < lp_ts, ?) + // Assume if pkt acked (but shouldn't be in list), or for forward progress + ptr = &pkt->next; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: completed - checked %d packets, retransmitted %d packets, total_retransmissions=%u", + checked_packets, retrans_packets, etcp->retransmissions_count); + + // Reschedule if list not empty + if (etcp->wait_ack_list) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: rescheduling retrans_timer (wait_ack_list not empty)"); + etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); + } else { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: clearing retrans_timer (wait_ack_list empty)"); + etcp->retrans_timer = NULL; + } +} + +// Find inflight (expanded) +static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: etcp=%p, id=%u, state=%u", etcp, id, state); + + inflight_packet_t* list = (state == INFLIGHT_STATE_WAIT_ACK) ? etcp->wait_ack_list : etcp->wait_send_list; + inflight_packet_t* pkt = list; + int position = 0; + + while (pkt && pkt->id != id) { + pkt = pkt->next; + position++; + } + + if (pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: found packet %u at position %d in %s list", + id, position, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: packet %u not found in %s list", + id, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + } + + return pkt; + return pkt; +} + +// Move to wait_send (expanded) +static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: etcp=%p, pkt=%p (id=%u, state=%u)", + etcp, pkt, pkt->id, pkt->state); + + remove_inflight(etcp, pkt); + pkt->state = INFLIGHT_STATE_WAIT_SEND; + pkt->next = etcp->wait_send_list; + etcp->wait_send_list = pkt; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: moved packet %u to wait_send_list", pkt->id); +} + +// Remove inflight (expanded) +static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: etcp=%p, pkt=%p (id=%u, state=%u)", + etcp, pkt, pkt->id, pkt->state); + + inflight_packet_t** list = (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? &etcp->wait_ack_list : &etcp->wait_send_list; + inflight_packet_t** ptr = list; + int position = 0; + + while (*ptr && *ptr != pkt) { + ptr = &(*ptr)->next; + position++; + } + + if (*ptr) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: found packet %u at position %d in %s list", + pkt->id, position, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + *ptr = (*ptr)->next; + } else { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "remove_inflight: packet %u not found in %s list", + pkt->id, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + } +} + +// Start burst (placeholder expanded) +static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "start_burst_measurement: etcp=%p, link=%p", etcp, link); + + // Periodically call this + etcp->burst_in_progress = 1; + uint16_t old_start_id = etcp->burst_start_id; + etcp->burst_start_id = etcp->next_tx_id; + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "start_burst_measurement: started burst measurement - burst_start_id=%u (was %u)", + etcp->burst_start_id, old_start_id); + + // Delay first, then burst 4 + // Assume handled in request_pkt by adding meas_ts to next BURST_SIZE pkts +} + +// Update link bw (expanded) +static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: link=%p, new_bw=%u bits/sec (current=%u)", + link, new_bw, link->bandwidth); + + uint32_t old_bw = link->bandwidth; + link->bandwidth = (uint32_t)((link->bandwidth * 0.9) + (new_bw * 0.1)); // Smooth + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: updated link bandwidth from %u to %u bits/sec (new_bw=%u)", + old_bw, link->bandwidth, new_bw); +} + +// Update RTT (expanded with history) +static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: etcp=%p, new_rtt=%u (last_rtt=%u, avg_10=%u, avg_100=%u)", + etcp, new_rtt, etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); + + etcp->rtt_last = new_rtt; + etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + new_rtt) / 10; + etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + new_rtt) / 100; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: updated RTT metrics - last=%u, avg_10=%u, avg_100=%u", + etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); + + // History for jitter + etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt; + if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "update_rtt: stored RTT in history at index %u", + (etcp->rtt_history_idx + RTT_HISTORY_SIZE - 1) % RTT_HISTORY_SIZE); + + calculate_jitter(etcp); +} + +static void calculate_jitter(struct ETCP_CONN* etcp) { + uint16_t min_rtt = UINT16_MAX; + uint16_t max_rtt = 0; + int valid_samples = 0; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: analyzing RTT history (history_idx=%u)", etcp->rtt_history_idx); + + for (int i = 0; i < RTT_HISTORY_SIZE; i++) { + if (etcp->rtt_history[i] == 0) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: history[%d] = 0 (invalid), skipping", i); + continue; + } + valid_samples++; + if (etcp->rtt_history[i] < min_rtt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new min_rtt=%u (was %u) at history[%d]", + etcp->rtt_history[i], min_rtt, i); + min_rtt = etcp->rtt_history[i]; + } + if (etcp->rtt_history[i] > max_rtt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new max_rtt=%u (was %u) at history[%d]", + etcp->rtt_history[i], max_rtt, i); + max_rtt = etcp->rtt_history[i]; + } + } + + uint16_t old_jitter = etcp->jitter; + etcp->jitter = max_rtt - min_rtt; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "calculate_jitter: completed - valid_samples=%d, min_rtt=%u, max_rtt=%u, jitter=%u (was %u)", + valid_samples, min_rtt, max_rtt, etcp->jitter, old_jitter); +} + +// Timers (expanded with debug) +static void retrans_timer_cb(void* arg) { + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_timer_cb: etcp=%p, triggered retransmission check", etcp); + retrans_check(etcp); +} + +static void ack_timer_cb(void* arg) { + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: etcp=%p, processing pending ACKs", etcp); + etcp->ack_timer = NULL; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: processing %u pending ACKs", etcp->pending_ack_count); + + // Force send ACK if pending (e.g., empty payload pkt) + // For now, assume appended next + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: ACK processing completed"); +} + +// Get stats (expanded) +void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, + size_t* pool_allocs, size_t* pool_reuse) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: etcp=%p", etcp); + + if (packets_sent) { + *packets_sent = etcp->total_packets_sent; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: total_packets_sent=%zu", *packets_sent); + } + if (packets_recv) { + *packets_recv = etcp->bytes_received_total / 1400; // Approx MTU + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: bytes_received_total=%u, estimated_packets=%zu", + etcp->bytes_received_total, *packets_recv); + } + if (pool_allocs || pool_reuse) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: getting memory pool stats"); + memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse); + if (pool_allocs) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_allocs=%zu", *pool_allocs); + if (pool_reuse) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_reuse=%zu", *pool_reuse); + } +} \ No newline at end of file diff --git a/!/etcp.c1 b/!/etcp.c1 new file mode 100755 index 00000000..5508a6d0 --- /dev/null +++ b/!/etcp.c1 @@ -0,0 +1,1147 @@ +// etcp.c - ETCP Protocol Implementation (refactored and expanded based on etcp_protocol.txt) +#include "etcp.h" +#include "etcp_loadbalancer.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include "crc32.h" +#include +#include +#include +#include // For bandwidth calcs + +// Enable comprehensive debug output for ETCP module +#define DEBUG_CATEGORY_ETCP_DETAILED 1 + +// Constants from spec (adjusted for completeness) +#define MAX_INFLIGHT_BYTES 65536 // Initial window +#define RETRANS_K1 2.0f // RTT multiplier for retrans timeout +#define RETRANS_K2 1.5f // Jitter multiplier +#define ACK_DELAY_TB 20 // ACK timer delay (2ms in 0.1ms units) +#define BURST_DELAY_FACTOR 4 // Delay before burst +#define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) +#define RTT_HISTORY_SIZE 10 // For jitter calc +#define MAX_PENDING 32 // For ACKs/retrans +#define SECTION_HEADER_SIZE 3 // type(1) + len(2) + +// Forward declaration for callback function +static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg); + +// Timestamp diff (with wrap-around) +static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2); + if (t1 >= t2) { + uint16_t diff = t1 - t2; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: normal case, diff=%u", diff); + return diff; + } + uint16_t diff = (0xFFFF - t2) + t1 + 1; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: wraparound case, diff=%u", diff); + return diff; +} + +// Get current time in 0.1ms units +uint64_t get_current_time_units() { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t time_units = ((uint64_t)tv.tv_sec * 10000ULL) + (tv.tv_usec / 100); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_time_units: tv_sec=%ld, tv_usec=%ld, result=%llu", + tv.tv_sec, tv.tv_usec, (unsigned long long)time_units); + return time_units; +} + +uint16_t get_current_timestamp() { + uint16_t timestamp = (uint16_t)(get_current_time_units() & 0xFFFF); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_timestamp: result=%u", timestamp); + return timestamp; +} + +// Internal functions (expanded) +static void retrans_timer_cb(void* arg); +static void ack_timer_cb(void* arg); +static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt); +static void calculate_jitter(struct ETCP_CONN* etcp); +static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp); +static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link); +static void deliver_contiguous_packets(struct ETCP_CONN* etcp); +static void retrans_check(struct ETCP_CONN* etcp); +static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state); +static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt); +static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt); +static void free_inflight_packet(inflight_packet_t* pkt); +static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link); +static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw); + +// Add RTT history to struct (for jitter) +/// In etcp.h, add to ETCP_CONN: +/// uint16_t rtt_history[RTT_HISTORY_SIZE]; +/// uint8_t rtt_history_idx; + +//======================================================================================================================================================================= +// Create ETCP connection (unchanged) +struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: instance=%p", instance); + + struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: failed to allocate ETCP_CONN"); + return NULL; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: allocated etcp=%p", etcp); + + etcp->instance = instance; + etcp->input_queue = queue_new(instance->ua, instance->pkt_pool); + etcp->output_queue = queue_new(instance->ua, instance->pkt_pool); + etcp->mtu = 1500; // Default + etcp->window_size = MAX_INFLIGHT_BYTES; + etcp->next_tx_id = 1; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, mtu=%d, window_size=%u, next_tx_id=%u", + etcp->mtu, etcp->window_size, etcp->next_tx_id); + + // Init crypto (as per original) + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: initializing crypto context"); + if (sc_init_ctx(&etcp->crypto_ctx, &instance->my_keys) != SC_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: crypto initialization failed"); + etcp_connection_close(etcp); + return NULL; + } + + // Add connection to instance list + etcp->next = instance->connections; + instance->connections = etcp; + instance->connections_count++; + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_create: successfully created ETCP connection %p", etcp); + return etcp; +} +//======================================================================================================================================================================= +// Close connection (expanded free) +void etcp_connection_close(struct ETCP_CONN* etcp) { + if (!etcp) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_connection_close: called with NULL etcp"); + return; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: closing connection %p", etcp); + + // Remove connection from instance list + if (etcp->instance) { + struct ETCP_CONN** prev = &etcp->instance->connections; + struct ETCP_CONN* cur = *prev; + while (cur) { + if (cur == etcp) { + *prev = cur->next; + etcp->instance->connections_count--; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: removed from instance list, count=%d", + etcp->instance->connections_count); + break; + } + prev = &cur->next; + cur = cur->next; + } + } + + // Cancel timers + if (etcp->retrans_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling retrans_timer"); + uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); + } + if (etcp->ack_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling ack_timer"); + uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); + } + + // Free inflight + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_ack_list"); + inflight_packet_t* pkt = etcp->wait_ack_list; + int wait_ack_count = 0; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + wait_ack_count++; + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_ack_list", wait_ack_count); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_send_list"); + pkt = etcp->wait_send_list; + int wait_send_count = 0; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + wait_send_count++; + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_send_list", wait_send_count); + + // Free rx_list + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing rx_list"); + rx_packet_t* rx = etcp->rx_list; + int rx_count = 0; + while (rx) { + rx_packet_t* next = rx->next; + free(rx->data); + free(rx); + rx = next; + rx_count++; + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from rx_list", rx_count); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing queues"); + queue_free(etcp->input_queue); + queue_free(etcp->output_queue); + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_close: connection %p closed successfully", etcp); + free(etcp); +} + +//======================================================================================================================================================================= + +static void free_inflight_packet(inflight_packet_t* pkt) { + if (pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "free_inflight_packet: freeing packet %p (id=%u, data_len=%u)", + pkt, pkt->id, pkt->data_len); + free(pkt->data); + free(pkt); + } +} + +//======================================================================================================================================================================= + +// Reset connection (unchanged) +void etcp_conn_reset(struct ETCP_CONN* etcp) { + if (!etcp) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: called with NULL etcp"); + return; + } + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: resetting connection %p", etcp); + + // Cancel timers + if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); + if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); + + // Free lists + inflight_packet_t* pkt = etcp->wait_ack_list; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + } + etcp->wait_ack_list = NULL; + pkt = etcp->wait_send_list; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + } + etcp->wait_send_list = NULL; + + rx_packet_t* rx = etcp->rx_list; + while (rx) { + rx_packet_t* next = rx->next; + free(rx->data); + free(rx); + rx = next; + } + etcp->rx_list = NULL; + + // Reset state + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: clearing packet lists and resetting state"); + etcp->next_tx_id = 1; + etcp->last_rx_id = 0; + etcp->last_delivered_id = 0; + etcp->unacked_bytes = 0; + etcp->pending_ack_count = 0; + etcp->pending_retrans_count = 0; + etcp->rtt_last = 0; + etcp->rtt_avg_10 = 0; + etcp->rtt_avg_100 = 0; + etcp->jitter = 0; + etcp->burst_in_progress = 0; + etcp->rtt_history_idx = 0; + memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); + // Queues cleared externally if needed + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: connection %p reset completed", etcp); +} + +//======================================================================================================================================================================= +// PACKET INPUT processing +//======================================================================================================================================================================= + + +// Input decrypted packet (unchanged structure, but calls expanded processes) +void etcp_conn_input(struct ETCP_DGRAM* pkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p, link=%p, etcp=%p", + pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); + + if (!pkt || !pkt->link || !pkt->link->etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: invalid parameters (pkt=%p, link=%p, etcp=%p)", + pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); + return; + } + + struct ETCP_CONN* etcp = pkt->link->etcp; + uint8_t* data = pkt->data; + uint16_t len = pkt->data_len; + uint16_t ts = pkt->timestamp; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: processing packet - etcp=%p, data_len=%u, timestamp=%u, link=%p", + etcp, len, ts, pkt->link); + + // Update link activity + pkt->link->last_activity = get_current_time_units(); + pkt->link->last_recv_local_time = pkt->link->last_activity; + pkt->link->last_recv_timestamp = ts; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_conn_input: updated link activity - last_activity=%llu, last_recv_timestamp=%u", + (unsigned long long)pkt->link->last_activity, pkt->link->last_recv_timestamp); + + // Parse sections + uint16_t pos = 0; + while (pos < len) { + if (pos + SECTION_HEADER_SIZE > len) break; + + uint8_t type = data[pos]; + uint16_t sec_len = (data[pos+1] << 8) | data[pos+2]; + pos += SECTION_HEADER_SIZE; + + if (pos + sec_len > len) break; + + uint8_t* sec_data = data + pos; + switch (type) { + case ETCP_SECTION_ACK: + process_section_ack(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_RETRANS: + process_section_retrans(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_TIMESTAMP: + process_section_timestamp(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_PAYLOAD: + process_section_payload(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_MEAS_TS: + process_section_meas_ts(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_MEAS_RESP: + process_section_meas_resp(etcp, sec_data, sec_len); + break; + default: + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Unknown section type: 0x%02x", type); + break; + } + pos += sec_len; + } + + // Post-processing + detect_gaps_and_request_retrans(etcp); + deliver_contiguous_packets(etcp); + + // Trigger if waiting + if (etcp->wait_timeout_active) { + etcp->wait_timeout_active = 0; + // Assume load balancer polls again + } +} + +// Request next packet (expanded) +// source 1: wait_ack_list +// source 2: queue > wait_ack_list + +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: etcp=%p", etcp); + + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: called with NULL etcp"); + return NULL; + } + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: input_queue_count=%d, wait_send_list=%p, unacked_bytes=%u, window_size=%u", + queue_entry_count(etcp->input_queue), etcp->wait_send_list, + etcp->unacked_bytes, etcp->window_size); + + // Check waiting conditions + if (queue_entry_count(etcp->input_queue) == 0 && etcp->wait_send_list == NULL) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no packets available, setting callback and returning NULL"); + // Wait for input or retrans + // Set callback for resume + queue_set_callback(etcp->input_queue, input_queue_cb, etcp); // Define cb to resume + return NULL; + } + + if (etcp->unacked_bytes >= etcp->window_size) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: window full (unacked_bytes=%u >= window_size=%u), waiting", + etcp->unacked_bytes, etcp->window_size); + // Wait ACK or timeout + etcp->wait_timeout_active = 1; + return NULL; + } + + if (etcp->unacked_bytes >= etcp->window_size) { + // Wait ACK or timeout + etcp->wait_timeout_active = 1; + return NULL; + } + + // Get packet (prefer retrans) + inflight_packet_t* ipkt = etcp->wait_send_list; + if (ipkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: using retransmission packet id=%u", ipkt->id); + remove_inflight(etcp, ipkt); + } else { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: getting new packet from input queue"); + struct ll_entry* entry = queue_entry_get(etcp->input_queue); + if (!entry) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no entry available from input queue"); + return NULL; + } + + ipkt = calloc(1, sizeof(inflight_packet_t)); + if (!ipkt) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate inflight_packet_t"); + queue_entry_put_first(etcp->input_queue, entry); + return NULL; + } + + ipkt->id = etcp->next_tx_id++; + ipkt->data_len = ll_entry_size(entry); + ipkt->data = malloc(ipkt->data_len); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: created new packet id=%u, data_len=%u", + ipkt->id, ipkt->data_len); + + if (!ipkt->data) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate packet data buffer"); + free(ipkt); + queue_entry_put_first(etcp->input_queue, entry); + return NULL; + } + memcpy(ipkt->data, ll_entry_data(entry), ipkt->data_len); + ipkt->payload_len = ipkt->data_len; // Assume full is payload for window + queue_entry_free(entry); + } + + // Select link + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selecting link via loadbalancer"); + struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); + if (!link) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no suitable link available"); + // Put back if new + if (ipkt->send_count == 0) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: putting new packet back to input queue"); + struct ll_entry* entry = queue_entry_new(ipkt->data_len); + memcpy(ll_entry_data(entry), ipkt->data, ipkt->data_len); + queue_entry_put_first(etcp->input_queue, entry); + } + free_inflight_packet(ipkt); + return NULL; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selected link %p", link); + + // Build packet buf + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: building packet with optional sections"); + uint8_t buf[1600]; + uint16_t len = append_optional_sections(etcp, buf, link); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: optional sections added, len=%u", len); + + // Add payload (assume payload includes ID as first 2 bytes) + uint16_t pay_len = ipkt->payload_len + 2; // ID + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload section - pay_len=%u (payload_len=%u + 2 for ID)", + pay_len, ipkt->payload_len); + + if (len + SECTION_HEADER_SIZE + pay_len > sizeof(buf)) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: packet too large (%u + %u + %u > %u), dropping", + len, SECTION_HEADER_SIZE, pay_len, (unsigned)sizeof(buf)); + free_inflight_packet(ipkt); + return NULL; + } + buf[len++] = ETCP_SECTION_PAYLOAD; + buf[len++] = (pay_len >> 8) & 0xFF; + buf[len++] = pay_len & 0xFF; + buf[len++] = (ipkt->id >> 8) & 0xFF; + buf[len++] = ipkt->id & 0xFF; + memcpy(buf + len, ipkt->data, ipkt->payload_len); + len += ipkt->payload_len; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload added, total len=%u", len); + + // Alloc dgram + struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); + if (!dgram) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate dgram from memory pool"); + free_inflight_packet(ipkt); + return NULL; + } + dgram->link = link; + dgram->data_len = len; + dgram->noencrypt_len = 0; + dgram->timestamp = get_current_timestamp(); + memcpy(dgram->data, buf, len); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: allocated dgram %p (link=%p, data_len=%u, timestamp=%u)", + dgram, link, len, dgram->timestamp); + + // Update inflight + ipkt->last_link = link; + ipkt->last_timestamp = dgram->timestamp; + ipkt->send_count++; + etcp->unacked_bytes += ipkt->payload_len; + etcp->bytes_sent_total += len; // Approx + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: updated inflight - send_count=%u, unacked_bytes=%u, bytes_sent_total=%u", + ipkt->send_count, etcp->unacked_bytes, etcp->bytes_sent_total); + + // Move to wait_ack + ipkt->state = INFLIGHT_STATE_WAIT_ACK; + ipkt->next = etcp->wait_ack_list; + etcp->wait_ack_list = ipkt; + + // Add to wait_ack list + ipkt->next = etcp->wait_ack_list; + etcp->wait_ack_list = ipkt; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: added packet %u to wait_ack_list", ipkt->id); + + // Start retrans timer if empty before + if (etcp->wait_ack_list->next == NULL && !etcp->retrans_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: starting retrans_timer (wait_ack_list was empty)"); + etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); + } + + // Update load balancer after send (call from connections after actual send) + // etcp_loadbalancer_update_after_send(link, len + headers); // Assume called externally + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: successfully created dgram %p for packet id=%u", + dgram, ipkt->id); + return dgram; +} + +// Input queue callback (placeholder to resume load balancer) +static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg) { + (void)q; + (void)entry; + struct ETCP_CONN* etcp = arg; + // Resume polling etcp_request_pkt + queue_resume_callback(q); +} + +// Append optional (expanded with debug) +static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: etcp=%p, link=%p", etcp, link); + + uint16_t len = 0; + int sections_added = 0; + + // Timestamp (assume 2 bytes TS) + uint16_t ts = get_current_timestamp(); + buf[len++] = ETCP_SECTION_TIMESTAMP; + buf[len++] = 0; // len high + buf[len++] = 2; // len low + buf[len++] = (ts >> 8) & 0xFF; + buf[len++] = ts & 0xFF; + sections_added++; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: added TIMESTAMP section (ts=%u, len=%u)", ts, len); + + // ACK (assume count(1) + [id(2)+ts(2)]*count) + if (etcp->pending_ack_count > 0) { + uint16_t ack_len = 1 + etcp->pending_ack_count * 4; + buf[len++] = ETCP_SECTION_ACK; + buf[len++] = (ack_len >> 8) & 0xFF; + buf[len++] = ack_len & 0xFF; + buf[len++] = etcp->pending_ack_count; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding ACK section with %u ACKs", etcp->pending_ack_count); + + for (uint8_t i = 0; i < etcp->pending_ack_count; i++) { + uint16_t id = etcp->pending_ack_ids[i]; + uint16_t ats = etcp->pending_ack_ts[i]; + buf[len++] = (id >> 8) & 0xFF; + buf[len++] = id & 0xFF; + buf[len++] = (ats >> 8) & 0xFF; + buf[len++] = ats & 0xFF; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: ACK[%u] - id=%u, ts=%u", i, id, ats); + } + + etcp->pending_ack_count = 0; + etcp->ack_packets_count++; + sections_added++; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added ACK section (total_len=%u)", len); + } + + // RETRANS (similar: count(1) + id(2)*count) + if (etcp->pending_retrans_count > 0) { + uint16_t ret_len = 1 + etcp->pending_retrans_count * 2; + buf[len++] = ETCP_SECTION_RETRANS; + buf[len++] = (ret_len >> 8) & 0xFF; + buf[len++] = ret_len & 0xFF; + buf[len++] = etcp->pending_retrans_count; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding RETRANS section with %u requests", etcp->pending_retrans_count); + + for (uint8_t i = 0; i < etcp->pending_retrans_count; i++) { + uint16_t id = etcp->pending_retrans_ids[i]; + buf[len++] = (id >> 8) & 0xFF; + buf[len++] = id & 0xFF; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: RETRANS[%u] - id=%u", i, id); + } + + etcp->pending_retrans_count = 0; + sections_added++; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added RETRANS section (total_len=%u)", len); + } + + // MEAS_TS if burst (assume [start_id(2)][n(1)]) + if (etcp->burst_in_progress) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: burst measurement in progress, would add MEAS_TS"); + // Logic for adding to burst packets + // Placeholder + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: completed - added %u sections, total_len=%u", + sections_added, len); + + return len; +} + +// Process ACK (expanded) +static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 1) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid length %u", len); + return; + } + uint8_t count = data[0]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: count=%u", count); + + if (1 + count * 4 > len) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid count %u for len %u", count, len); + return; + } + + uint16_t lp_ts = 0; + int acked_count = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: processing %u ACK entries", count); + + for (uint8_t i = 0; i < count; i++) { + uint16_t id = (data[1 + i*4] << 8) | data[2 + i*4]; + uint16_t ts = (data[3 + i*4] << 8) | data[4 + i*4]; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: entry %u - id=%u, ts=%u", i, id, ts); + + inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); + if (pkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: found inflight packet %u, removing (payload_len=%u)", + id, pkt->payload_len); + etcp->unacked_bytes -= pkt->payload_len; + remove_inflight(etcp, pkt); + free_inflight_packet(pkt); + lp_ts = ts; // Update last positive ts + acked_count++; + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: no inflight packet found for id=%u", id); + } + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: successfully ACKed %u/%u packets", acked_count, count); + // Use lp_ts in retrans_check if needed + etcp->last_rx_ack_id = (data[1 + (count-1)*4] << 8) | data[2 + (count-1)*4]; // Last ACK ID +} + +// Process RETRANS (expanded) +static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 1) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid length %u", len); + return; + } + uint8_t count = data[0]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: count=%u", count); + + if (1 + count * 2 > len) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid count %u for len %u", count, len); + return; + } + + int retrans_count = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: processing %u retrans requests", count); + + for (uint8_t i = 0; i < count; i++) { + uint16_t id = (data[1 + i*2] << 8) | data[2 + i*2]; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: requesting retrans for id=%u", id); + + inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); + if (pkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: found packet %u, marking for retransmission", id); + pkt->retrans_req_count++; + pkt->need_retrans = 1; + move_to_wait_send(etcp, pkt); + retrans_count++; + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: no inflight packet found for id=%u", id); + } + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: marked %u/%u packets for retransmission", retrans_count, count); +} + +// Process TIMESTAMP (expanded) +static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len != 2) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_timestamp: invalid length %u (expected 2)", len); + return; + } + uint16_t recv_ts = (data[0] << 8) | data[1]; + uint16_t current_ts = get_current_timestamp(); + uint16_t rtt = timestamp_diff(current_ts, recv_ts); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: recv_ts=%u, current_ts=%u, calculated_rtt=%u", + recv_ts, current_ts, rtt); + update_rtt(etcp, rtt); +} + +// Process PAYLOAD (expanded) +static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 2) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: invalid length %u (minimum 2)", len); + return; + } + uint16_t id = (data[0] << 8) | data[1]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u, last_rx_id=%u", id, etcp->last_rx_id); + + if (id <= etcp->last_rx_id) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u (last_rx_id=%u), ignoring", + id, etcp->last_rx_id); + return; // Dup + } + + rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t)); + if (!new_rx) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx_packet_t"); + return; + } + + new_rx->id = id; + new_rx->timestamp = get_current_timestamp(); + new_rx->data_len = len - 2; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: allocated rx_packet %p (id=%u, data_len=%u, timestamp=%u)", + new_rx, new_rx->id, new_rx->data_len, new_rx->timestamp); + + new_rx->data = malloc(new_rx->data_len); + if (!new_rx->data) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx data buffer (size=%u)", new_rx->data_len); + free(new_rx); + return; + } + memcpy(new_rx->data, data + 2, new_rx->data_len); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: copied %u bytes of payload data", new_rx->data_len); + + // Insert sorted (assume IDs sequential, no wrap for simplicity) + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: inserting packet into rx_list (id=%u)", id); + rx_packet_t** ptr = &etcp->rx_list; + while (*ptr && (*ptr)->id < id) ptr = &(*ptr)->next; + if (*ptr && (*ptr)->id == id) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u found in rx_list, discarding", id); + free(new_rx->data); + free(new_rx); + return; + } + new_rx->next = *ptr; + *ptr = new_rx; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: successfully inserted packet id=%u into rx_list", id); + + if (id > etcp->last_rx_id) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: updating last_rx_id from %u to %u", + etcp->last_rx_id, id); + etcp->last_rx_id = id; + } + + // Pending ACK + if (etcp->pending_ack_count < MAX_PENDING) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: adding packet id=%u to pending ACKs (count=%u)", + id, etcp->pending_ack_count + 1); + etcp->pending_ack_ids[etcp->pending_ack_count] = id; + etcp->pending_ack_ts[etcp->pending_ack_count++] = new_rx->timestamp; + } else { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: pending ACK buffer full (%u), cannot add id=%u", + MAX_PENDING, id); + } + + // Schedule ACK + if (!etcp->ack_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: scheduling ACK timer (delay=%u tb)", ACK_DELAY_TB); + etcp->ack_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_timer_cb); + } + + etcp->bytes_received_total += len; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u processed successfully, bytes_received_total=%u", + id, etcp->bytes_received_total); +} + +// Process MEAS_TS (expanded: send resp) +static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 3) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: invalid length %u (minimum 3)", len); + return; + } + uint16_t start_id = (data[0] << 8) | data[1]; + uint8_t n = data[2]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: start_id=%u, n=%u", start_id, n); + + // Send MEAS_RESP (add to pending or direct) + // Placeholder: assume added to next packet + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "MEAS_TS: start_id=%u n=%u", start_id, n); + // Build resp [start_id(2)][n(1)][recv_ts(2)] + // But since appended in optional, set state +} + +// Process MEAS_RESP (expanded: calc bw) +static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: etcp=%p, data=%p, len=%u", etcp, data, len); + + // Assume format [start_id(2)][n(1)][recv_ts(2)] + if (len < 5) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: invalid length %u (minimum 5)", len); + return; + } + uint16_t start_id = (data[0] << 8) | data[1]; + uint8_t n = data[2]; + uint16_t recv_ts = (data[3] << 8) | data[4]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: start_id=%u, n=%u, recv_ts=%u", start_id, n, recv_ts); + + if (etcp->burst_start_id != start_id || !etcp->burst_in_progress) return; + + // Calc bw based on burst (placeholder logic) + uint16_t delta = timestamp_diff(get_current_timestamp(), recv_ts); + uint32_t bw = (BURST_SIZE * etcp->mtu * 8) / (delta / 1000.0); // bits/sec approx + update_link_bandwidth(etcp->links, bw); // Assume primary link + etcp->burst_in_progress = 0; +} + +// Detect gaps (expanded) +static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: etcp=%p, last_delivered_id=%u", + etcp, etcp->last_delivered_id); + + rx_packet_t* rx = etcp->rx_list; + uint16_t expected = etcp->last_delivered_id + 1; + int gaps_found = 0; + int retrans_requested = 0; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: starting gap detection from expected_id=%u", expected); + + while (rx) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: checking rx packet id=%u (expected=%u)", + rx->id, expected); + + if (rx->id > expected) { + // Gap: add retrans for expected to rx->id -1 + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: gap detected - expected=%u, found=%u", + expected, rx->id); + gaps_found++; + + for (uint16_t gid = expected; gid < rx->id && etcp->pending_retrans_count < MAX_PENDING; gid++) { + etcp->pending_retrans_ids[etcp->pending_retrans_count++] = gid; + retrans_requested++; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: queued retrans request for id=%u", gid); + } + expected = rx->id + 1; + } else if (rx->id == expected) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: found expected packet id=%u", expected); + expected++; + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: packet id=%u < expected=%u, already processed", + rx->id, expected); + } + rx = rx->next; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: completed - gaps_found=%d, retrans_requested=%u", + gaps_found, retrans_requested); +} + +// Deliver contiguous (expanded) +static void deliver_contiguous_packets(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: etcp=%p, last_delivered_id=%u", + etcp, etcp->last_delivered_id); + + rx_packet_t** ptr = &etcp->rx_list; + int delivered_count = 0; + uint32_t delivered_bytes = 0; + + while (*ptr && (*ptr)->id == etcp->last_delivered_id + 1) { + rx_packet_t* rx = *ptr; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivering packet id=%u (data_len=%u)", + rx->id, rx->data_len); + + struct ll_entry* entry = queue_entry_new(rx->data_len); + if (entry) { + memcpy(ll_entry_data(entry), rx->data, rx->data_len); + queue_entry_put(etcp->output_queue, entry); + delivered_bytes += rx->data_len; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: queued packet id=%u to output_queue", rx->id); + } else { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: failed to allocate queue entry for packet id=%u", rx->id); + } + + etcp->last_delivered_id = rx->id; + *ptr = rx->next; + free(rx->data); + free(rx); + delivered_count++; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivered %u contiguous packets (%u bytes), last_delivered_id=%u", + delivered_count, delivered_bytes, etcp->last_delivered_id); +} + +// Retrans check (expanded with lp_ts) +static void retrans_check(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: etcp=%p, wait_ack_list=%p, rtt_avg_10=%u, jitter=%u", + etcp, etcp->wait_ack_list, etcp->rtt_avg_10, etcp->jitter); + + uint64_t now = get_current_time_units(); + inflight_packet_t** ptr = &etcp->wait_ack_list; + uint16_t lp_ts = 0; + int checked_packets = 0; + int retrans_packets = 0; + uint16_t timeout_calc = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: timeout calculation: rtt_avg_10=%u * %.1f + jitter=%u * %.1f = %u", + etcp->rtt_avg_10, RETRANS_K1, etcp->jitter, RETRANS_K2, timeout_calc); + + while (*ptr) { + inflight_packet_t* pkt = *ptr; + checked_packets++; + uint16_t timeout = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); + uint16_t time_since_send = timestamp_diff(now, pkt->last_timestamp); + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "retrans_check: checking packet id=%u (time_since_send=%u, timeout=%u, need_retrans=%u)", + pkt->id, time_since_send, timeout, pkt->need_retrans); + + if (time_since_send > timeout) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: packet id=%u timed out, marking for retransmission", pkt->id); + pkt->need_retrans = 1; + move_to_wait_send(etcp, pkt); + etcp->retransmissions_count++; + retrans_packets++; + ptr = &etcp->wait_ack_list; // Restart scan after move + continue; + } + // lp_ts logic (per spec: if acked, remember ts; if later unacked with ts < lp_ts, ?) + // Assume if pkt acked (but shouldn't be in list), or for forward progress + ptr = &pkt->next; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: completed - checked %d packets, retransmitted %d packets, total_retransmissions=%u", + checked_packets, retrans_packets, etcp->retransmissions_count); + + // Reschedule if list not empty + if (etcp->wait_ack_list) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: rescheduling retrans_timer (wait_ack_list not empty)"); + etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); + } else { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: clearing retrans_timer (wait_ack_list empty)"); + etcp->retrans_timer = NULL; + } +} + +// Find inflight (expanded) +static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: etcp=%p, id=%u, state=%u", etcp, id, state); + + inflight_packet_t* list = (state == INFLIGHT_STATE_WAIT_ACK) ? etcp->wait_ack_list : etcp->wait_send_list; + inflight_packet_t* pkt = list; + int position = 0; + + while (pkt && pkt->id != id) { + pkt = pkt->next; + position++; + } + + if (pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: found packet %u at position %d in %s list", + id, position, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: packet %u not found in %s list", + id, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + } + + return pkt; + return pkt; +} + +// Move to wait_send (expanded) +static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: etcp=%p, pkt=%p (id=%u, state=%u)", + etcp, pkt, pkt->id, pkt->state); + + remove_inflight(etcp, pkt); + pkt->state = INFLIGHT_STATE_WAIT_SEND; + pkt->next = etcp->wait_send_list; + etcp->wait_send_list = pkt; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: moved packet %u to wait_send_list", pkt->id); +} + +// Remove inflight (expanded) +static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: etcp=%p, pkt=%p (id=%u, state=%u)", + etcp, pkt, pkt->id, pkt->state); + + inflight_packet_t** list = (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? &etcp->wait_ack_list : &etcp->wait_send_list; + inflight_packet_t** ptr = list; + int position = 0; + + while (*ptr && *ptr != pkt) { + ptr = &(*ptr)->next; + position++; + } + + if (*ptr) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: found packet %u at position %d in %s list", + pkt->id, position, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + *ptr = (*ptr)->next; + } else { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "remove_inflight: packet %u not found in %s list", + pkt->id, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + } +} + +// Start burst (placeholder expanded) +static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "start_burst_measurement: etcp=%p, link=%p", etcp, link); + + // Periodically call this + etcp->burst_in_progress = 1; + uint16_t old_start_id = etcp->burst_start_id; + etcp->burst_start_id = etcp->next_tx_id; + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "start_burst_measurement: started burst measurement - burst_start_id=%u (was %u)", + etcp->burst_start_id, old_start_id); + + // Delay first, then burst 4 + // Assume handled in request_pkt by adding meas_ts to next BURST_SIZE pkts +} + +// Update link bw (expanded) +static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: link=%p, new_bw=%u bits/sec (current=%u)", + link, new_bw, link->bandwidth); + + uint32_t old_bw = link->bandwidth; + link->bandwidth = (uint32_t)((link->bandwidth * 0.9) + (new_bw * 0.1)); // Smooth + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: updated link bandwidth from %u to %u bits/sec (new_bw=%u)", + old_bw, link->bandwidth, new_bw); +} + +// Update RTT (expanded with history) +static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: etcp=%p, new_rtt=%u (last_rtt=%u, avg_10=%u, avg_100=%u)", + etcp, new_rtt, etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); + + etcp->rtt_last = new_rtt; + etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + new_rtt) / 10; + etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + new_rtt) / 100; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: updated RTT metrics - last=%u, avg_10=%u, avg_100=%u", + etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); + + // History for jitter + etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt; + if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "update_rtt: stored RTT in history at index %u", + (etcp->rtt_history_idx + RTT_HISTORY_SIZE - 1) % RTT_HISTORY_SIZE); + + calculate_jitter(etcp); +} + +static void calculate_jitter(struct ETCP_CONN* etcp) { + uint16_t min_rtt = UINT16_MAX; + uint16_t max_rtt = 0; + int valid_samples = 0; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: analyzing RTT history (history_idx=%u)", etcp->rtt_history_idx); + + for (int i = 0; i < RTT_HISTORY_SIZE; i++) { + if (etcp->rtt_history[i] == 0) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: history[%d] = 0 (invalid), skipping", i); + continue; + } + valid_samples++; + if (etcp->rtt_history[i] < min_rtt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new min_rtt=%u (was %u) at history[%d]", + etcp->rtt_history[i], min_rtt, i); + min_rtt = etcp->rtt_history[i]; + } + if (etcp->rtt_history[i] > max_rtt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new max_rtt=%u (was %u) at history[%d]", + etcp->rtt_history[i], max_rtt, i); + max_rtt = etcp->rtt_history[i]; + } + } + + uint16_t old_jitter = etcp->jitter; + etcp->jitter = max_rtt - min_rtt; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "calculate_jitter: completed - valid_samples=%d, min_rtt=%u, max_rtt=%u, jitter=%u (was %u)", + valid_samples, min_rtt, max_rtt, etcp->jitter, old_jitter); +} + +// Timers (expanded with debug) +static void retrans_timer_cb(void* arg) { + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_timer_cb: etcp=%p, triggered retransmission check", etcp); + retrans_check(etcp); +} + +static void ack_timer_cb(void* arg) { + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: etcp=%p, processing pending ACKs", etcp); + etcp->ack_timer = NULL; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: processing %u pending ACKs", etcp->pending_ack_count); + + // Force send ACK if pending (e.g., empty payload pkt) + // For now, assume appended next + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: ACK processing completed"); +} + +// Get stats (expanded) +void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, + size_t* pool_allocs, size_t* pool_reuse) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: etcp=%p", etcp); + + if (packets_sent) { + *packets_sent = etcp->total_packets_sent; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: total_packets_sent=%zu", *packets_sent); + } + if (packets_recv) { + *packets_recv = etcp->bytes_received_total / 1400; // Approx MTU + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: bytes_received_total=%u, estimated_packets=%zu", + etcp->bytes_received_total, *packets_recv); + } + if (pool_allocs || pool_reuse) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: getting memory pool stats"); + memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse); + if (pool_allocs) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_allocs=%zu", *pool_allocs); + if (pool_reuse) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_reuse=%zu", *pool_reuse); + } +} \ No newline at end of file diff --git a/!/etcp.c2 b/!/etcp.c2 new file mode 100755 index 00000000..49711d17 --- /dev/null +++ b/!/etcp.c2 @@ -0,0 +1,1161 @@ +// etcp.c - ETCP Protocol Implementation (refactored and expanded based on etcp_protocol.txt) +#include "etcp.h" +#include "etcp_loadbalancer.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include "crc32.h" +#include +#include +#include +#include // For bandwidth calcs + +// Enable comprehensive debug output for ETCP module +#define DEBUG_CATEGORY_ETCP_DETAILED 1 + +// Constants from spec (adjusted for completeness) +#define MAX_INFLIGHT_BYTES 65536 // Initial window +#define RETRANS_K1 2.0f // RTT multiplier for retrans timeout +#define RETRANS_K2 1.5f // Jitter multiplier +#define ACK_DELAY_TB 20 // ACK timer delay (2ms in 0.1ms units) +#define BURST_DELAY_FACTOR 4 // Delay before burst +#define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) +#define RTT_HISTORY_SIZE 10 // For jitter calc +#define MAX_PENDING 32 // For ACKs/retrans +#define SECTION_HEADER_SIZE 3 // type(1) + len(2) + +// Forward declaration for callback function +static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg); + +// Timestamp diff (with wrap-around) +static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2); + if (t1 >= t2) { + uint16_t diff = t1 - t2; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: normal case, diff=%u", diff); + return diff; + } + uint16_t diff = (0xFFFF - t2) + t1 + 1; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: wraparound case, diff=%u", diff); + return diff; +} + +// Get current time in 0.1ms units +uint64_t get_current_time_units() { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t time_units = ((uint64_t)tv.tv_sec * 10000ULL) + (tv.tv_usec / 100); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_time_units: tv_sec=%ld, tv_usec=%ld, result=%llu", + tv.tv_sec, tv.tv_usec, (unsigned long long)time_units); + return time_units; +} + +uint16_t get_current_timestamp() { + uint16_t timestamp = (uint16_t)(get_current_time_units() & 0xFFFF); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_timestamp: result=%u", timestamp); + return timestamp; +} + +// Internal functions (expanded) +static void retrans_timer_cb(void* arg); +static void ack_timer_cb(void* arg); +static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt); +static void calculate_jitter(struct ETCP_CONN* etcp); +static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp); +static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); +static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link); +static void deliver_contiguous_packets(struct ETCP_CONN* etcp); +static void retrans_check(struct ETCP_CONN* etcp); +static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state); +static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt); +static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt); +static void free_inflight_packet(inflight_packet_t* pkt); +static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link); +static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw); + +// Add RTT history to struct (for jitter) +/// In etcp.h, add to ETCP_CONN: +/// uint16_t rtt_history[RTT_HISTORY_SIZE]; +/// uint8_t rtt_history_idx; + +//======================================================================================================================================================================= +// Create ETCP connection (unchanged) +struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: instance=%p", instance); + + struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: failed to allocate ETCP_CONN"); + return NULL; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: allocated etcp=%p", etcp); + + etcp->instance = instance; + etcp->input_queue = queue_new(instance->ua, instance->pkt_pool); + etcp->output_queue = queue_new(instance->ua, instance->pkt_pool); + etcp->mtu = 1500; // Default + etcp->window_size = MAX_INFLIGHT_BYTES; + etcp->next_tx_id = 1; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, mtu=%d, window_size=%u, next_tx_id=%u", + etcp->mtu, etcp->window_size, etcp->next_tx_id); + + // Init crypto (as per original) + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: initializing crypto context"); + if (sc_init_ctx(&etcp->crypto_ctx, &instance->my_keys) != SC_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: crypto initialization failed"); + goto conn_create_err; + } + +// создаём очереди + + if (!etcp->inflight_pool=memory_pool_init(sizeof(struct INFLIGHT_PACKET))) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: inflight pool alloc error"); goto conn_create_err; } + if (!etcp->input_send_q=queue_new(instance->ua, etcp->inflight_pool)) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: send q alloc error"); goto conn_create_err; } + if (!etcp->input_wait_ack=queue_new(instance->ua, etcp->inflight_pool)) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: wait q alloc error"); goto conn_create_err; } + + // Add connection to instance list + etcp->next = instance->connections; + instance->connections = etcp; + instance->connections_count++; + +// для простоты предлагаю если что-то не так - etcp_connection_close и он закрывает все что не null + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_create: successfully created ETCP connection %p", etcp); + return etcp; +conn_create_err: + etcp_connection_close(etcp); + return NULL; +} +//======================================================================================================================================================================= +// Close connection (expanded free) +void etcp_connection_close(struct ETCP_CONN* etcp) { + if (!etcp) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_connection_close: called with NULL etcp"); + return; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: closing connection %p", etcp); + + // Remove connection from instance list + if (etcp->instance) { + struct ETCP_CONN** prev = &etcp->instance->connections; + struct ETCP_CONN* cur = *prev; + while (cur) { + if (cur == etcp) { + *prev = cur->next; + etcp->instance->connections_count--; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: removed from instance list, count=%d", + etcp->instance->connections_count); + break; + } + prev = &cur->next; + cur = cur->next; + } + } + + // Cancel timers + if (etcp->retrans_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling retrans_timer"); + uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); + } + if (etcp->ack_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling ack_timer"); + uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); + } + + // Free inflight + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_ack_list"); + inflight_packet_t* pkt = etcp->wait_ack_list; + int wait_ack_count = 0; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + wait_ack_count++; + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_ack_list", wait_ack_count); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_send_list"); + pkt = etcp->wait_send_list; + int wait_send_count = 0; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + wait_send_count++; + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_send_list", wait_send_count); + + // Free rx_list + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing rx_list"); + rx_packet_t* rx = etcp->rx_list; + int rx_count = 0; + while (rx) { + rx_packet_t* next = rx->next; + free(rx->data); + free(rx); + rx = next; + rx_count++; + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from rx_list", rx_count); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing queues"); + queue_free(etcp->input_queue); + queue_free(etcp->output_queue); + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_close: connection %p closed successfully", etcp); + free(etcp); +} + +//======================================================================================================================================================================= + +static void free_inflight_packet(inflight_packet_t* pkt) { + if (pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "free_inflight_packet: freeing packet %p (id=%u, data_len=%u)", + pkt, pkt->id, pkt->data_len); + free(pkt->data); + free(pkt); + } +} + +//======================================================================================================================================================================= + +// Reset connection (unchanged) +void etcp_conn_reset(struct ETCP_CONN* etcp) { + if (!etcp) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: called with NULL etcp"); + return; + } + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: resetting connection %p", etcp); + + // Cancel timers + if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); + if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); + + // Free lists + inflight_packet_t* pkt = etcp->wait_ack_list; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + } + etcp->wait_ack_list = NULL; + pkt = etcp->wait_send_list; + while (pkt) { + inflight_packet_t* next = pkt->next; + free_inflight_packet(pkt); + pkt = next; + } + etcp->wait_send_list = NULL; + + rx_packet_t* rx = etcp->rx_list; + while (rx) { + rx_packet_t* next = rx->next; + free(rx->data); + free(rx); + rx = next; + } + etcp->rx_list = NULL; + + // Reset state + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: clearing packet lists and resetting state"); + etcp->next_tx_id = 1; + etcp->last_rx_id = 0; + etcp->last_delivered_id = 0; + etcp->unacked_bytes = 0; + etcp->pending_ack_count = 0; + etcp->pending_retrans_count = 0; + etcp->rtt_last = 0; + etcp->rtt_avg_10 = 0; + etcp->rtt_avg_100 = 0; + etcp->jitter = 0; + etcp->burst_in_progress = 0; + etcp->rtt_history_idx = 0; + memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); + // Queues cleared externally if needed + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: connection %p reset completed", etcp); +} + +//======================================================================================================================================================================= +// PACKET INPUT processing +//======================================================================================================================================================================= + + +// Input decrypted packet (unchanged structure, but calls expanded processes) +void etcp_conn_input(struct ETCP_DGRAM* pkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p, link=%p, etcp=%p", + pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); + + if (!pkt || !pkt->link || !pkt->link->etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: invalid parameters (pkt=%p, link=%p, etcp=%p)", + pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); + return; + } + + struct ETCP_CONN* etcp = pkt->link->etcp; + uint8_t* data = pkt->data; + uint16_t len = pkt->data_len; + uint16_t ts = pkt->timestamp; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: processing packet - etcp=%p, data_len=%u, timestamp=%u, link=%p", + etcp, len, ts, pkt->link); + + // Update link activity + pkt->link->last_activity = get_current_time_units(); + pkt->link->last_recv_local_time = pkt->link->last_activity; + pkt->link->last_recv_timestamp = ts; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_conn_input: updated link activity - last_activity=%llu, last_recv_timestamp=%u", + (unsigned long long)pkt->link->last_activity, pkt->link->last_recv_timestamp); + + // Parse sections + uint16_t pos = 0; + while (pos < len) { + if (pos + SECTION_HEADER_SIZE > len) break; + + uint8_t type = data[pos]; + uint16_t sec_len = (data[pos+1] << 8) | data[pos+2]; + pos += SECTION_HEADER_SIZE; + + if (pos + sec_len > len) break; + + uint8_t* sec_data = data + pos; + switch (type) { + case ETCP_SECTION_ACK: + process_section_ack(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_RETRANS: + process_section_retrans(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_TIMESTAMP: + process_section_timestamp(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_PAYLOAD: + process_section_payload(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_MEAS_TS: + process_section_meas_ts(etcp, sec_data, sec_len); + break; + case ETCP_SECTION_MEAS_RESP: + process_section_meas_resp(etcp, sec_data, sec_len); + break; + default: + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Unknown section type: 0x%02x", type); + break; + } + pos += sec_len; + } + + // Post-processing + detect_gaps_and_request_retrans(etcp); + deliver_contiguous_packets(etcp); + + // Trigger if waiting + if (etcp->wait_timeout_active) { + etcp->wait_timeout_active = 0; + // Assume load balancer polls again + } +} + +// Request next packet (expanded) +// source 1: wait_ack_list +// source 2: queue > wait_ack_list + +// Эта основная функция для формирования кодограмм на передачу из входящей очереди в UDP +// она вызывается когда какой-либо из каналов освободился, либо появились данные во входящей очереди, либо сработал таймер ретрансмиссии +// должна формировать пакеты до тех пор пока их может принимать loadbalancer +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: etcp=%p", etcp); + + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: called with NULL etcp"); + return NULL; + } + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: input_queue_count=%d, wait_send_list=%p, unacked_bytes=%u, window_size=%u", + queue_entry_count(etcp->input_queue), etcp->wait_send_list, + etcp->unacked_bytes, etcp->window_size); + + etcp_get_wait_list(); + // Check waiting conditions + if (queue_entry_count(etcp->input_queue) == 0 && etcp->wait_send_list == NULL) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no packets available, setting callback and returning NULL"); + // Wait for input or retrans + // Set callback for resume + queue_set_callback(etcp->input_queue, input_queue_cb, etcp); // Define cb to resume + return NULL; + } + + if (etcp->unacked_bytes >= etcp->window_size) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: window full (unacked_bytes=%u >= window_size=%u), waiting", + etcp->unacked_bytes, etcp->window_size); + // Wait ACK or timeout + etcp->wait_timeout_active = 1; + return NULL; + } + + if (etcp->unacked_bytes >= etcp->window_size) { + // Wait ACK or timeout + etcp->wait_timeout_active = 1; + return NULL; + } + + // Get packet (prefer retrans) + inflight_packet_t* ipkt = etcp->wait_send_list; + if (ipkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: using retransmission packet id=%u", ipkt->id); + remove_inflight(etcp, ipkt); + } else { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: getting new packet from input queue"); + struct ll_entry* entry = queue_entry_get(etcp->input_queue); + if (!entry) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no entry available from input queue"); + return NULL; + } + + ipkt = calloc(1, sizeof(inflight_packet_t)); + if (!ipkt) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate inflight_packet_t"); + queue_entry_put_first(etcp->input_queue, entry); + return NULL; + } + + ipkt->id = etcp->next_tx_id++; + ipkt->data_len = ll_entry_size(entry); + ipkt->data = malloc(ipkt->data_len); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: created new packet id=%u, data_len=%u", + ipkt->id, ipkt->data_len); + + if (!ipkt->data) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate packet data buffer"); + free(ipkt); + queue_entry_put_first(etcp->input_queue, entry); + return NULL; + } + memcpy(ipkt->data, ll_entry_data(entry), ipkt->data_len); + ipkt->payload_len = ipkt->data_len; // Assume full is payload for window + queue_entry_free(entry); + } + + // Select link + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selecting link via loadbalancer"); + struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); + if (!link) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no suitable link available"); + // Put back if new + if (ipkt->send_count == 0) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: putting new packet back to input queue"); + struct ll_entry* entry = queue_entry_new(ipkt->data_len); + memcpy(ll_entry_data(entry), ipkt->data, ipkt->data_len); + queue_entry_put_first(etcp->input_queue, entry); + } + free_inflight_packet(ipkt); + return NULL; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selected link %p", link); + + // Build packet buf + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: building packet with optional sections"); + uint8_t buf[1600]; + uint16_t len = append_optional_sections(etcp, buf, link); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: optional sections added, len=%u", len); + + // Add payload (assume payload includes ID as first 2 bytes) + uint16_t pay_len = ipkt->payload_len + 2; // ID + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload section - pay_len=%u (payload_len=%u + 2 for ID)", + pay_len, ipkt->payload_len); + + if (len + SECTION_HEADER_SIZE + pay_len > sizeof(buf)) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: packet too large (%u + %u + %u > %u), dropping", + len, SECTION_HEADER_SIZE, pay_len, (unsigned)sizeof(buf)); + free_inflight_packet(ipkt); + return NULL; + } + buf[len++] = ETCP_SECTION_PAYLOAD; + buf[len++] = (pay_len >> 8) & 0xFF; + buf[len++] = pay_len & 0xFF; + buf[len++] = (ipkt->id >> 8) & 0xFF; + buf[len++] = ipkt->id & 0xFF; + memcpy(buf + len, ipkt->data, ipkt->payload_len); + len += ipkt->payload_len; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload added, total len=%u", len); + + // Alloc dgram + struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); + if (!dgram) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate dgram from memory pool"); + free_inflight_packet(ipkt); + return NULL; + } + dgram->link = link; + dgram->data_len = len; + dgram->noencrypt_len = 0; + dgram->timestamp = get_current_timestamp(); + memcpy(dgram->data, buf, len); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: allocated dgram %p (link=%p, data_len=%u, timestamp=%u)", + dgram, link, len, dgram->timestamp); + + // Update inflight + ipkt->last_link = link; + ipkt->last_timestamp = dgram->timestamp; + ipkt->send_count++; + etcp->unacked_bytes += ipkt->payload_len; + etcp->bytes_sent_total += len; // Approx + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: updated inflight - send_count=%u, unacked_bytes=%u, bytes_sent_total=%u", + ipkt->send_count, etcp->unacked_bytes, etcp->bytes_sent_total); + + // Move to wait_ack + ipkt->state = INFLIGHT_STATE_WAIT_ACK; + ipkt->next = etcp->wait_ack_list; + etcp->wait_ack_list = ipkt; + + // Add to wait_ack list + ipkt->next = etcp->wait_ack_list; + etcp->wait_ack_list = ipkt; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: added packet %u to wait_ack_list", ipkt->id); + + // Start retrans timer if empty before + if (etcp->wait_ack_list->next == NULL && !etcp->retrans_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: starting retrans_timer (wait_ack_list was empty)"); + etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); + } + + // Update load balancer after send (call from connections after actual send) + // etcp_loadbalancer_update_after_send(link, len + headers); // Assume called externally + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: successfully created dgram %p for packet id=%u", + dgram, ipkt->id); + return dgram; +} + +// Input queue callback (placeholder to resume load balancer) +static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg) { + (void)q; + (void)entry; + struct ETCP_CONN* etcp = arg; + // Resume polling etcp_request_pkt + queue_resume_callback(q); +} + +// Append optional (expanded with debug) +static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: etcp=%p, link=%p", etcp, link); + + uint16_t len = 0; + int sections_added = 0; + + // Timestamp (assume 2 bytes TS) + uint16_t ts = get_current_timestamp(); + buf[len++] = ETCP_SECTION_TIMESTAMP; + buf[len++] = 0; // len high + buf[len++] = 2; // len low + buf[len++] = (ts >> 8) & 0xFF; + buf[len++] = ts & 0xFF; + sections_added++; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: added TIMESTAMP section (ts=%u, len=%u)", ts, len); + + // ACK (assume count(1) + [id(2)+ts(2)]*count) + if (etcp->pending_ack_count > 0) { + uint16_t ack_len = 1 + etcp->pending_ack_count * 4; + buf[len++] = ETCP_SECTION_ACK; + buf[len++] = (ack_len >> 8) & 0xFF; + buf[len++] = ack_len & 0xFF; + buf[len++] = etcp->pending_ack_count; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding ACK section with %u ACKs", etcp->pending_ack_count); + + for (uint8_t i = 0; i < etcp->pending_ack_count; i++) { + uint16_t id = etcp->pending_ack_ids[i]; + uint16_t ats = etcp->pending_ack_ts[i]; + buf[len++] = (id >> 8) & 0xFF; + buf[len++] = id & 0xFF; + buf[len++] = (ats >> 8) & 0xFF; + buf[len++] = ats & 0xFF; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: ACK[%u] - id=%u, ts=%u", i, id, ats); + } + + etcp->pending_ack_count = 0; + etcp->ack_packets_count++; + sections_added++; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added ACK section (total_len=%u)", len); + } + + // RETRANS (similar: count(1) + id(2)*count) + if (etcp->pending_retrans_count > 0) { + uint16_t ret_len = 1 + etcp->pending_retrans_count * 2; + buf[len++] = ETCP_SECTION_RETRANS; + buf[len++] = (ret_len >> 8) & 0xFF; + buf[len++] = ret_len & 0xFF; + buf[len++] = etcp->pending_retrans_count; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding RETRANS section with %u requests", etcp->pending_retrans_count); + + for (uint8_t i = 0; i < etcp->pending_retrans_count; i++) { + uint16_t id = etcp->pending_retrans_ids[i]; + buf[len++] = (id >> 8) & 0xFF; + buf[len++] = id & 0xFF; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: RETRANS[%u] - id=%u", i, id); + } + + etcp->pending_retrans_count = 0; + sections_added++; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added RETRANS section (total_len=%u)", len); + } + + // MEAS_TS if burst (assume [start_id(2)][n(1)]) + if (etcp->burst_in_progress) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: burst measurement in progress, would add MEAS_TS"); + // Logic for adding to burst packets + // Placeholder + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: completed - added %u sections, total_len=%u", + sections_added, len); + + return len; +} + +// Process ACK (expanded) +static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 1) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid length %u", len); + return; + } + uint8_t count = data[0]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: count=%u", count); + + if (1 + count * 4 > len) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid count %u for len %u", count, len); + return; + } + + uint16_t lp_ts = 0; + int acked_count = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: processing %u ACK entries", count); + + for (uint8_t i = 0; i < count; i++) { + uint16_t id = (data[1 + i*4] << 8) | data[2 + i*4]; + uint16_t ts = (data[3 + i*4] << 8) | data[4 + i*4]; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: entry %u - id=%u, ts=%u", i, id, ts); + + inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); + if (pkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: found inflight packet %u, removing (payload_len=%u)", + id, pkt->payload_len); + etcp->unacked_bytes -= pkt->payload_len; + remove_inflight(etcp, pkt); + free_inflight_packet(pkt); + lp_ts = ts; // Update last positive ts + acked_count++; + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: no inflight packet found for id=%u", id); + } + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: successfully ACKed %u/%u packets", acked_count, count); + // Use lp_ts in retrans_check if needed + etcp->last_rx_ack_id = (data[1 + (count-1)*4] << 8) | data[2 + (count-1)*4]; // Last ACK ID +} + +// Process RETRANS (expanded) +static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 1) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid length %u", len); + return; + } + uint8_t count = data[0]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: count=%u", count); + + if (1 + count * 2 > len) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid count %u for len %u", count, len); + return; + } + + int retrans_count = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: processing %u retrans requests", count); + + for (uint8_t i = 0; i < count; i++) { + uint16_t id = (data[1 + i*2] << 8) | data[2 + i*2]; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: requesting retrans for id=%u", id); + + inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); + if (pkt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: found packet %u, marking for retransmission", id); + pkt->retrans_req_count++; + pkt->need_retrans = 1; + move_to_wait_send(etcp, pkt); + retrans_count++; + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: no inflight packet found for id=%u", id); + } + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: marked %u/%u packets for retransmission", retrans_count, count); +} + +// Process TIMESTAMP (expanded) +static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len != 2) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_timestamp: invalid length %u (expected 2)", len); + return; + } + uint16_t recv_ts = (data[0] << 8) | data[1]; + uint16_t current_ts = get_current_timestamp(); + uint16_t rtt = timestamp_diff(current_ts, recv_ts); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: recv_ts=%u, current_ts=%u, calculated_rtt=%u", + recv_ts, current_ts, rtt); + update_rtt(etcp, rtt); +} + +// Process PAYLOAD (expanded) +static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 2) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: invalid length %u (minimum 2)", len); + return; + } + uint16_t id = (data[0] << 8) | data[1]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u, last_rx_id=%u", id, etcp->last_rx_id); + + if (id <= etcp->last_rx_id) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u (last_rx_id=%u), ignoring", + id, etcp->last_rx_id); + return; // Dup + } + + rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t)); + if (!new_rx) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx_packet_t"); + return; + } + + new_rx->id = id; + new_rx->timestamp = get_current_timestamp(); + new_rx->data_len = len - 2; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: allocated rx_packet %p (id=%u, data_len=%u, timestamp=%u)", + new_rx, new_rx->id, new_rx->data_len, new_rx->timestamp); + + new_rx->data = malloc(new_rx->data_len); + if (!new_rx->data) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx data buffer (size=%u)", new_rx->data_len); + free(new_rx); + return; + } + memcpy(new_rx->data, data + 2, new_rx->data_len); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: copied %u bytes of payload data", new_rx->data_len); + + // Insert sorted (assume IDs sequential, no wrap for simplicity) + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: inserting packet into rx_list (id=%u)", id); + rx_packet_t** ptr = &etcp->rx_list; + while (*ptr && (*ptr)->id < id) ptr = &(*ptr)->next; + if (*ptr && (*ptr)->id == id) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u found in rx_list, discarding", id); + free(new_rx->data); + free(new_rx); + return; + } + new_rx->next = *ptr; + *ptr = new_rx; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: successfully inserted packet id=%u into rx_list", id); + + if (id > etcp->last_rx_id) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: updating last_rx_id from %u to %u", + etcp->last_rx_id, id); + etcp->last_rx_id = id; + } + + // Pending ACK + if (etcp->pending_ack_count < MAX_PENDING) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: adding packet id=%u to pending ACKs (count=%u)", + id, etcp->pending_ack_count + 1); + etcp->pending_ack_ids[etcp->pending_ack_count] = id; + etcp->pending_ack_ts[etcp->pending_ack_count++] = new_rx->timestamp; + } else { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: pending ACK buffer full (%u), cannot add id=%u", + MAX_PENDING, id); + } + + // Schedule ACK + if (!etcp->ack_timer) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: scheduling ACK timer (delay=%u tb)", ACK_DELAY_TB); + etcp->ack_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_timer_cb); + } + + etcp->bytes_received_total += len; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u processed successfully, bytes_received_total=%u", + id, etcp->bytes_received_total); +} + +// Process MEAS_TS (expanded: send resp) +static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: etcp=%p, data=%p, len=%u", etcp, data, len); + + if (len < 3) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: invalid length %u (minimum 3)", len); + return; + } + uint16_t start_id = (data[0] << 8) | data[1]; + uint8_t n = data[2]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: start_id=%u, n=%u", start_id, n); + + // Send MEAS_RESP (add to pending or direct) + // Placeholder: assume added to next packet + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "MEAS_TS: start_id=%u n=%u", start_id, n); + // Build resp [start_id(2)][n(1)][recv_ts(2)] + // But since appended in optional, set state +} + +// Process MEAS_RESP (expanded: calc bw) +static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: etcp=%p, data=%p, len=%u", etcp, data, len); + + // Assume format [start_id(2)][n(1)][recv_ts(2)] + if (len < 5) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: invalid length %u (minimum 5)", len); + return; + } + uint16_t start_id = (data[0] << 8) | data[1]; + uint8_t n = data[2]; + uint16_t recv_ts = (data[3] << 8) | data[4]; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: start_id=%u, n=%u, recv_ts=%u", start_id, n, recv_ts); + + if (etcp->burst_start_id != start_id || !etcp->burst_in_progress) return; + + // Calc bw based on burst (placeholder logic) + uint16_t delta = timestamp_diff(get_current_timestamp(), recv_ts); + uint32_t bw = (BURST_SIZE * etcp->mtu * 8) / (delta / 1000.0); // bits/sec approx + update_link_bandwidth(etcp->links, bw); // Assume primary link + etcp->burst_in_progress = 0; +} + +// Detect gaps (expanded) +static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: etcp=%p, last_delivered_id=%u", + etcp, etcp->last_delivered_id); + + rx_packet_t* rx = etcp->rx_list; + uint16_t expected = etcp->last_delivered_id + 1; + int gaps_found = 0; + int retrans_requested = 0; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: starting gap detection from expected_id=%u", expected); + + while (rx) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: checking rx packet id=%u (expected=%u)", + rx->id, expected); + + if (rx->id > expected) { + // Gap: add retrans for expected to rx->id -1 + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: gap detected - expected=%u, found=%u", + expected, rx->id); + gaps_found++; + + for (uint16_t gid = expected; gid < rx->id && etcp->pending_retrans_count < MAX_PENDING; gid++) { + etcp->pending_retrans_ids[etcp->pending_retrans_count++] = gid; + retrans_requested++; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: queued retrans request for id=%u", gid); + } + expected = rx->id + 1; + } else if (rx->id == expected) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: found expected packet id=%u", expected); + expected++; + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: packet id=%u < expected=%u, already processed", + rx->id, expected); + } + rx = rx->next; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: completed - gaps_found=%d, retrans_requested=%u", + gaps_found, retrans_requested); +} + +// Deliver contiguous (expanded) +static void deliver_contiguous_packets(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: etcp=%p, last_delivered_id=%u", + etcp, etcp->last_delivered_id); + + rx_packet_t** ptr = &etcp->rx_list; + int delivered_count = 0; + uint32_t delivered_bytes = 0; + + while (*ptr && (*ptr)->id == etcp->last_delivered_id + 1) { + rx_packet_t* rx = *ptr; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivering packet id=%u (data_len=%u)", + rx->id, rx->data_len); + + struct ll_entry* entry = queue_entry_new(rx->data_len); + if (entry) { + memcpy(ll_entry_data(entry), rx->data, rx->data_len); + queue_entry_put(etcp->output_queue, entry); + delivered_bytes += rx->data_len; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: queued packet id=%u to output_queue", rx->id); + } else { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: failed to allocate queue entry for packet id=%u", rx->id); + } + + etcp->last_delivered_id = rx->id; + *ptr = rx->next; + free(rx->data); + free(rx); + delivered_count++; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivered %u contiguous packets (%u bytes), last_delivered_id=%u", + delivered_count, delivered_bytes, etcp->last_delivered_id); +} + +// Retrans check (expanded with lp_ts) +static void retrans_check(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: etcp=%p, wait_ack_list=%p, rtt_avg_10=%u, jitter=%u", + etcp, etcp->wait_ack_list, etcp->rtt_avg_10, etcp->jitter); + + uint64_t now = get_current_time_units(); + inflight_packet_t** ptr = &etcp->wait_ack_list; + uint16_t lp_ts = 0; + int checked_packets = 0; + int retrans_packets = 0; + uint16_t timeout_calc = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: timeout calculation: rtt_avg_10=%u * %.1f + jitter=%u * %.1f = %u", + etcp->rtt_avg_10, RETRANS_K1, etcp->jitter, RETRANS_K2, timeout_calc); + + while (*ptr) { + inflight_packet_t* pkt = *ptr; + checked_packets++; + uint16_t timeout = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); + uint16_t time_since_send = timestamp_diff(now, pkt->last_timestamp); + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "retrans_check: checking packet id=%u (time_since_send=%u, timeout=%u, need_retrans=%u)", + pkt->id, time_since_send, timeout, pkt->need_retrans); + + if (time_since_send > timeout) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: packet id=%u timed out, marking for retransmission", pkt->id); + pkt->need_retrans = 1; + move_to_wait_send(etcp, pkt); + etcp->retransmissions_count++; + retrans_packets++; + ptr = &etcp->wait_ack_list; // Restart scan after move + continue; + } + // lp_ts logic (per spec: if acked, remember ts; if later unacked with ts < lp_ts, ?) + // Assume if pkt acked (but shouldn't be in list), or for forward progress + ptr = &pkt->next; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: completed - checked %d packets, retransmitted %d packets, total_retransmissions=%u", + checked_packets, retrans_packets, etcp->retransmissions_count); + + // Reschedule if list not empty + if (etcp->wait_ack_list) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: rescheduling retrans_timer (wait_ack_list not empty)"); + etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); + } else { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: clearing retrans_timer (wait_ack_list empty)"); + etcp->retrans_timer = NULL; + } +} + +// Find inflight (expanded) +static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: etcp=%p, id=%u, state=%u", etcp, id, state); + + inflight_packet_t* list = (state == INFLIGHT_STATE_WAIT_ACK) ? etcp->wait_ack_list : etcp->wait_send_list; + inflight_packet_t* pkt = list; + int position = 0; + + while (pkt && pkt->id != id) { + pkt = pkt->next; + position++; + } + + if (pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: found packet %u at position %d in %s list", + id, position, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: packet %u not found in %s list", + id, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + } + + return pkt; + return pkt; +} + +// Move to wait_send (expanded) +static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: etcp=%p, pkt=%p (id=%u, state=%u)", + etcp, pkt, pkt->id, pkt->state); + + remove_inflight(etcp, pkt); + pkt->state = INFLIGHT_STATE_WAIT_SEND; + pkt->next = etcp->wait_send_list; + etcp->wait_send_list = pkt; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: moved packet %u to wait_send_list", pkt->id); +} + +// Remove inflight (expanded) +static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: etcp=%p, pkt=%p (id=%u, state=%u)", + etcp, pkt, pkt->id, pkt->state); + + inflight_packet_t** list = (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? &etcp->wait_ack_list : &etcp->wait_send_list; + inflight_packet_t** ptr = list; + int position = 0; + + while (*ptr && *ptr != pkt) { + ptr = &(*ptr)->next; + position++; + } + + if (*ptr) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: found packet %u at position %d in %s list", + pkt->id, position, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + *ptr = (*ptr)->next; + } else { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "remove_inflight: packet %u not found in %s list", + pkt->id, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + } +} + +// Start burst (placeholder expanded) +static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "start_burst_measurement: etcp=%p, link=%p", etcp, link); + + // Periodically call this + etcp->burst_in_progress = 1; + uint16_t old_start_id = etcp->burst_start_id; + etcp->burst_start_id = etcp->next_tx_id; + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "start_burst_measurement: started burst measurement - burst_start_id=%u (was %u)", + etcp->burst_start_id, old_start_id); + + // Delay first, then burst 4 + // Assume handled in request_pkt by adding meas_ts to next BURST_SIZE pkts +} + +// Update link bw (expanded) +static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: link=%p, new_bw=%u bits/sec (current=%u)", + link, new_bw, link->bandwidth); + + uint32_t old_bw = link->bandwidth; + link->bandwidth = (uint32_t)((link->bandwidth * 0.9) + (new_bw * 0.1)); // Smooth + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: updated link bandwidth from %u to %u bits/sec (new_bw=%u)", + old_bw, link->bandwidth, new_bw); +} + +// Update RTT (expanded with history) +static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: etcp=%p, new_rtt=%u (last_rtt=%u, avg_10=%u, avg_100=%u)", + etcp, new_rtt, etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); + + etcp->rtt_last = new_rtt; + etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + new_rtt) / 10; + etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + new_rtt) / 100; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: updated RTT metrics - last=%u, avg_10=%u, avg_100=%u", + etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); + + // History for jitter + etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt; + if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "update_rtt: stored RTT in history at index %u", + (etcp->rtt_history_idx + RTT_HISTORY_SIZE - 1) % RTT_HISTORY_SIZE); + + calculate_jitter(etcp); +} + +static void calculate_jitter(struct ETCP_CONN* etcp) { + uint16_t min_rtt = UINT16_MAX; + uint16_t max_rtt = 0; + int valid_samples = 0; + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: analyzing RTT history (history_idx=%u)", etcp->rtt_history_idx); + + for (int i = 0; i < RTT_HISTORY_SIZE; i++) { + if (etcp->rtt_history[i] == 0) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: history[%d] = 0 (invalid), skipping", i); + continue; + } + valid_samples++; + if (etcp->rtt_history[i] < min_rtt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new min_rtt=%u (was %u) at history[%d]", + etcp->rtt_history[i], min_rtt, i); + min_rtt = etcp->rtt_history[i]; + } + if (etcp->rtt_history[i] > max_rtt) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new max_rtt=%u (was %u) at history[%d]", + etcp->rtt_history[i], max_rtt, i); + max_rtt = etcp->rtt_history[i]; + } + } + + uint16_t old_jitter = etcp->jitter; + etcp->jitter = max_rtt - min_rtt; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "calculate_jitter: completed - valid_samples=%d, min_rtt=%u, max_rtt=%u, jitter=%u (was %u)", + valid_samples, min_rtt, max_rtt, etcp->jitter, old_jitter); +} + +// Timers (expanded with debug) +static void retrans_timer_cb(void* arg) { + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_timer_cb: etcp=%p, triggered retransmission check", etcp); + retrans_check(etcp); +} + +static void ack_timer_cb(void* arg) { + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: etcp=%p, processing pending ACKs", etcp); + etcp->ack_timer = NULL; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: processing %u pending ACKs", etcp->pending_ack_count); + + // Force send ACK if pending (e.g., empty payload pkt) + // For now, assume appended next + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: ACK processing completed"); +} + +// Get stats (expanded) +void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, + size_t* pool_allocs, size_t* pool_reuse) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: etcp=%p", etcp); + + if (packets_sent) { + *packets_sent = etcp->total_packets_sent; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: total_packets_sent=%zu", *packets_sent); + } + if (packets_recv) { + *packets_recv = etcp->bytes_received_total / 1400; // Approx MTU + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: bytes_received_total=%u, estimated_packets=%zu", + etcp->bytes_received_total, *packets_recv); + } + if (pool_allocs || pool_reuse) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: getting memory pool stats"); + memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse); + if (pool_allocs) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_allocs=%zu", *pool_allocs); + if (pool_reuse) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_reuse=%zu", *pool_reuse); + } +} \ No newline at end of file diff --git a/src/etcp.c_ b/!/etcp.c_ similarity index 100% rename from src/etcp.c_ rename to !/etcp.c_ diff --git a/!/etcp.h b/!/etcp.h new file mode 100755 index 00000000..03f1444a --- /dev/null +++ b/!/etcp.h @@ -0,0 +1,148 @@ +// etcp.h - ETCP Protocol Header (refactored based on etcp_protocol.txt) +#ifndef ETCP_H +#define ETCP_H + +#include "etcp_connections.h" +#include "secure_channel.h" +#include "../lib/ll_queue.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Forward declarations +struct UTUN_INSTANCE; +struct UASYNC; + +// ETCP packet section types (from protocol spec) +#define ETCP_SECTION_PAYLOAD 0x00 // Data payload +#define ETCP_SECTION_ACK 0x01 // ACK section +#define ETCP_SECTION_RETRANS 0x10 // Retransmission request base (0x10-0x2F) +#define ETCP_SECTION_TIMESTAMP 0x06 // Channel timestamp (example, adjust if needed) +#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth +#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response + +// Inflight packet states +#define INFLIGHT_STATE_WAIT_ACK 0 +#define INFLIGHT_STATE_WAIT_SEND 1 + +// Inflight packet structure +typedef struct inflight_packet { + struct inflight_packet* next; + uint16_t id; // Packet ID + struct ETCP_LINK* last_link; // Last sent link + uint16_t last_timestamp; // Last send timestamp + uint8_t send_count; // Number of sends + uint8_t retrans_req_count; // Number of retrans requests + uint8_t state; // WAIT_ACK or WAIT_SEND + uint8_t need_retrans; // Flag for forced retrans + uint8_t* data; // Packet data + uint16_t data_len; // Data length + uint16_t payload_len; // Payload length for window +} inflight_packet_t; + +// RX packet for assembly linked list +typedef struct rx_packet { + struct rx_packet* next; + uint16_t id; + uint16_t timestamp; + uint8_t* data; // Assembled data (payload only) + uint16_t data_len; +} rx_packet_t; + +// ETCP connection structure (refactored) +struct ETCP_CONN { + struct ETCP_CONN* next; + int mtu; + + struct UTUN_INSTANCE* instance; + + // Links (channels) - linked list + struct ETCP_LINK* links; + + // Crypto and state + struct secure_channel crypto_ctx; + + // Peer info + uint64_t peer_node_id; // Peer node ID + + // Queues + struct ll_queue* input_queue; // Incoming packets to send + struct ll_queue* output_queue; // Assembled outgoing packets + + // Inflight lists (two lists as per spec) + inflight_packet_t* wait_ack_list; // Waiting for ACK + inflight_packet_t* wait_send_list; // Waiting for send (retrans) + + // RX assembly list + rx_packet_t* rx_list; // Sorted by ID for gap detection + + // IDs and state + uint16_t next_tx_id; // Next TX ID + uint16_t last_rx_id; // Last received ID + uint16_t last_delivered_id; // Last delivered to output_queue + + // Pending ACKs and retrans + uint16_t pending_ack_ids[32]; + uint16_t pending_ack_ts[32]; // Timestamps for ACKs + uint8_t pending_ack_count; + uint16_t pending_retrans_ids[32]; + uint8_t pending_retrans_count; + + // Metrics (RTT, jitter, etc.) + uint16_t rtt_last; + uint16_t rtt_avg_10; + uint16_t rtt_avg_100; + uint16_t jitter; + uint32_t bytes_sent_total; + uint32_t bytes_received_total; + uint32_t retransmissions_count; + + // Window and inflight management + uint32_t unacked_bytes; // Current inflight bytes + uint32_t window_size; // Receive window + uint32_t optimal_inflight; // Sum over links + + // Timers + void* retrans_timer; // Retrans check timer + void* ack_timer; // ACK send timer + + // Bandwidth measurement state + uint8_t burst_in_progress; // Burst transmission flag + uint16_t burst_start_id; // Start ID for burst + // ... (add more for meas_ts, meas_resp) + + // Statistics counters + uint32_t ack_packets_count; // Count of ACK packets received + uint16_t last_rx_ack_id; // Last ACK ID received + uint16_t rtt_history[10]; // RTT history for jitter calculation (RTT_HISTORY_SIZE=10) + uint8_t rtt_history_idx; // Current index in RTT history + uint32_t total_packets_sent; // Total packets sent counter + + // Flags + uint8_t wait_timeout_active; // In wait timeout state +}; + +// Functions +struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance); +void etcp_connection_close(struct ETCP_CONN* etcp); +void etcp_conn_reset(struct ETCP_CONN* etcp); + +// Input from etcp_connections (decrypted packet) +void etcp_conn_input(struct ETCP_DGRAM* pkt); + +// Request next packet for load balancer +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp); + +// Get stats +void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, + size_t* pool_allocs, size_t* pool_reuse); + +uint16_t get_current_timestamp(void); + +#ifdef __cplusplus +} +#endif + +#endif // ETCP_H \ No newline at end of file diff --git a/!/etcp_connections.c b/!/etcp_connections.c new file mode 100755 index 00000000..ecec373d --- /dev/null +++ b/!/etcp_connections.c @@ -0,0 +1,846 @@ +#include "etcp_connections.h" +#include +#include +#include +#include +#include +#include +#include "routing.h" +#include "utun_instance.h" +#include "utun_test_hooks.h" +#include "config_parser.h" +#include "crc32.h" +#include "etcp.h" +#include "../lib/memory_pool.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include + +// Packet dump configuration +#define ETCP_PACKET_DUMP_COMPACT 1 // Use compact single-line format +#define ETCP_PACKET_DUMP_FULL 0 // Use full multi-line format (legacy) +#include + +// Simple debug macros to replace missing debug_config.h +#define DEBUG_CATEGORY_CONNECTION 1 +#define DEBUG_CATEGORY_ETCP 2 +#define DEBUG_CATEGORY_CRYPTO 3 +#define DEBUG_CATEGORY_CONFIG 4 +#define DEBUG_CATEGORY_MEMORY 5 + +// Forward declaration +static void etcp_connections_read_callback(int fd, void* arg); + +// Single-line packet dump for debug output +static void dump_packet_bytes(const char* prefix, const uint8_t* data, size_t len) { + // Build packet data as hex string for single-line output + char hex_buf[513]; // 256 bytes * 2 chars + 1 for null terminator + size_t hex_len = 0; + size_t show_len = (len > 128) ? 128 : len; // Show max 128 bytes + + for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) { + hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]); + if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes + hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " "); + } + } + + if (len > 128) { + hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "..."); + } + + // Single-line debug output with packet info + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] %s: len=%zu hex=%s", prefix, len, hex_buf); + + // Additional debug info for first few bytes + if (len >= 2) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[ETCP] %s: first_bytes=%02x%02x last_bytes=%02x%02x", + prefix, data[0], data[1], data[len-2], data[len-1]); + } +} + +// Compact packet info for high-frequency logging +static void log_packet_compact(const char* prefix, const uint8_t* data, size_t len, struct ETCP_LINK* link) { + if (!data || len == 0) return; + + // Extract packet type from first byte + uint8_t pkt_type = data[0]; + uint16_t timestamp = 0; + + if (len >= 3) { + // Extract timestamp from bytes 1-2 (big endian) + timestamp = (data[1] << 8) | data[2]; + } + + // Single line debug with key packet info + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] %s: link=%p type=0x%02x ts=%u len=%zu", + prefix, link, pkt_type, timestamp, len); + + // Trace level for detailed hex dump + if (len <= 64) { // Only for small packets to avoid spam + char hex_buf[133]; // 64 bytes * 2 + 1 + size_t hex_len = 0; + for (size_t i = 0; i < len && hex_len < sizeof(hex_buf) - 3; i++) { + hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]); + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[ETCP] %s: data=%s", prefix, hex_buf); + } +} + +// Unified packet dump function - uses configured format +static void packet_dump(const char* prefix, const uint8_t* data, size_t len, struct ETCP_LINK* link) { + if (!data || len == 0) return; + + #if ETCP_PACKET_DUMP_COMPACT + // Compact single-line format + log_packet_compact(prefix, data, len, link); + #else + // Full multi-line format (legacy) + dump_packet_bytes(prefix, data, len); + #endif +} + +// Legacy multi-line dump for compatibility (kept but not used) +static void dump_packet_bytes_multiline(const char* prefix, const uint8_t* data, size_t len) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DUMP] %s: len=%zu; dump: ", prefix, len); + size_t show = len < 160 ? len : 160; + for (size_t i = 0; i < show; i++) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "%02x ", data[i]); + } + if (len > 160) DEBUG_INFO(DEBUG_CATEGORY_ETCP, "..."); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "\n"); +} + + +// Forward declarations for missing functions +struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance); + +// CONNECTION MANAGEMENT (!!!это всё должно быть static!!!) +static void etcp_link_remove_from_connections(struct ETCP_SOCKET* conn, struct ETCP_LINK* link); + + +// Отправка кодограмм протокола (!!!это всё должно быть static!!!) +static void etcp_link_send_init(struct ETCP_LINK* link); +static int etcp_link_send_reset(struct ETCP_LINK* link); +static void etcp_link_init_timer_cbk(void* arg); + +#define INIT_TIMEOUT_INITIAL 500 +#define INIT_TIMEOUT_MAX 50000 + +static void etcp_link_send_init(struct ETCP_LINK* link) { + if (!link || !link->etcp || !link->etcp->instance) return; + + struct ETCP_DGRAM* dgram = malloc(sizeof(struct ETCP_DGRAM) + 100); + if (!dgram) return; + + dgram->link = link; + dgram->noencrypt_len = SC_PUBKEY_SIZE; + size_t offset = 0; + + dgram->data[offset++] = ETCP_INIT_REQUEST; + + uint64_t node_id = link->etcp->instance->node_id; + dgram->data[offset++] = (node_id >> 56) & 0xFF; + dgram->data[offset++] = (node_id >> 48) & 0xFF; + dgram->data[offset++] = (node_id >> 40) & 0xFF; + dgram->data[offset++] = (node_id >> 32) & 0xFF; + dgram->data[offset++] = (node_id >> 24) & 0xFF; + dgram->data[offset++] = (node_id >> 16) & 0xFF; + dgram->data[offset++] = (node_id >> 8) & 0xFF; + dgram->data[offset++] = node_id & 0xFF; + + dgram->data[offset++] = (link->mtu >> 8) & 0xFF; + dgram->data[offset++] = link->mtu & 0xFF; + + dgram->data[offset++] = (link->keepalive_interval >> 8) & 0xFF; + dgram->data[offset++] = link->keepalive_interval & 0xFF; + + memcpy(dgram->data + offset, link->etcp->instance->my_keys.public_key, SC_PUBKEY_SIZE); + dgram->data_len = offset + SC_PUBKEY_SIZE; + + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Sending INIT request to link, node_id=%llu, retry=%d", (unsigned long long)node_id, link->init_retry_count); + + // Debug: print remote address before sending + if (link->remote_addr.ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)&link->remote_addr; + char addr_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &sin->sin_addr, addr_str, INET_ADDRSTRLEN); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] INIT sending to %s:%d, link=%p, conn_fd=%d", addr_str, ntohs(sin->sin_port), link, link->conn->fd); + } + + etcp_encrypt_send(dgram); + free(dgram); + + link->init_retry_count++; + + if (!link->init_timer && link->is_server == 0) { + link->init_timeout = INIT_TIMEOUT_INITIAL; + link->init_timer = uasync_set_timeout(link->etcp->instance->ua, link->init_timeout, link, etcp_link_init_timer_cbk); + } else if (link->init_timer) { + if ((link->init_retry_count % 10) == 0 && link->init_timeout < INIT_TIMEOUT_MAX) { + link->init_timeout *= 2; + if (link->init_timeout > INIT_TIMEOUT_MAX) link->init_timeout = INIT_TIMEOUT_MAX; + } + uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer); + link->init_timer = uasync_set_timeout(link->etcp->instance->ua, link->init_timeout, link, etcp_link_init_timer_cbk); + } +} + +static void etcp_link_init_timer_cbk(void* arg) { + struct ETCP_LINK* link = (struct ETCP_LINK*)arg; + if (!link || link->initialized || link->is_server != 0) return; + + link->init_timer = NULL; + etcp_link_send_init(link); +} + +static int etcp_link_send_reset(struct ETCP_LINK* link) { + if (!link) return -1; + + struct ETCP_DGRAM* dgram = malloc(sizeof(struct ETCP_DGRAM) + 1); + if (!dgram) return -1; + + dgram->link = link; + dgram->data_len = 1; + dgram->noencrypt_len = 0; + dgram->data[0] = 0x06; + + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Sending RESET to link"); + int ret = etcp_encrypt_send(dgram); + free(dgram); + return ret; +} + +static uint32_t sockaddr_hash(struct sockaddr_storage* addr) { + socklen_t addr_len = (addr->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + return crc32_calc((void*)addr, addr_len); + } + +// Бинарный поиск линка по ip_port_hash +static int find_link_index(struct ETCP_SOCKET* e_sock, uint32_t hash) { + if (!e_sock || e_sock->num_channels == 0) return -1; + + int left = 0; + int right = e_sock->num_channels - 1; + + while (left <= right) { + int mid = left + (right - left) / 2; + if (e_sock->links[mid]->ip_port_hash == hash) { + return mid; + } else if (e_sock->links[mid]->ip_port_hash < hash) { + left = mid + 1; + } else { + right = mid - 1; + } + } + + return -(left + 1); +} + +// Реалокация массива линков с увеличением в 2 раза +static int realloc_links(struct ETCP_SOCKET* e_sock) { + size_t new_max = e_sock->max_channels == 0 ? 8 : e_sock->max_channels * 2; + struct ETCP_LINK** new_links = realloc(e_sock->links, new_max * sizeof(struct ETCP_LINK*)); + if (!new_links) return -1; + + e_sock->links = new_links; + e_sock->max_channels = new_max; + return 0; +} + +// Вставка линка в отсортированный массив +static int insert_link(struct ETCP_SOCKET* e_sock, struct ETCP_LINK* link) { + if (!e_sock || !link) return -1; + + if (e_sock->num_channels >= e_sock->max_channels) { + if (realloc_links(e_sock) < 0) return -1; + } + + int idx = find_link_index(e_sock, link->ip_port_hash); + if (idx >= 0) return -1; + + idx = -(idx + 1); + + if (idx < (int)e_sock->num_channels) { + memmove(&e_sock->links[idx + 1], &e_sock->links[idx], + (e_sock->num_channels - idx) * sizeof(struct ETCP_LINK*)); + } + + e_sock->links[idx] = link; + e_sock->num_channels++; + return 0; +} + +// Удаление линка из массива +static void remove_link(struct ETCP_SOCKET* e_sock, uint32_t hash) { + if (!e_sock || e_sock->num_channels == 0) return; + + int idx = find_link_index(e_sock, hash); + if (idx < 0) return; + + if (idx < (int)e_sock->num_channels - 1) { + memmove(&e_sock->links[idx], &e_sock->links[idx + 1], + (e_sock->num_channels - idx - 1) * sizeof(struct ETCP_LINK*)); + } + + e_sock->num_channels--; +} + +// надо править, используй sockaddr_hash +struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr) { + if (!e_sock || !addr) return NULL; + + int idx = find_link_index(e_sock, sockaddr_hash(addr)); + if (idx < 0) return NULL; + + return e_sock->links[idx]; +} + + +// =============================== + +struct ETCP_SOCKET* etcp_socket_add_ex(struct UTUN_INSTANCE* instance, struct sockaddr_storage* ip, uint32_t netif_index, int so_mark, uint8_t type, uint32_t flags) { + if (!instance) return NULL; + + struct ETCP_SOCKET* e_sock = calloc(1, sizeof(struct ETCP_SOCKET)); + if (!e_sock) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to allocate connection"); + return NULL; + } + + int family = AF_INET; + if (ip) { + family = ip->ss_family; + if (family != AF_INET && family != AF_INET6) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Unsupported address family: %d", family); + free(e_sock); + return NULL; + } + } + + // Use test hook if available and in test mode + if ((flags & UTUN_CREATE_TEST_MODE) && g_utun_test_hooks && g_utun_test_hooks->socket_create_override) { + e_sock->fd = g_utun_test_hooks->socket_create_override(family, SOCK_DGRAM, 0, + g_utun_test_hooks->test_context); + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Socket creation using test hook: fd=%d", e_sock->fd); + } else { + e_sock->fd = socket(family, SOCK_DGRAM, 0); + } + + if (e_sock->fd < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Failed to create socket: %s", strerror(errno)); + free(e_sock); + return NULL; + } + + int flags_fcntl = fcntl(e_sock->fd, F_GETFL, 0); + fcntl(e_sock->fd, F_SETFL, flags_fcntl | O_NONBLOCK); + + // Set socket mark if specified + if (so_mark > 0) { +#ifdef SO_MARK + if (setsockopt(e_sock->fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Failed to set SO_MARK: %s", strerror(errno)); + } +#endif + } + + // Bind to interface if specified + if (netif_index > 0) { +#ifdef SO_BINDTODEVICE + char ifname[IF_NAMESIZE]; + if (if_indextoname(netif_index, ifname)) { + if (setsockopt(e_sock->fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Failed to bind to interface %s: %s", ifname, strerror(errno)); + } + } +#endif + } + + // Store the local address and bind socket if provided + if (ip) { + memcpy(&e_sock->local_addr, ip, sizeof(struct sockaddr_storage)); + + // Skip binding if requested (for test injection) + if (!(flags & UTUN_CREATE_NO_SOCKET_BIND)) { + // CRITICAL: Actually bind the socket to the address - this was missing! + socklen_t addr_len = (ip->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + if (bind(e_sock->fd, (struct sockaddr*)ip, addr_len) < 0) { + perror("bind"); + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[ETCP] Failed to bind socket to address family %d", ip->ss_family); + if (ip->ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)ip; + char addr_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &sin->sin_addr, addr_str, INET_ADDRSTRLEN); + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[ETCP] Failed to bind to %s:%d", addr_str, ntohs(sin->sin_port)); + } + close(e_sock->fd); + free(e_sock); + return NULL; + } + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Successfully bound socket to local address, family=%d", ip->ss_family); + } else { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Socket binding skipped (NO_SOCKET_BIND flag)"); + } + } + + e_sock->instance = instance; + e_sock->errorcode = 0; + e_sock->pkt_format_errors = 0; + + // Add to instance's socket list + e_sock->next = instance->etcp_sockets; + instance->etcp_sockets = e_sock; + + // Register socket with uasync for receiving packets + e_sock->socket_id = uasync_add_socket(instance->ua, e_sock->fd, + etcp_connections_read_callback, + NULL, NULL, e_sock); + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Registered ETCP socket with uasync (fd=%d)", e_sock->fd); + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Socket %p (fd=%d) registered and active", e_sock, e_sock->fd); + + return e_sock; +} + +void etcp_socket_remove(struct ETCP_SOCKET* conn) { + if (!conn) return; + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Removing socket %p, fd=%d, socket_id=%p", conn, conn->fd, conn->socket_id); + + // Remove from uasync if registered + if (conn->socket_id) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Removing socket from uasync, instance=%p, ua=%p", conn->instance, conn->instance->ua); + uasync_remove_socket(conn->instance->ua, conn->socket_id); + conn->socket_id = NULL; + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Unregistered socket from uasync"); + } + + if (conn->fd >= 0) { + close(conn->fd); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Closed fd=%d", conn->fd); + } + + for (size_t i = 0; i < conn->num_channels; i++) { + etcp_link_close(conn->links[i]); + } + free(conn->links); + + free(conn); +} + + +struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server) { + if (!remote_addr) return NULL; + + struct ETCP_LINK* link = calloc(1, sizeof(struct ETCP_LINK)); + if (!link) return NULL; + + link->conn = conn; + link->etcp = etcp; + link->is_server = is_server; + link->mtu = 1500; + link->keepalive_interval = 30; + link->initialized = 0; + link->init_timer = NULL; + link->init_timeout = 0; + link->init_retry_count = 0; + + memcpy(&link->remote_addr, remote_addr, sizeof(struct sockaddr_storage)); + link->last_activity = time(NULL); + + link->ip_port_hash = sockaddr_hash(remote_addr); + + insert_link(conn, link); + + struct ETCP_LINK* l=etcp->links; + while (l && l->next) l=l->next; + if (l) l->next = link; else etcp->links = link; + + if (is_server == 0) { + etcp_link_send_init(link); + } + + return link; +} + +void etcp_link_close(struct ETCP_LINK* link) { + if (!link || !link->etcp) return; + + // Cancel init timer if active + if (link->init_timer) { + uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer); + link->init_timer = NULL; + } + + // универсальное удаление из односвязного списка + struct ETCP_LINK **pp = &link->etcp->links; + while (*pp) { + if (*pp == link) { + *pp = link->next; + break; + } + pp = &(*pp)->next; + } + + remove_link(link->conn, link->ip_port_hash); + + free(link); +} + +int etcp_encrypt_send(struct ETCP_DGRAM* dgram) { +// printf("[ETCP DEBUG] etcp_encrypt_send: ENTERING FUNCTION\n"); + + int errcode=0; + sc_context_t* sc = &dgram->link->etcp->crypto_ctx; + int len=dgram->data_len-dgram->noencrypt_len;// не забываем добавить timestamp (2 bytes) + if (len<=0 || len>1480) { dgram->link->send_errors++; errcode=1; goto es_err; } + uint8_t enc_buf[1600]; + size_t enc_buf_len=0; + dgram->timestamp=get_current_timestamp(); + + // DUMP: Show packet before encryption + packet_dump("ECTP_ENCRYPT_SEND", dgram->data, dgram->data_len, dgram->link); + sc_encrypt(sc, (uint8_t*)&dgram->timestamp/*не править это, тут верно!*/, sizeof(uint16_t) + len, enc_buf, &enc_buf_len); + if (enc_buf_len == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_encrypt_send: encryption failed for node %llu", (unsigned long long)dgram->link->etcp->instance->node_id); + dgram->link->send_errors++; + errcode=2; + goto es_err; + } + if (enc_buf_len + dgram->noencrypt_len > 1480) { dgram->link->send_errors++; errcode=2; goto es_err; } + memcpy(enc_buf+enc_buf_len, dgram->data+len, dgram->noencrypt_len); + + // DUMP: Show complete packet before sending +// dump_packet_bytes("READY TO SEND", enc_buf, enc_buf_len + dgram->noencrypt_len); + + struct sockaddr_storage* addr=&dgram->link->remote_addr; + socklen_t addr_len = (addr->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + + // Debug: print where we're sending the packet + if (addr->ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)addr; + char addr_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &sin->sin_addr, addr_str, INET_ADDRSTRLEN); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Sending packet to %s:%d, size=%zd", addr_str, ntohs(sin->sin_port), enc_buf_len + dgram->noencrypt_len); + } + + ssize_t sent = utun_sendto_hook(dgram->link->conn->fd, enc_buf, enc_buf_len + dgram->noencrypt_len, 0, (struct sockaddr*)addr, addr_len); + if (sent < 0) { dgram->link->send_errors++; errcode=3; goto es_err;} else dgram->link->total_encrypted += sent; + return (int)sent; +es_err: + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[ETCP] encrypt_send error %d", errcode); + return -1; +} + +static void etcp_connections_read_callback(int fd, void* arg) { +// !!!!!! DANGER: в этой функции ПРЕДЕЛЬНАЯ АККУРАТНОСТЬ. Если кажется что не туда указатель то невнимательно аланизировал !!!!! +// НЕ РУИНИТЬ (uint8_t*)&pkt->timestamp - это правильно !!!! +// +// Ошибки функции (errorcode): +// 1 - пакет слишком маленький для init (< SC_PUBKEY_SIZE) +// 2 - не удалось установить peer public key при init +// 3 - не удалось расшифровать init пакет +// 4 - не init пакет (неверный код) +// 5 - коллизия peer ID и ключей +// 6 - не удалось расшифровать обычный пакет +// 13 - переполнение при парсинге пакета +// 46 - расшифрованный пакет слишком маленький (< 3 байта) +// 55 - не удалось создать подключение +// 66 - не удалось создать линк + struct ETCP_SOCKET* e_sock = (struct ETCP_SOCKET*)arg; + if (!e_sock) return; + +// printf("[ETCP] Read callback triggered for fd=%d, socket=%p\n", fd, e_sock); + + struct sockaddr_storage addr; + uint8_t data[PACKET_DATA_SIZE]; + socklen_t addr_len=sizeof(addr); + memset(&addr, 0, sizeof(addr)); + ssize_t recv_len = utun_recvfrom_hook(fd, data, PACKET_DATA_SIZE, 0, (struct sockaddr*)&addr, &addr_len); + + if (recv_len <= 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[ETCP] recvfrom failed or no data, recv_len=%zd, errno=%d", recv_len, errno); + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: recvfrom failed, error=%zd, errno=%d", recv_len, errno); + return; + } + +// printf("[ETCP] Received packet: %zd bytes from address\n", recv_len); + + // DUMP: Show received packet content + packet_dump("RECV in:", data, recv_len, NULL); // link unknown at this point + + struct ETCP_DGRAM* pkt = memory_pool_alloc(e_sock->instance->pkt_pool); + if (!pkt) return; + size_t pkt_len=0; + int errorcode=0; + + struct ETCP_LINK* link=etcp_link_find_by_addr(e_sock, &addr); +// printf("[ETCP DEBUG] Received packet, link=%p, recv_len=%zd\n", link, recv_len); + if (link==NULL) {// пробуем расшифровать, возможно это init +// printf("[ETCP DEBUG] No existing link found, trying to decrypt as INIT packet\n"); + struct secure_channel sc; + if (recv_len<=SC_PUBKEY_SIZE) { errorcode=1; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: packet too small for init, size=%zd", recv_len); goto ec_fr; } + sc_init_ctx(&sc, &e_sock->instance->my_keys); +// printf("[ETCP DEBUG] Extracting peer public key from position %ld, total packet size=%zd\n", recv_len-SC_PUBKEY_SIZE, recv_len); +// printf("[ETCP DEBUG] Last 64 bytes of packet (PUBKEY): "); + for (int i=0; itimestamp, &pkt_len)) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_connections_read_callback: failed to decrypt init packet"); + errorcode=3; + goto ec_fr; + } +// printf("[ETCP DEBUG] Decrypt OK\n"); + pkt->data_len=pkt_len-2; + pkt->noencrypt_len=0; + struct { + uint8_t code; + uint8_t id[8]; + uint8_t mtu[2]; + uint8_t keepalive[2]; + uint8_t pubkey[SC_PUBKEY_SIZE]; + } *ack_hdr=(void*)&pkt->data[0]; + uint64_t peer_id; + memcpy(&peer_id, &ack_hdr->id[0], 8); + if (ack_hdr->code!=ETCP_INIT_REQUEST && ack_hdr->code!=ETCP_CHANNEL_INIT) { errorcode=4; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: not an init packet, code=%02x", ack_hdr->code); goto ec_fr; }// не init + + struct ETCP_CONN* conn=e_sock->instance->connections; + while (conn) {// ищем есть ли подключение к этому пиру + if (conn->peer_node_id==peer_id) break; + conn=conn->next; + } + + int new_conn=0; + if (!conn || conn->peer_node_id!=peer_id) {// создаём новое + new_conn=1; + conn=etcp_connection_create(e_sock->instance); + if (!conn) { errorcode=55; DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connections_read_callback: failed to create connection"); goto ec_fr; }// облом + memcpy(&conn->crypto_ctx, &sc, sizeof(sc));// добавляем ключ + conn->peer_node_id=peer_id; + } + else {// check keys если существующее подключение + if (memcmp(conn->crypto_ctx.peer_public_key, sc.peer_public_key, SC_PUBKEY_SIZE)) { errorcode=5; DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_connections_read_callback: peer key mismatch for node %llu", (unsigned long long)peer_id); goto ec_fr; }// коллизия - peer id совпал а ключи разные. + } + struct ETCP_LINK* link = etcp_link_new(conn, e_sock, &addr, 1); + if (!link) { if (new_conn) etcp_connection_close(conn); errorcode=66; DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connections_read_callback: failed to create link for connection"); goto ec_fr; }// облом + if (ack_hdr->code==0x02) etcp_conn_reset(conn); + + struct { + uint8_t code; + uint8_t id[8]; + uint8_t mtu[2]; + } *ack_repl_hdr=(void*)&pkt->data[0]; + ack_repl_hdr->code+=1; + memcpy(ack_repl_hdr->id, &e_sock->instance->node_id, 8); + int mtu=e_sock->instance->config->global.mtu; + ack_repl_hdr->mtu[0]=mtu>>8; + ack_repl_hdr->mtu[1]=mtu; + pkt->data_len=sizeof(*ack_repl_hdr); + pkt->noencrypt_len=0; + pkt->link=link; + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] Send INIT RESPONSE"); + etcp_encrypt_send(pkt); +// printf("[ETCP DEBUG] Send INIT RESPONSE ok\n"); + + memory_pool_free(e_sock->instance->pkt_pool, pkt); + return; + } + + if (sc_decrypt(&link->etcp->crypto_ctx, data, recv_len, (uint8_t*)&pkt->timestamp, &pkt_len)) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "etcp_connections_read_callback: failed to decrypt packet from node %llu", (unsigned long long)link->etcp->instance->node_id); + errorcode=6; + goto ec_fr; + } + if (pkt_len<3) { errorcode=46; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: decrypted packet too small, size=%zu", pkt_len); goto ec_fr; } + pkt->data_len=pkt_len-2; + pkt->noencrypt_len=0; + pkt->link=link; + + link->last_recv_local_time=get_current_time_units(); + link->last_recv_timestamp=pkt->timestamp; + + size_t offset = 0; + uint8_t code = pkt->data[offset++]; + + if (code == ETCP_INIT_RESPONSE || code == ETCP_CHANNEL_RESPONSE) { + // Parse response + if (code == ETCP_INIT_RESPONSE) etcp_conn_reset(link->etcp); + uint64_t server_node_id = 0; + for (int i = 0; i < 8; i++) { + server_node_id = (server_node_id << 8) | pkt->data[offset++]; + } + link->mtu = (pkt->data[offset++] << 8) | pkt->data[offset++]; + if (offset > pkt_len) { errorcode=13; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: packet parsing overflow, offset=%zu, pkt_len=%zu", offset, pkt_len); goto ec_fr; } + + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Received INIT_RESPONSE from server_node_id=%llu, mtu=%d", + (unsigned long long)server_node_id, link->mtu); + + link->etcp->peer_node_id = server_node_id; // If not set + + // Mark link as initialized + link->initialized = 1; + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[ETCP] Link initialized successfully! Server node_id=%llu, mtu=%d", + (unsigned long long)server_node_id, link->mtu); + + // Cancel init timer if exists + if (link->init_timer) { + uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer); + link->init_timer = NULL; + } + + memory_pool_free(e_sock->instance->pkt_pool, pkt); + return; // INIT_RESPONSE is handled, no further processing needed + } + + packet_dump("RECV decrypted:", pkt->data, pkt->data_len, link); + + etcp_conn_input(pkt); + return; + +ec_fr: + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: error %d", errorcode); + e_sock->pkt_format_errors++; + e_sock->errorcode=errorcode; + memory_pool_free(e_sock->instance->pkt_pool, pkt); + return; +} + +int init_connections(struct UTUN_INSTANCE* instance) { + if (!instance || !instance->config) return -1; + + struct utun_config* config = instance->config; + + // Initialize servers first - create sockets for incoming connections + struct CFG_SERVER* server = config->servers; + while (server) { + // Create socket for this server + struct ETCP_SOCKET* e_sock = etcp_socket_add(instance, &server->ip, server->netif_index, server->so_mark, server->type); + if (!e_sock) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create socket for server %s", server->name); + server = server->next; + continue; + } + + // Convert IP to string for logging + char addr_str[INET6_ADDRSTRLEN + 6]; + if (server->ip.ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)&server->ip; + inet_ntop(AF_INET, &sin->sin_addr, addr_str, INET_ADDRSTRLEN); + sprintf(addr_str + strlen(addr_str), ":%d", ntohs(sin->sin_port)); + } else { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&server->ip; + inet_ntop(AF_INET6, &sin6->sin6_addr, addr_str, INET6_ADDRSTRLEN); + sprintf(addr_str + strlen(addr_str), ":%d", ntohs(sin6->sin6_port)); + } + + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Initialized server %s on %s (links: %zu)", + server->name, addr_str, e_sock->num_channels); + server = server->next; + } + + // Initialize clients - create outgoing connections + struct CFG_CLIENT* client = config->clients; + while (client) { + // Create ETCP connection for this client + struct ETCP_CONN* etcp_conn = etcp_connection_create(instance); + if (!etcp_conn) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create ETCP connection for client %s", client->name); + client = client->next; + continue; + } + + // Initialize crypto context for this connection + if (sc_init_ctx(&etcp_conn->crypto_ctx, &instance->my_keys) != SC_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "init_connections: failed to initialize crypto context for client %s", client->name); + etcp_connection_close(etcp_conn); + client = client->next; + continue; + } + // If client has peer public key configured, set it + if (strlen(client->peer_public_key_hex) > 0) { + // For now, set peer node ID to indicate we have peer key + // The actual peer key will be exchanged during connection establishment + etcp_conn->peer_node_id = 1; // Simple indicator + + DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "init_connections: setting peer public key for client %s", client->name); + // Set peer public key (assuming hex format) + if (sc_set_peer_public_key(&etcp_conn->crypto_ctx, client->peer_public_key_hex, 1) != SC_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "init_connections: failed to set peer public key for client %s", client->name); + } else { + DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "init_connections: successfully set peer public key for client %s", client->name); + } + } else { + DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "init_connections: no peer public key configured for client %s", client->name); + } + + // Create links for this client + struct CFG_CLIENT_LINK* client_link = client->links; + while (client_link) { + // Find the local server for this link + struct CFG_SERVER* local_server = client_link->local_srv; + if (!local_server) { + client_link = client_link->next; + continue; + } + + // Find the socket for this server + struct ETCP_SOCKET* e_sock = NULL; + struct ETCP_SOCKET* sock = instance->etcp_sockets; + while (sock) { + if (sock->local_addr.ss_family == local_server->ip.ss_family) { + if (sock->local_addr.ss_family == AF_INET) { + struct sockaddr_in* sock_addr = (struct sockaddr_in*)&sock->local_addr; + struct sockaddr_in* srv_addr = (struct sockaddr_in*)&local_server->ip; + if (sock_addr->sin_addr.s_addr == srv_addr->sin_addr.s_addr && + sock_addr->sin_port == srv_addr->sin_port) { + e_sock = sock; + break; + } + } + } + sock = sock->next; + } + + if (!e_sock) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "No socket found for client %s link", client->name); + client_link = client_link->next; + continue; + } + + // Create link for this client connection + struct ETCP_LINK* link = etcp_link_new(etcp_conn, e_sock, &client_link->remote_addr, 0); // 0 = client initiates + if (!link) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create link for client %s", client->name); + client_link = client_link->next; + continue; + } + + client_link = client_link->next; + } + + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Added client %s with %d links", client->name, client->keepalive); + client = client->next; + } + + // If there are clients configured but no connections created, that's an error + // If there are no clients (server-only mode), 0 connections is OK (server will accept incoming) + if (instance->connections_count == 0 && config->clients != NULL) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Clients configured but no connections initialized"); + return -1; + } + + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Initialized %d connections", instance->connections_count); + return 0; +} + +// Wrapper for backward compatibility +struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct sockaddr_storage* ip, uint32_t netif_index, int so_mark, uint8_t type) { + return etcp_socket_add_ex(instance, ip, netif_index, so_mark, type, 0); +} diff --git a/src/etcp_connections.c1 b/!/etcp_connections.c1 similarity index 100% rename from src/etcp_connections.c1 rename to !/etcp_connections.c1 diff --git a/!/etcp_connections.h b/!/etcp_connections.h new file mode 100755 index 00000000..54fa4f71 --- /dev/null +++ b/!/etcp_connections.h @@ -0,0 +1,100 @@ +#ifndef ETCP_CONNECTIONS_H +#define ETCP_CONNECTIONS_H + +// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover) + +#include "secure_channel.h" +#include "utun_instance.h" +#include +#include + +#define PACKET_DATA_SIZE 1536 + +// Типы кодограмм протокола +#define ETCP_INIT_REQUEST 0x02 +#define ETCP_INIT_RESPONSE 0x03 +#define ETCP_CHANNEL_INIT 0x04 +#define ETCP_CHANNEL_RESPONSE 0x05 + + +struct ETCP_DGRAM {// пакет (незашифрованный) + struct ETCP_LINK* link;// откуда получена или куда отправялем + uint16_t data_len;// общий размер пакета не включая timestamp + uint16_t noencrypt_len;// число байт (с конца) которые не надо шифровать. для передачи pubkey + uint16_t timestamp;// timestamp принятого или для отправки + uint8_t data[0];// данные пакета (без timestamp) +}; + +// список активных подключений которые обслуживает сокет. каждый сокет может обслуживать много подключений +struct ETCP_SOCKET { + struct ETCP_SOCKET* next; // Linked list для всех соединений + struct UTUN_INSTANCE* instance; + int fd; // Файловый дескриптор UDP сокета + struct sockaddr_storage local_addr; // Локальный адрес + + // для входящих подключений (links) - массив упорядоченный по ip_port_hash + size_t max_channels; // сколько выделено памяти + size_t num_channels; // сколько активно + struct ETCP_LINK** links;// массив указателей на линки, сортированный по ip_port_hash + int errorcode; + size_t pkt_format_errors; + + void* socket_id; // Socket ID from uasync_add_socket +}; + +// ETCP Link - одно динамическое соединение (один путь) +struct ETCP_LINK { + uint32_t ip_port_hash; // crc32 для быстрого поиска + struct ETCP_LINK* next; // Linked list подключений для ETCP_CONN (каждое подключение это child для ETCP_CONN) + + struct ETCP_CONN* etcp; // подключение (parent) + struct ETCP_SOCKET* conn; // сокет через который работаем + + // Путь соединения + struct sockaddr_storage remote_addr; // Удалённый адрес + + // Параметры соединения + uint16_t mtu; // MTU удаленного узла + uint16_t keepalive_interval; // Keepalive интервал + uint8_t is_server; // инициирует подключение клиент + uint8_t initialized; // Флаг инициализации (1=подтверждено или получен request) + + // Состояние установки соединения (только для клиентов) + void* init_timer; // Таймер для повторов INIT (NULL=не подключается) + uint16_t init_timeout; // Текущий таймаут в мс + uint16_t init_retry_count; // Счетчик попыток + + uint64_t last_activity; // Время последней активности + uint64_t last_recv_local_time; + uint16_t last_recv_timestamp; + + size_t encrypt_errors; + size_t decrypt_errors; + size_t send_errors; + size_t recv_errors; + size_t total_encrypted; + size_t total_decrypted; + + uint32_t bandwidth; // Link bandwidth in bits/sec +}; + +// INITIALIZATION (создаёт listen-сокеты и подключения из конфига) +int init_connections(struct UTUN_INSTANCE* instance); + +// SOCKET FUNCTIONS +// добавляет новый версер (сокет для приёма и отправки кодограмм. обслуживает много подключений) +struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct sockaddr_storage* ip, uint32_t netif_index, int so_mark, uint8_t type); +struct ETCP_SOCKET* etcp_socket_add_ex(struct UTUN_INSTANCE* instance, struct sockaddr_storage* ip, uint32_t netif_index, int so_mark, uint8_t type, uint32_t flags); +// удаляет сокет и освобождает ресурсы (грохает все его подключения и сокет) +void etcp_socket_remove(struct ETCP_SOCKET* conn); + +// connection functions +// создает новый канал связи для etcp подключения (ETCP_CONN) +struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server); +void etcp_link_close(struct ETCP_LINK* link); +//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет +int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет +// find link by address +struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr); + +#endif // ETCP_CONNECTIONS_H diff --git a/!/etcp_loadbalancer.c b/!/etcp_loadbalancer.c new file mode 100755 index 00000000..7e0e16c5 --- /dev/null +++ b/!/etcp_loadbalancer.c @@ -0,0 +1,164 @@ +// etcp_loadbalancer.c - Load Balancer Implementation (based on etcp_protocol.txt) +#include "etcp_loadbalancer.h" +#include "../lib/debug_config.h" +#include "../lib/u_async.h" +#include + +// Enable comprehensive debug output for loadbalancer module +#define DEBUG_CATEGORY_LOADBALANCER 1 + +// Forward declaration +static void init_timeout_cb(void* arg); + +// Constants +#define TIMEBASE_NS 100000 // 0.1ms = 100us = 100000ns +#define DELTA_TIME_NS 10000 // Example delta + +// Internal +static uint64_t get_current_nanotime() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t nanotime = (uint64_t)ts.tv_sec * 1000000000ULL + ts.tv_nsec; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_nanotime: tv_sec=%ld, tv_nsec=%ld, result=%llu", + ts.tv_sec, ts.tv_nsec, (unsigned long long)nanotime); + return nanotime; +} + +// Select link for transmission (per spec) +struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: etcp=%p", etcp); + + if (!etcp || !etcp->links) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: invalid parameters (etcp=%p, links=%p)", + etcp, etcp ? etcp->links : NULL); + return NULL; + } + + struct ETCP_LINK* best = NULL; + uint64_t min_load_time = UINT64_MAX; + uint64_t now_ns = get_current_nanotime(); + uint64_t now_tb = now_ns / (TIMEBASE_NS / 10); // To 0.1ms units + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: current time=%llu tb, scanning %d links", + (unsigned long long)now_tb, etcp->links ? 1 : 0); + + struct ETCP_LINK* link = etcp->links; + int link_index = 0; + while (link) { + link_index++; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: evaluating link %d (%p) - initialized=%u, is_server=%u, bandwidth=%u", + link_index, link, link->initialized, link->is_server, link->bandwidth); + + if (!link->initialized) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d not initialized, checking if client connection needed", + link_index); + // Initiate connection if client + if (!link->is_server && !link->init_timer) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: initiating client connection for link %d", link_index); + // Send INIT (via etcp_encrypt_send with special dgram) + // Set timer + link->init_timer = uasync_set_timeout(etcp->instance->ua, link->init_timeout, link, init_timeout_cb); // Define cb + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: set init timer for link %d", link_index); + } + link = link->next; + continue; + } + + // Update load time if inactive + if (link->last_activity < now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10))) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d inactive, updating last_activity from %llu to %llu", + link_index, (unsigned long long)link->last_activity, + (unsigned long long)(now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10)))); + link->last_activity = now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10)); + } + + // Check if can send (load time < now) + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d last_activity=%llu, now_tb=%llu", + link_index, (unsigned long long)link->last_activity, (unsigned long long)now_tb); + + if (link->last_activity < now_tb) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d can send (load_time < now)", link_index); + if (link->last_activity < min_load_time) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d is better candidate (activity=%llu < %llu)", + link_index, (unsigned long long)link->last_activity, (unsigned long long)min_load_time); + min_load_time = link->last_activity; + best = link; + } + } else { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d cannot send yet (load_time >= now)", link_index); + } + + link = link->next; + } + + if (best) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: selected link %p (activity=%llu)", + best, (unsigned long long)min_load_time); + } else { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: no suitable link found"); + } + + return best; +} + +// Init timeout callback (placeholder) +static void init_timeout_cb(void* arg) { + struct ETCP_LINK* link = (struct ETCP_LINK*)arg; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_timeout_cb: link=%p, retry_count=%u, timeout=%u", + link, link->init_retry_count, link->init_timeout); + + // Resend INIT, increment retry + link->init_retry_count++; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_timeout_cb: incremented retry_count to %u", link->init_retry_count); + + if (link->init_retry_count > 5) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "init_timeout_cb: max retries exceeded (%u > 5), closing link", link->init_retry_count); + // Fail + etcp_link_close(link); + return; + } + + // Resend... + link->init_timeout *= 2; // Backoff + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_timeout_cb: doubled timeout to %u ms", link->init_timeout); + link->init_timer = uasync_set_timeout(link->etcp->instance->ua, link->init_timeout, link, init_timeout_cb); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_timeout_cb: rescheduled timer"); +} + +// Bandwidth limit update (called after send) +void etcp_loadbalancer_update_after_send(struct ETCP_LINK* link, size_t pkt_size) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: link=%p, pkt_size=%zu", link, pkt_size); + + if (!link) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: called with NULL link"); + return; + } + + if (link->bandwidth == 0) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: link bandwidth is 0, skipping update"); + return; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: link bandwidth=%u bits/sec, packet size=%zu bytes", + link->bandwidth, pkt_size); + + // Time to transmit (ns) + double byte_time_ns = 1000000000.0 / (link->bandwidth * 8.0); // bits/sec to byte/ns + uint64_t tx_time_ns = (uint64_t)(pkt_size * byte_time_ns); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: byte_time_ns=%.2f, tx_time_ns=%llu", + byte_time_ns, (unsigned long long)tx_time_ns); + + // To timebase (0.1ms units) + uint64_t tx_time_tb = tx_time_ns / (TIMEBASE_NS / 10); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: tx_time_tb=%llu", (unsigned long long)tx_time_tb); + + // Update last_activity + uint64_t now_tb = get_current_nanotime() / (TIMEBASE_NS / 10); + uint64_t old_activity = link->last_activity; + link->last_activity += tx_time_tb; + if (link->last_activity < now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10))) { + link->last_activity = now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10)); + } + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: updated last_activity from %llu to %llu", + (unsigned long long)old_activity, (unsigned long long)link->last_activity); +} \ No newline at end of file diff --git a/!/etcp_loadbalancer.h b/!/etcp_loadbalancer.h new file mode 100755 index 00000000..7e46d309 --- /dev/null +++ b/!/etcp_loadbalancer.h @@ -0,0 +1,20 @@ +// etcp_loadbalancer.h - Load Balancer for ETCP Channels +#ifndef ETCP_LOADBALANCER_H +#define ETCP_LOADBALANCER_H + +#include "etcp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Functions +struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp); + +// Add more as needed (e.g., update bandwidth) + +#ifdef __cplusplus +} +#endif + +#endif // ETCP_LOADBALANCER_H \ No newline at end of file diff --git a/src/fix.patch b/!/fix.patch old mode 100644 new mode 100755 similarity index 100% rename from src/fix.patch rename to !/fix.patch diff --git a/!/packet_dump.h b/!/packet_dump.h new file mode 100755 index 00000000..7c15f3d0 --- /dev/null +++ b/!/packet_dump.h @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include + +// Packet dump function for debugging +static void dump_packet(const char* direction, const uint8_t* data, size_t len, struct sockaddr_storage* addr) { + printf("[DUMP] %s packet: %zd bytes ", direction, len); + + // Print address if provided + if (addr) { + if (addr->ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)addr; + printf("to/from %s:%d ", + inet_ntoa(sin->sin_addr), + ntohs(sin->sin_port)); + } + } + + // Print first 64 bytes + printf("data: "); + for (size_t i = 0; i < len && i < 64; i++) { + printf("%02x", data[i]); + if (i % 4 == 3) printf(" "); + } + if (len > 64) printf("..."); + + // If it's INIT packet, parse fields + if (len > 0 && data[0] == 0x02) { // ETCP_INIT_REQUEST + if (len >= 15) { // Minimum INIT size: code(1) + node_id(8) + mtu(2) + keepalive(2) + pubkey(2 at least) + uint64_t node_id = 0; + memcpy(&node_id, data + 1, 8); + uint16_t mtu = (data[9] << 8) | data[10]; + uint16_t keepalive = (data[11] << 8) | data[12]; + printf(" | INIT: node_id=%llu mtu=%d keepalive=%d", + (unsigned long long)node_id, mtu, keepalive); + } + } + + printf("\n"); +} \ No newline at end of file diff --git a/!/pkt_normalizer.c b/!/pkt_normalizer.c new file mode 100755 index 00000000..cfc1cfc2 --- /dev/null +++ b/!/pkt_normalizer.c @@ -0,0 +1,604 @@ +#include "pkt_normalizer.h" +#include "../lib/u_async.h" +#include +#include +#include +#include +static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* arg); +static void unpacker_handler(struct ll_queue* q, struct ll_entry* unused, void* arg); +static void send_buf(struct pn_struct* pn); +static int get_header(uint8_t* header, size_t L); +/* Calculate fragment size from mtu */ +struct pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer, int mtu) { + struct pn_struct* pn = malloc(sizeof(struct pn_struct)); + if (!pn) return NULL; + pn->ua = ua; + pn->input = queue_new(ua, NULL); // No memory pool for now + if (!pn->input) { + free(pn); + return NULL; + } + pn->output = queue_new(ua, NULL); // No memory pool for now + if (!pn->output) { + queue_free(pn->input); + free(pn); + return NULL; + } + pn->is_packer = is_packer; + if (is_packer) { + // Calculate fragment size from mtu: fragment = mtu - 100 + int fragment_size = mtu - ETCP_OVERHEAD; + if (fragment_size < 256) fragment_size = 256; // Minimum sane value + pn->u.packer.cap = fragment_size; + pn->u.packer.buf = malloc(pn->u.packer.cap); + if (!pn->u.packer.buf) { + queue_free(pn->input); + queue_free(pn->output); + free(pn); + return NULL; + } + pn->u.packer.len = 0; + pn->u.packer.error_count = 0; + queue_set_callback(pn->input, packer_handler, pn); + } else { + pn->u.unpacker.buf = NULL; + pn->u.unpacker.len = 0; + pn->u.unpacker.total_len = 0; + pn->u.unpacker.cap = 0; + pn->u.unpacker.error_count = 0; + queue_set_callback(pn->input, unpacker_handler, pn); + } + return pn; +} + +void pkt_normalizer_deinit(struct pn_struct* pn) { + if (!pn) return; + queue_free(pn->input); + queue_free(pn->output); + if (pn->is_packer) { + free(pn->u.packer.buf); + } else { + free(pn->u.unpacker.buf); + free(pn->u.unpacker.service_buf); + } + free(pn); +} +struct pkt_normalizer_pair* pkt_normalizer_pair_init(uasync_t* ua, int mtu) { + struct pkt_normalizer_pair* pair = malloc(sizeof(struct pkt_normalizer_pair)); + if (!pair) return NULL; + pair->packer = pkt_normalizer_init(ua, 1, mtu); + if (!pair->packer) { + free(pair); + return NULL; + } + pair->unpacker = pkt_normalizer_init(ua, 0, mtu); + if (!pair->unpacker) { + pkt_normalizer_deinit(pair->packer); + free(pair); + return NULL; + } + return pair; +} +void pkt_normalizer_pair_deinit(struct pkt_normalizer_pair* pair) { + if (!pair) return; + pkt_normalizer_deinit(pair->packer); + pkt_normalizer_deinit(pair->unpacker); + free(pair); +} +static int get_header(uint8_t* header, size_t L) { + if (L > 1535) return -1; + if (L <= 239) { + header[0] = (uint8_t)L; + return 1; + } else { + uint8_t high = (uint8_t)(L >> 8); + if (high > 5) return -1; + header[0] = 0xF0 + high; + header[1] = (uint8_t)(L & 0xFF); + return 2; + } +} +/* Сбросить состояние сборки фрагментов */ +static void reset_fragment_state(struct pn_struct* pn) { + if (!pn->is_packer) { + pn->u.unpacker.len = 0; + pn->u.unpacker.total_len = 0; + pn->u.unpacker.in_fragment = 0; + } +} +/* Таймаут для сборки фрагментов */ +static void send_buf(struct pn_struct* pn) { + if (pn->u.packer.len == 0) return; + size_t payload_len = pn->u.packer.len; + struct ll_entry* out = queue_entry_new(2 + payload_len); + if (!out) return; + uint8_t* d = ll_entry_data(out); + *(uint16_t*)d = (uint16_t)payload_len; + memcpy(d + 2, pn->u.packer.buf, payload_len); + queue_entry_put(pn->output, out); + pn->u.packer.len = 0; +} +static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* arg) { + (void)unused; + struct pn_struct* pn = arg; + size_t max = (size_t)1400; + struct ll_entry* entry = queue_entry_get(q); + if (!entry) { + queue_resume_callback(q); + return; + } + size_t L = ll_entry_size(entry); + uint8_t* data = ll_entry_data(entry); + uint8_t header[2]; + int hsize = get_header(header, L); + size_t needed = (size_t)hsize + L; + if (hsize < 0 || needed > max) { + // Fragment + if (pn->u.packer.len > 0) { + send_buf(pn); + } + size_t remaining = L; + size_t pos = 0; + int fragment_count = 0; + while (remaining > 0) { + size_t chunk; + size_t payload_len; + struct ll_entry* fout; + uint8_t* fd; + uint8_t frag_header[2]; + int frag_hsize; + if (fragment_count == 0) { + // Первый фрагмент: FF + общая длина (2 байта) + chunk = remaining > (max - 5) ? (max - 5) : remaining; // 2+1+2+chunk <= max + payload_len = 1 + 2 + chunk; // FF + total_len + data + fout = queue_entry_new(2 + payload_len); + if (!fout) { + break; + } + fd = ll_entry_data(fout); + *(uint16_t*)fd = (uint16_t)payload_len; + fd += 2; + *fd++ = 0xFF; + *fd++ = (uint8_t)(L >> 8); // старший байт общей длины + *fd++ = (uint8_t)(L & 0xFF); // младший байт общей длины + } else { + // Не первый фрагмент + if (remaining <= max - 3) { + // Это последний возможный фрагмент (помещается в один пакет с префиксом FE) + // Пытаемся отправить как обычный блок + frag_hsize = get_header(frag_header, remaining); + if (frag_hsize > 0 && (size_t)frag_hsize + remaining + 2 <= max) { + // Успешно: обычный блок + payload_len = frag_hsize + remaining; + chunk = remaining; + fout = queue_entry_new(2 + payload_len); + if (!fout) { + break; + } + fd = ll_entry_data(fout); + *(uint16_t*)fd = (uint16_t)payload_len; + fd += 2; + memcpy(fd, frag_header, frag_hsize); + fd += frag_hsize; + } else { + // Не удалось отправить как обычный блок - разбиваем на 2 фрагмента + // 1. FE фрагмент с частью данных + // 2. Обычный блок с оставшимися данными + // Находим максимальный размер для FE фрагмента + size_t max_fe_data = max - 3; // 2 байта длины + 0xFE + if (max_fe_data > remaining) { + max_fe_data = remaining; + } + // Пробуем различные размеры, начиная с максимального + size_t fe_data_size = 0; + for (size_t try_fe = max_fe_data; try_fe > 0; try_fe--) { + size_t try_regular = remaining - try_fe; + if (try_regular == 0) continue; // Нужно отправить что-то как обычный блок + uint8_t test_header[2]; + int hsize = get_header(test_header, try_regular); + if (hsize <= 0) continue; + if ((size_t)hsize + try_regular + 2 <= max) { + fe_data_size = try_fe; + break; + } + } + if (fe_data_size == 0) { + // Не удалось найти разбиение - ошибка + pn->u.packer.error_count++; + // Отправляем как FE (нарушение спецификации, но это крайний случай) + chunk = remaining > (max - 3) ? (max - 3) : remaining; + payload_len = 1 + chunk; // FE + data + fout = queue_entry_new(2 + payload_len); + if (!fout) break; + fd = ll_entry_data(fout); + *(uint16_t*)fd = (uint16_t)payload_len; + fd += 2; + *fd++ = 0xFE; + } else { + // Отправляем FE фрагмент + chunk = fe_data_size; + payload_len = 1 + chunk; // FE + data + fout = queue_entry_new(2 + payload_len); + if (!fout) break; + fd = ll_entry_data(fout); + *(uint16_t*)fd = (uint16_t)payload_len; + fd += 2; + *fd++ = 0xFE; + memcpy(fd, data + pos, chunk); + queue_entry_put(pn->output, fout); + pos += chunk; + remaining -= chunk; + fragment_count++; + // Обновляем оставшиеся данные для обычного блока + // (цикл продолжит обработку на следующей итерации) + continue; + } + } + } else { + // Промежуточный фрагмент, отправляем как FE + chunk = remaining > (max - 3) ? (max - 3) : remaining; + payload_len = 1 + chunk; // FE + data + fout = queue_entry_new(2 + payload_len); + if (!fout) { + break; + } + fd = ll_entry_data(fout); + *(uint16_t*)fd = (uint16_t)payload_len; + fd += 2; + *fd++ = 0xFE; + } + } + memcpy(fd, data + pos, chunk); + queue_entry_put(pn->output, fout); + pos += chunk; + remaining -= chunk; + fragment_count++; + } + } else { + if (pn->u.packer.len + needed > max) { + send_buf(pn); + } + // Add to buffer + uint8_t* p = pn->u.packer.buf + pn->u.packer.len; + memcpy(p, header, (size_t)hsize); + memcpy(p + hsize, data, L); + pn->u.packer.len += needed; + } + queue_entry_free(entry); + if (pn->u.packer.len > 0) { + send_buf(pn); + } + queue_resume_callback(q); +} +void pkt_normalizer_set_service_callback(struct pn_struct* pn, pkt_normalizer_service_callback_t callback, void* user_data) { + if (!pn) return; + pn->service_callback = callback; + pn->service_callback_user_data = user_data; +} +void pkt_normalizer_reset_service_state(struct pn_struct* pn) { + if (!pn || pn->is_packer) return; + if (pn->u.unpacker.in_service) { + // Deliver pending service packet + if (pn->service_callback) { + pn->service_callback(pn->service_callback_user_data, + pn->u.unpacker.service_type, + pn->u.unpacker.service_buf, + pn->u.unpacker.service_len); + } + free(pn->u.unpacker.service_buf); + pn->u.unpacker.service_buf = NULL; + pn->u.unpacker.service_len = 0; + pn->u.unpacker.service_cap = 0; + pn->u.unpacker.in_service = 0; + } +} +void pkt_normalizer_reset_state(struct pn_struct* pn) { + if (!pn) return; + if (pn->is_packer) { + // Flush packer buffer + if (pn->u.packer.len > 0) { + send_buf(pn); + } + } else { + // Reset unpacker fragment state + reset_fragment_state(pn); + // Reset service state + pkt_normalizer_reset_service_state(pn); + } +} +int pkt_normalizer_send_service(struct pn_struct* pn, uint8_t type, const void* data, size_t len) { + if (!pn || !pn->is_packer) return -1; + // Service packet ограничен 256 байтами всего + if (len > 256 - 2) return -1; // 2 байта на заголовок (0xFC + тип) + size_t max = (size_t)1400; + if (max < 3) return -1; + // Размер сервисного пакета: 2 байта длины + 1 байт 0xFC + 1 байт тип + данные + // Если не помещается в один фрагмент - используем продолжение 0xFD + size_t total_service_len = 1 + 1 + len; // 0xFC + type + data + size_t pos = 0; + while (total_service_len > 0) { + // Определяем размер куска для этого пакета + size_t chunk; + uint8_t service_header; + if (pos == 0) { + // Первый пакет: 0xFC + тип + часть данных + // Максимум данных в первом пакете: max - 2 (длина) - 2 (0xFC+тип) + size_t max_first_data = max - 4; + if (max_first_data > len) max_first_data = len; + chunk = max_first_data; + service_header = 0xFC; + } else { + // Продолжение: 0xFD + данные + // Максимум данных: max - 2 (длина) - 1 (0xFD) + size_t max_cont_data = max - 3; + size_t remaining = len - pos; + if (max_cont_data > remaining) max_cont_data = remaining; + chunk = max_cont_data; + service_header = 0xFD; + } + if (chunk == 0) break; + size_t payload_len = 1 + chunk + (pos == 0 ? 1 : 0); // +1 байт типа для первого пакета + struct ll_entry* entry = queue_entry_new(2 + payload_len); + if (!entry) return -1; + uint8_t* d = ll_entry_data(entry); + *(uint16_t*)d = (uint16_t)payload_len; + d += 2; + *d++ = service_header; + if (pos == 0) { + *d++ = type; + } + memcpy(d, (const uint8_t*)data + pos, chunk); + queue_entry_put(pn->output, entry); + pos += chunk; + total_service_len -= chunk + (pos == chunk ? 2 : 1); // корректно вычитаем заголовки + } + return 0; +} +int pkt_normalizer_get_error_count(const struct pn_struct* pn) { + if (!pn) return 0; + if (pn->is_packer) { + return pn->u.packer.error_count; + } + return pn->u.unpacker.error_count; +} +void pkt_normalizer_reset_error_count(struct pn_struct* pn) { + if (!pn) return; + if (pn->is_packer) { + pn->u.packer.error_count = 0; + } else { + pn->u.unpacker.error_count = 0; + } +} +void pkt_normalizer_flush(struct pn_struct* pn) { + if (!pn || !pn->is_packer) return; + if (pn->u.packer.len > 0) { + send_buf(pn); + } +} +static void unpacker_handler(struct ll_queue* q, struct ll_entry* unused, void* arg) { + (void)unused; + struct pn_struct* pn = arg; + while (queue_entry_count(q) > 0) { + struct ll_entry* entry = queue_entry_get(q); + uint8_t* data = ll_entry_data(entry); + size_t total = ll_entry_size(entry); + uint16_t payload_len = *(uint16_t*)data; + if (total != 2 + (size_t)payload_len) { + queue_entry_free(entry); + continue; + } + uint8_t* cg = data + 2; + size_t cg_pos = 0; + size_t cg_len = (size_t)payload_len; + while (cg_pos < cg_len) { + uint8_t byte = cg[cg_pos++]; + if (byte == 0xFF) { + /* Начало нового фрагментированного пакета */ + if (pn->u.unpacker.in_fragment) { + /* Не завершен предыдущий фрагмент - ошибка */ + pn->u.unpacker.error_count++; + reset_fragment_state(pn); + } + /* Проверить, что есть 2 байта для общей длины */ + if (cg_pos + 2 > cg_len) { + pn->u.unpacker.error_count++; + goto err; + } + /* Прочитать общую длину */ + uint16_t total_len = ((uint16_t)cg[cg_pos] << 8) | cg[cg_pos + 1]; + cg_pos += 2; + size_t chunk_len = cg_len - cg_pos; + /* Выделить буфер при необходимости */ + size_t new_len = chunk_len; + if (new_len > pn->u.unpacker.cap) { + size_t new_cap = pn->u.unpacker.cap ? pn->u.unpacker.cap * 2 : 4096; + if (new_cap < new_len) new_cap = new_len; + pn->u.unpacker.buf = realloc(pn->u.unpacker.buf, new_cap); + pn->u.unpacker.cap = new_cap; + } + memcpy(pn->u.unpacker.buf, cg + cg_pos, chunk_len); + pn->u.unpacker.len = chunk_len; + pn->u.unpacker.total_len = total_len; + pn->u.unpacker.in_fragment = 1; + cg_pos += chunk_len; + /* Проверить, не собрали ли уже весь пакет */ + if (pn->u.unpacker.len >= pn->u.unpacker.total_len) { + if (pn->u.unpacker.len == pn->u.unpacker.total_len) { + struct ll_entry* out = queue_entry_new(pn->u.unpacker.total_len); + if (out) { + memcpy(ll_entry_data(out), pn->u.unpacker.buf, pn->u.unpacker.total_len); + queue_entry_put(pn->output, out); + } + } else { + /* Слишком много данных - ошибка */ + pn->u.unpacker.error_count++; + } + reset_fragment_state(pn); + } + continue; + } + if (byte == 0xFE) { + /* Продолжение фрагментированного пакета */ + if (!pn->u.unpacker.in_fragment) { + /* Не было начала фрагмента - ошибка */ + pn->u.unpacker.error_count++; + goto err; + } + size_t chunk_len = cg_len - cg_pos; + size_t new_len = pn->u.unpacker.len + chunk_len; + if (new_len > pn->u.unpacker.cap) { + size_t new_cap = pn->u.unpacker.cap ? pn->u.unpacker.cap * 2 : 4096; + if (new_cap < new_len) new_cap = new_len; + pn->u.unpacker.buf = realloc(pn->u.unpacker.buf, new_cap); + pn->u.unpacker.cap = new_cap; + } + memcpy(pn->u.unpacker.buf + pn->u.unpacker.len, cg + cg_pos, chunk_len); + pn->u.unpacker.len = new_len; + cg_pos += chunk_len; + /* Проверить, не собрали ли уже весь пакет */ + if (pn->u.unpacker.len >= pn->u.unpacker.total_len) { + if (pn->u.unpacker.len == pn->u.unpacker.total_len) { + struct ll_entry* out = queue_entry_new(pn->u.unpacker.total_len); + if (out) { + memcpy(ll_entry_data(out), pn->u.unpacker.buf, pn->u.unpacker.total_len); + queue_entry_put(pn->output, out); + } + } else { + /* Слишком много данных - ошибка */ + pn->u.unpacker.error_count++; + } + reset_fragment_state(pn); + } + continue; + } + if (byte == 0xFC || byte == 0xFD) { + /* Service packet */ + if (byte == 0xFC) { + /* Start of service packet */ + if (pn->u.unpacker.in_service) { + /* Previous service packet finished - deliver it */ + if (pn->service_callback) { + pn->service_callback(pn->service_callback_user_data, + pn->u.unpacker.service_type, + pn->u.unpacker.service_buf, + pn->u.unpacker.service_len); + } + free(pn->u.unpacker.service_buf); + pn->u.unpacker.service_buf = NULL; + pn->u.unpacker.service_len = 0; + pn->u.unpacker.service_cap = 0; + pn->u.unpacker.in_service = 0; + } + /* Read service type */ + if (cg_pos >= cg_len) goto err; + uint8_t service_type = cg[cg_pos++]; + pn->u.unpacker.service_type = service_type; + pn->u.unpacker.in_service = 1; + pn->u.unpacker.service_len = 0; + } else { + /* 0xFD - continuation */ + if (!pn->u.unpacker.in_service) { + /* No service packet started - error */ + pn->u.unpacker.error_count++; + goto err; + } + } + /* Read data */ + size_t data_len = cg_len - cg_pos; + if (data_len > 0) { + size_t new_len = pn->u.unpacker.service_len + data_len; + if (new_len > 256) { + /* Service packet too long - error */ + pn->u.unpacker.error_count++; + free(pn->u.unpacker.service_buf); + pn->u.unpacker.service_buf = NULL; + pn->u.unpacker.service_len = 0; + pn->u.unpacker.service_cap = 0; + pn->u.unpacker.in_service = 0; + goto err; + } + if (new_len > pn->u.unpacker.service_cap) { + size_t new_cap = pn->u.unpacker.service_cap ? pn->u.unpacker.service_cap * 2 : 256; + if (new_cap < new_len) new_cap = new_len; + if (new_cap > 256) new_cap = 256; + uint8_t* new_buf = realloc(pn->u.unpacker.service_buf, new_cap); + if (!new_buf) { + pn->u.unpacker.error_count++; + goto err; + } + pn->u.unpacker.service_buf = new_buf; + pn->u.unpacker.service_cap = new_cap; + } + memcpy(pn->u.unpacker.service_buf + pn->u.unpacker.service_len, cg + cg_pos, data_len); + pn->u.unpacker.service_len = new_len; + cg_pos += data_len; + } + /* Check if this is the end of service packet (end of payload) */ + if (cg_pos >= cg_len) { + /* End of current payload, but service packet may continue in next transport packet */ + continue; + } else { + /* There is more data in this payload after service packet - error */ + pn->u.unpacker.error_count++; + free(pn->u.unpacker.service_buf); + pn->u.unpacker.service_buf = NULL; + pn->u.unpacker.service_len = 0; + pn->u.unpacker.service_cap = 0; + pn->u.unpacker.in_service = 0; + goto err; + } + } + /* Обычная запись (не фрагмент) */ + size_t L; + if (byte <= 0xEF) { + L = byte; + } else if (byte >= 0xF0 && byte <= 0xF5) { + if (cg_pos >= cg_len) goto err; + uint8_t ext = cg[cg_pos++]; + L = ((size_t)(byte - 0xF0) << 8) | ext; + } else { + /* Недопустимый байт */ + goto err; + } + if (cg_pos + L > cg_len) goto err; + if (pn->u.unpacker.in_fragment) { + /* Это последний фрагмент в виде обычной записи */ + size_t new_len = pn->u.unpacker.len + L; + if (new_len > pn->u.unpacker.cap) { + size_t new_cap = pn->u.unpacker.cap ? pn->u.unpacker.cap * 2 : 4096; + if (new_cap < new_len) new_cap = new_len; + pn->u.unpacker.buf = realloc(pn->u.unpacker.buf, new_cap); + pn->u.unpacker.cap = new_cap; + } + memcpy(pn->u.unpacker.buf + pn->u.unpacker.len, cg + cg_pos, L); + pn->u.unpacker.len = new_len; + cg_pos += L; + /* Проверить, собрали ли весь пакет */ + if (pn->u.unpacker.len >= pn->u.unpacker.total_len) { + if (pn->u.unpacker.len == pn->u.unpacker.total_len) { + struct ll_entry* out = queue_entry_new(pn->u.unpacker.total_len); + if (out) { + memcpy(ll_entry_data(out), pn->u.unpacker.buf, pn->u.unpacker.total_len); + queue_entry_put(pn->output, out); + } + } else { + /* Слишком много данных - ошибка */ + pn->u.unpacker.error_count++; + } + reset_fragment_state(pn); + } + } else { + /* Обычная запись (не часть фрагмента) */ + struct ll_entry* out = queue_entry_new(L); + if (out) { + memcpy(ll_entry_data(out), cg + cg_pos, L); + queue_entry_put(pn->output, out); + } + cg_pos += L; + } + } + err: + queue_entry_free(entry); + } + queue_resume_callback(q); +} diff --git a/!/pkt_normalizer.h b/!/pkt_normalizer.h new file mode 100755 index 00000000..f709bf52 --- /dev/null +++ b/!/pkt_normalizer.h @@ -0,0 +1,75 @@ +// pkt_normalizer.h +#ifndef PKT_NORMALIZER_H +#define PKT_NORMALIZER_H + +#include "ll_queue.h" +#include "../lib/u_async.h" +#include + +/* Default fragment reassembly timeout in uasync timebase units (0.1 ms) */ +#ifndef PKT_NORMALIZER_FRAGMENT_TIMEOUT +#define PKT_NORMALIZER_FRAGMENT_TIMEOUT 5000 /* 500 ms */ +#endif + +/* ETCP overhead for calculating fragment size from MTU */ +#define ETCP_OVERHEAD 100 // Reserve 100 bytes for headers, crypto, etc.*/ + +/* Service packet callback type */ +typedef void (*pkt_normalizer_service_callback_t)(void* user_data, uint8_t type, const uint8_t* data, size_t len); + +struct pn_struct { + struct ll_queue* input; + struct ll_queue* output; + uasync_t* ua; + int is_packer; + union { + struct { + uint8_t* buf; + size_t len; + size_t cap; + int error_count; + } packer; + struct { + uint8_t* buf; /* буфер для сборки фрагментов */ + size_t len; /* текущая накопленная длина */ + size_t total_len; /* ожидаемая общая длина из первого фрагмента */ + size_t cap; /* ёмкость буфера */ + int error_count; /* счетчик ошибок сборки */ + int in_fragment; /* флаг: идет сборка фрагментов (1) или нет (0) */ + /* Service packet reassembly */ + uint8_t* service_buf; /* буфер для сборки сервисных пакетов */ + size_t service_len; /* текущая накопленная длина сервисного пакета */ + size_t service_cap; /* ёмкость буфера сервисного пакета */ + uint8_t service_type; /* тип сервисного пакета */ + int in_service; /* флаг: идет сборка сервисного пакета (1) или нет (0) */ + } unpacker; + } u; + /* Service packet callback */ + pkt_normalizer_service_callback_t service_callback; + void* service_callback_user_data; +}; + +struct pkt_normalizer_pair { + struct pn_struct* packer; + struct pn_struct* unpacker; +}; + +struct pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer, int mtu); // 1 for packer, 0 for unpacker, mtu for fragment size calculation +void pkt_normalizer_deinit(struct pn_struct* pn); + +struct pkt_normalizer_pair* pkt_normalizer_pair_init(uasync_t* ua, int mtu); +void pkt_normalizer_pair_deinit(struct pkt_normalizer_pair* pair); + +/* Error handling */ +int pkt_normalizer_get_error_count(const struct pn_struct* pn); +void pkt_normalizer_reset_error_count(struct pn_struct* pn); + +/* Flush internal buffer (packer only) */ +void pkt_normalizer_flush(struct pn_struct* pn); + +int pkt_normalizer_send_service(struct pn_struct* pn, uint8_t type, const void* data, size_t len); +void pkt_normalizer_set_service_callback(struct pn_struct* pn, pkt_normalizer_service_callback_t callback, void* user_data); +void pkt_normalizer_reset_service_state(struct pn_struct* pn); +void pkt_normalizer_reset_state(struct pn_struct* pn); + +#endif // PKT_NORMALIZER_H diff --git a/!/routing.c b/!/routing.c new file mode 100755 index 00000000..80bf340b --- /dev/null +++ b/!/routing.c @@ -0,0 +1,372 @@ +/** + * @file enhanced_routing.c + * @brief Enhanced routing system with bandwidth support and route types + */ + +#include "routing.h" +#include "etcp_connections.h" +#include "../lib/debug_config.h" +#include +#include +#include +#include +#include + +#define INITIAL_ROUTE_CAPACITY 100 +#define ROUTE_EXPANSION_FACTOR 2 +#define MAX_SUBNET_VALIDATION_RANGES 50 + +// Parse subnet string +int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_length) { + if (!subnet_str || !network || !prefix_length) return -1; + char ip[64]; + int prefix; + if (sscanf(subnet_str, "%[^/]/%d", ip, &prefix) != 2) return -1; + if (prefix < 0 || prefix > 32) return -1; + struct in_addr addr; + if (inet_pton(AF_INET, ip, &addr) != 1) return -1; + *network = ntohl(addr.s_addr); + *prefix_length = (uint8_t)prefix; + return 0; +} + + + +static int compare_routes(const void *a, const void *b) { + const struct route_entry *route_a = (const struct route_entry *)a; + const struct route_entry *route_b = (const struct route_entry *)b; + + // First compare by prefix length (longest prefix first) + if (route_b->prefix_length != route_a->prefix_length) { + return route_b->prefix_length - route_a->prefix_length; + } + + // Then by network + if (route_a->network != route_b->network) { + return route_a->network < route_b->network ? -1 : 1; + } + + // Finally by metrics (best route first) + if (route_a->metrics.bandwidth_kbps != route_b->metrics.bandwidth_kbps) { + return route_b->metrics.bandwidth_kbps - route_a->metrics.bandwidth_kbps; + } + + if (route_a->metrics.latency_ms != route_b->metrics.latency_ms) { + return route_a->metrics.latency_ms - route_b->metrics.latency_ms; + } + + if (route_a->metrics.packet_loss_rate != route_b->metrics.packet_loss_rate) { + return route_a->metrics.packet_loss_rate - route_b->metrics.packet_loss_rate; + } + + return route_a->metrics.hop_count - route_b->metrics.hop_count; +} + +struct routing_table *routing_table_create(void) { + struct routing_table *table = calloc(1, sizeof(struct routing_table)); + if (!table) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "routing_table_create: failed to allocate memory for routing table"); + return NULL; + } + + table->capacity = INITIAL_ROUTE_CAPACITY; + table->entries = calloc(table->capacity, sizeof(struct route_entry)); + if (!table->entries) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "routing_table_create: failed to allocate memory for %d route entries", table->capacity); + free(table); + return NULL; + } + + // Initialize subnet validation arrays + table->dynamic_subnets = calloc(MAX_SUBNET_VALIDATION_RANGES, sizeof(uint32_t) * 2); + table->local_subnets = calloc(MAX_SUBNET_VALIDATION_RANGES, sizeof(uint32_t) * 2); + + if (!table->dynamic_subnets || !table->local_subnets) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "routing_table_create: failed to allocate memory for subnet validation arrays"); + free(table->entries); + free(table->dynamic_subnets); + free(table->local_subnets); + free(table); + return NULL; + } + + return table; +} + +void routing_table_destroy(struct routing_table *table) { + if (!table) return; + + // Free route entries + free(table->entries); + + // Free validation ranges + free(table->dynamic_subnets); + free(table->local_subnets); + + free(table); +} + +bool routing_table_insert(struct routing_table *table, const struct route_entry *entry) { + if (!table || !entry) return false; + + // Validate the route + if (!routing_validate_route(table, entry->network, entry->prefix_length, entry->type)) { + char ip_str[16]; + ip_to_string(entry->network, ip_str); + DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Ignoring invalid route: %s/%d (type: %s)", + ip_str, entry->prefix_length, route_type_to_string(entry->type)); + + return false; + } + + + // Check if we need to expand the table + if (table->count >= table->capacity) { + size_t new_capacity = table->capacity * ROUTE_EXPANSION_FACTOR; + struct route_entry *new_entries = realloc(table->entries, new_capacity * sizeof(struct route_entry)); + if (!new_entries) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "routing_table_insert: failed to expand routing table to %zu entries", new_capacity); + return false; + } + + table->entries = new_entries; + table->capacity = new_capacity; + } + + // Set timestamps and ensure route is active + struct route_entry new_entry = *entry; + new_entry.created_time = new_entry.last_update = (uint64_t)time(NULL) * 1000000; // Convert to microseconds + new_entry.last_used = 0; + + // Ensure route is active by default + if (!(new_entry.flags & ROUTE_FLAG_ACTIVE)) { + new_entry.flags |= ROUTE_FLAG_ACTIVE; + } + + + + // Insert and maintain sorted order + table->stats.routes_added++; + size_t insert_pos = table->count; + for (size_t i = 0; i < table->count; i++) { + if (compare_routes(&new_entry, &table->entries[i]) < 0) { + insert_pos = i; + break; + } + } + + return false; +} + +bool routing_table_lookup(struct routing_table *table, uint32_t dest_ip, struct route_entry *best_route) { + if (!table || !best_route) { + DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "routing_table_lookup: invalid parameters (table=%p, best_route=%p)", table, best_route); + return false; + } + + table->stats.lookup_count++; + + // Find longest prefix match + for (size_t i = 0; i < table->count; i++) { + const struct route_entry *entry = &table->entries[i]; + + // Check if route is valid and matches destination + if (entry->flags & ROUTE_FLAG_ACTIVE) { + uint32_t mask = (entry->prefix_length == 0) ? 0 : (0xFFFFFFFFU << (32 - entry->prefix_length)); + if ((dest_ip & mask) == (entry->network & mask)) { + *best_route = *entry; + best_route->last_update = (uint64_t)time(NULL) * 1000000; + + char ip_str[16]; + ip_to_string(dest_ip, ip_str); + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Found route for %s: %s/%d -> %s", + ip_str, ip_to_string(entry->network, ip_str), entry->prefix_length, + ip_to_string(entry->next_hop_ip, ip_str)); + + table->stats.hit_count++; + table->stats.routes_lookup_hits++; + return true; + } + } + } + + // No route found + char ip_str[16]; + ip_to_string(dest_ip, ip_str); + DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "routing_table_lookup: no route found for destination IP %s", ip_str); + + return false; +} + +bool routing_validate_route(struct routing_table *table, uint32_t network, uint8_t prefix_length, route_type_t route_type) { + if (!table) return false; + + uint32_t *validation_ranges = NULL; + size_t range_count = 0; + + switch (route_type) { + case ROUTE_TYPE_STATIC: + case ROUTE_TYPE_LEARNED: + return true; // No validation for static or learned routes + case ROUTE_TYPE_DYNAMIC: + validation_ranges = table->dynamic_subnets; + range_count = table->dynamic_subnet_count; + break; + case ROUTE_TYPE_LOCAL: + validation_ranges = table->local_subnets; + range_count = table->local_subnet_count; + break; + default: + table->stats.routes_lookup_misses++; + return false; + } + + if (range_count == 0) return true; // No validation ranges configured + + // Check if network falls within any validation range + for (size_t i = 0; i < range_count; i += 2) { + uint32_t range_network = validation_ranges[i]; + uint8_t range_prefix = (uint8_t)validation_ranges[i + 1]; + + uint32_t mask = (range_prefix == 0) ? 0 : (0xFFFFFFFFU << (32 - range_prefix)); + if ((network & mask) == (range_network & mask)) { + // Check if this route is more specific than or equal to the validation range + if (prefix_length >= range_prefix) { + return true; + } + } + } + + return false; +} + +bool enhanced_routing_add_subnet_range(struct routing_table *table, uint32_t network, uint8_t prefix_length, uint32_t **ranges, size_t *count) { + if (!table || !ranges || !count) return false; + + if (*count >= MAX_SUBNET_VALIDATION_RANGES - 2) return false; + + // Expand array if needed + if (*ranges == NULL) { + *ranges = calloc(MAX_SUBNET_VALIDATION_RANGES, sizeof(uint32_t)); + if (!*ranges) return false; + *count = 0; + } + + // Add network and prefix length as a pair + (*ranges)[*count] = network; + (*ranges)[*count + 1] = prefix_length; + *count += 2; + + char ip_str[16]; + ip_to_string(network, ip_str); + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Added subnet validation range: %s/%d", ip_str, prefix_length); + + return true; +} + + + +bool routing_add_dynamic_subnet(struct routing_table *table, uint32_t network, uint8_t prefix_length) { + return enhanced_routing_add_subnet_range(table, network, prefix_length, &table->dynamic_subnets, &table->dynamic_subnet_count); +} + +bool routing_add_local_subnet(struct routing_table *table, uint32_t network, uint8_t prefix_length) { + return enhanced_routing_add_subnet_range(table, network, prefix_length, &table->local_subnets, &table->local_subnet_count); +} + +bool routing_get_all_routes(const struct routing_table *table, uint32_t network, uint8_t prefix_length, + struct route_entry **routes, size_t *count) { + if (!table || !routes || !count) return false; + + *routes = NULL; + *count = 0; + + // Count matching routes + for (size_t i = 0; i < table->count; i++) { + if (table->entries[i].network == network && table->entries[i].prefix_length == prefix_length) { + (*count)++; + } + } + + if (*count == 0) return true; // No routes found, but not an error + + // Allocate result array + *routes = calloc(*count, sizeof(struct route_entry)); + if (!*routes) { + *count = 0; + return false; + } + + // Copy matching routes + size_t result_index = 0; + for (size_t i = 0; i < table->count; i++) { + if (table->entries[i].network == network && table->entries[i].prefix_length == prefix_length) { + (*routes)[result_index] = table->entries[i]; + + result_index++; + } + } + + return true; +} + +void routing_table_print(const struct routing_table *table) { + if (!table) return; + + printf("\n=== Routing Table ===\n"); + printf("Total routes: %zu\n", table->count); + printf("Dynamic subnets: %zu, Local subnets: %zu\n", + table->dynamic_subnet_count / 2, table->local_subnet_count / 2); + + if (table->count > 0) { + printf("\nRoutes:\n"); + for (size_t i = 0; i < table->count; i++) { + const struct route_entry *entry = &table->entries[i]; + char network_str[16], next_hop_str[16]; + + ip_to_string(entry->network, network_str); + ip_to_string(entry->next_hop_ip, next_hop_str); + + printf(" %zu: %s/%d -> %s [%s]\n", + i + 1, network_str, entry->prefix_length, next_hop_str, + route_type_to_string(entry->type)); + + printf(" Bandwidth: %uK, Loss: %.2f%%, Latency: %ums, Hops: %d\n", + entry->metrics.bandwidth_kbps, + entry->metrics.packet_loss_rate / 100.0, + entry->metrics.latency_ms, + entry->metrics.hop_count); + + + + printf(" Flags: %s%s%s%s\n", + (entry->flags & ROUTE_FLAG_ACTIVE) ? "ACTIVE " : "", + (entry->flags & ROUTE_FLAG_VALIDATED) ? "VALIDATED " : "", + (entry->flags & ROUTE_FLAG_ADVERTISED) ? "ADVERTISED " : "", + (entry->flags & ROUTE_FLAG_LEARNED) ? "LEARNED" : ""); + printf("\n"); + } + } else { + printf("\nNo routes configured.\n"); + } +} + +const char *route_type_to_string(route_type_t type) { + switch (type) { + case ROUTE_TYPE_STATIC: return "STATIC"; + case ROUTE_TYPE_DYNAMIC: return "DYNAMIC"; + case ROUTE_TYPE_LOCAL: return "LOCAL"; + case ROUTE_TYPE_LEARNED: return "LEARNED"; + default: return "UNKNOWN"; + } +} + +char *ip_to_string(uint32_t ip, char *buffer) { + if (!buffer) return NULL; + + struct in_addr addr; + addr.s_addr = htonl(ip); + strncpy(buffer, inet_ntoa(addr), 15); + buffer[15] = '\0'; + return buffer; +} \ No newline at end of file diff --git a/!/routing.h b/!/routing.h new file mode 100755 index 00000000..3302e7e7 --- /dev/null +++ b/!/routing.h @@ -0,0 +1,87 @@ +#ifndef ROUTING_H +#define ROUTING_H +#include +#include +#include + +// Forward declarations +struct ETCP_CONNECTIONS; + +// Типы маршрутов +typedef enum { + ROUTE_TYPE_STATIC = 0, + ROUTE_TYPE_DYNAMIC = 1, + ROUTE_TYPE_LOCAL = 2, + ROUTE_TYPE_LEARNED = 3 +} route_type_t; + +// Флаги маршрута +typedef enum { + ROUTE_FLAG_ACTIVE = (1 << 0), + ROUTE_FLAG_VALIDATED = (1 << 1), + ROUTE_FLAG_ADVERTISED = (1 << 2), + ROUTE_FLAG_LEARNED = (1 << 3) +} route_flags_t; + +// Расширенные метрики маршрута +struct route_metrics { + uint32_t bandwidth_kbps; + uint16_t packet_loss_rate; + uint16_t latency_ms; + uint8_t hop_count; + uint64_t last_updated; +}; + +// Расширенная запись маршрута +struct route_entry { + uint32_t network; + uint8_t prefix_length; + uint32_t next_hop_ip; + struct ETCP_SOCKET* next_hop; + route_type_t type; + uint8_t flags; + struct route_metrics metrics; + uint64_t created_time; + uint64_t last_update; + uint64_t last_used; +}; + +// Таблица маршрутизации +struct routing_table { + struct route_entry *entries; + size_t count; + size_t capacity; + uint32_t *dynamic_subnets; + size_t dynamic_subnet_count; + uint32_t *local_subnets; + size_t local_subnet_count; + struct { + uint64_t total_routes; + uint64_t static_routes; + uint64_t dynamic_routes; + uint64_t local_routes; + uint64_t learned_routes; + uint64_t routes_added; + uint64_t routes_deleted; + uint64_t lookup_count; + uint64_t hit_count; + uint64_t routes_lookup_hits; + uint64_t routes_lookup_misses; + uint64_t validation_failures; + } stats; +}; + +struct routing_table *routing_table_create(void); +void routing_table_destroy(struct routing_table *table); +bool routing_table_insert(struct routing_table *table, const struct route_entry *entry); +bool routing_table_delete(struct routing_table *table, uint32_t network, uint8_t prefix_length, uint32_t source_node_id); +bool routing_table_lookup(struct routing_table *table, uint32_t dest_ip, struct route_entry *best_route); +bool routing_validate_route(struct routing_table *table, uint32_t network, uint8_t prefix_length, route_type_t route_type); +bool routing_add_dynamic_subnet(struct routing_table *table, uint32_t network, uint8_t prefix_length); +bool routing_add_local_subnet(struct routing_table *table, uint32_t network, uint8_t prefix_length); +bool routing_get_all_routes(const struct routing_table *table, uint32_t network, uint8_t prefix_length, struct route_entry **routes, size_t *count); +void routing_table_print(const struct routing_table *table); +const char* route_type_to_string(route_type_t type); +char* ip_to_string(uint32_t ip, char *buffer); + +#endif // ROUTING_H diff --git a/!/routing.txt b/!/routing.txt new file mode 100755 index 00000000..bc05dd2b --- /dev/null +++ b/!/routing.txt @@ -0,0 +1,7 @@ +internal_routing.c/h: + +это модуль роутинга пакетов. +пакеты приходят: +1. из очередей ETCP (struct ETCP_CONN output_queue). + при добавлении подключения (из конфига)) в вызовы etcp_connection_create/close надо добавить +2. из tun интерфейса \ No newline at end of file diff --git a/!/secure_channel.c b/!/secure_channel.c new file mode 100755 index 00000000..4749a3a9 --- /dev/null +++ b/!/secure_channel.c @@ -0,0 +1,388 @@ +/* sc_lib.c - Secure Channel library implementation using TinyCrypt */ + +#include "secure_channel.h" +#include "../tinycrypt/lib/include/tinycrypt/ecc.h" +#include "../tinycrypt/lib/include/tinycrypt/ecc_dh.h" +#include "../tinycrypt/lib/include/tinycrypt/aes.h" +#include "../tinycrypt/lib/include/tinycrypt/ccm_mode.h" +#include "../tinycrypt/lib/include/tinycrypt/constants.h" +#include "../tinycrypt/lib/include/tinycrypt/ecc_platform_specific.h" +#include "../tinycrypt/lib/include/tinycrypt/sha256.h" +#include +#include +#include +#include +#include +#include + +// Simple debug macros +#define DEBUG_CATEGORY_CRYPTO 1 +#define DEBUG_ERROR(category, fmt, ...) fprintf(stderr, "ERROR: " fmt "\n", ##__VA_ARGS__) +#define DEBUG_INFO(category, fmt, ...) fprintf(stdout, "INFO: " fmt "\n", ##__VA_ARGS__) +#include +#include +#include "crc32.h" + +static const struct uECC_Curve_t *curve = NULL; +static uint8_t sc_urandom_seed[8] = {0}; +static int sc_urandom_initialized = 0; + +static void sc_init_random_seed(void) +{ + int fd = open("/dev/urandom", O_RDONLY); + if (fd >= 0) { + ssize_t ret = read(fd, sc_urandom_seed, 8); + close(fd); + if (ret == 8) { + sc_urandom_initialized = 1; + } + } +} + + +static int sc_rng(uint8_t *dest, unsigned size) +{ + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + return 0; + } + + ssize_t ret = read(fd, dest, size); + close(fd); + if (ret != size) { + return 0; + } + + /* Mix in PID and microtime for additional entropy */ + pid_t pid = getpid(); + struct timeval tv; + gettimeofday(&tv, NULL); + + for (unsigned i = 0; i < size; i++) { + dest[i] ^= ((pid >> (i % (sizeof(pid) * 8))) & 0xFF); + dest[i] ^= ((tv.tv_sec >> (i % (sizeof(tv.tv_sec) * 8))) & 0xFF); + dest[i] ^= ((tv.tv_usec >> (i % (sizeof(tv.tv_usec) * 8))) & 0xFF); + } + + return 1; +} + +static int sc_validate_key(const uint8_t *public_key) +{ + if (!curve) { + curve = uECC_secp256r1(); + } + int result = uECC_valid_public_key(public_key, curve); + DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_validate_key: uECC_valid_public_key returned %d", result); + return result; +} + +sc_status_t sc_generate_keypair(struct SC_MYKEYS *pk) +{ + if (!pk) { + return SC_ERR_INVALID_ARG; + } + + if (!curve) { + curve = uECC_secp256r1(); + } + + /* Set custom RNG function */ + uECC_set_rng(sc_rng); + + if (!uECC_make_key(pk->public_key, pk->private_key, curve)) { + return SC_ERR_CRYPTO; + } + return SC_OK; +} + +// Конвертация hex строки в бинарный формат +static int hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len) { + if (!hex_str || !binary || strlen(hex_str) != binary_len * 2) return -1; + + for (size_t i = 0; i < binary_len; i++) { + unsigned int byte; + if (sscanf(hex_str + i * 2, "%2x", &byte) != 1) return -1; + binary[i] = (uint8_t)byte; + } + return 0; +} + +sc_status_t sc_init_local_keys(struct SC_MYKEYS *mykeys, const char *public_key, const char *private_key) { + if (!mykeys || !public_key || !private_key) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: invalid arguments"); + return SC_ERR_INVALID_ARG; + } + + if (!curve) { + curve = uECC_secp256r1(); + } + + DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: public_key len=%zu, private_key len=%zu", + strlen(public_key), strlen(private_key)); + + /* Convert hex to binary first */ + if (hex_to_binary(public_key, mykeys->public_key, SC_PUBKEY_SIZE)) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: failed to convert public key from hex"); + return SC_ERR_INVALID_ARG; + } + if (hex_to_binary(private_key, mykeys->private_key, SC_PRIVKEY_SIZE)) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: failed to convert private key from hex"); + return SC_ERR_INVALID_ARG; + } + + /* Validate the converted binary public key */ + if (sc_validate_key(mykeys->public_key) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: public key validation failed"); + return SC_ERR_INVALID_ARG; + } + + DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: keys initialized successfully"); + return SC_OK; +} + +sc_status_t sc_init_ctx(sc_context_t *ctx, struct SC_MYKEYS *mykeys) { + + ctx->pk=mykeys; + ctx->initialized = 1; + ctx->peer_key_set = 0; + ctx->session_ready = 0; + ctx->tx_counter = 0; + ctx->rx_counter = 0; + + return SC_OK; +} + +sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_key_h, int mode) { + uint8_t shared_secret[SC_SHARED_SECRET_SIZE]; + uint8_t peer_public_key[SC_PUBKEY_SIZE]; + + if (mode) { + if (hex_to_binary(peer_public_key_h, peer_public_key, SC_PUBKEY_SIZE)) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid hex key format"); + return SC_ERR_INVALID_ARG; + } + } + else memcpy(peer_public_key, peer_public_key_h, SC_PUBKEY_SIZE); + + if (!ctx) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid ctx"); + return SC_ERR_INVALID_ARG; + } + + if (!ctx->initialized) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: ctx not initialized"); + return SC_ERR_NOT_INITIALIZED; + } + + if (!curve) { + curve = uECC_secp256r1(); + } + + /* Validate peer public key */ + if (sc_validate_key(peer_public_key) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid key"); + return SC_ERR_INVALID_ARG; + } + + /* Compute shared secret using ECDH */ + if (!ctx->pk) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: no private key"); + return SC_ERR_NOT_INITIALIZED; + } + if (!uECC_shared_secret(peer_public_key, ctx->pk->private_key, + shared_secret, curve)) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: shared secret error"); + return SC_ERR_CRYPTO; + } + + /* Derive session key from shared secret (simple copy for demo) */ + memcpy(ctx->session_key, shared_secret, SC_SESSION_KEY_SIZE); + + /* Store peer public key */ + memcpy(ctx->peer_public_key, peer_public_key, SC_PUBKEY_SIZE); + ctx->peer_key_set = 1; + + ctx->session_ready = 1; + + return SC_OK; +} + +static void sc_build_nonce(uint64_t counter, uint8_t *nonce_out) +{ + struct tc_sha256_state_struct sha_ctx; + uint8_t hash[32]; + struct timeval tv; + uint8_t data[8 + 8 + 4]; + + if (!sc_urandom_initialized) { + sc_init_random_seed(); + } + + gettimeofday(&tv, NULL); + + memcpy(data, sc_urandom_seed, 8); + data[8] = (counter >> 0) & 0xFF; + data[9] = (counter >> 8) & 0xFF; + data[10] = (counter >> 16) & 0xFF; + data[11] = (counter >> 24) & 0xFF; + data[12] = (counter >> 32) & 0xFF; + data[13] = (counter >> 40) & 0xFF; + data[14] = (counter >> 48) & 0xFF; + data[15] = (counter >> 56) & 0xFF; + data[16] = (tv.tv_sec >> 0) & 0xFF; + data[17] = (tv.tv_sec >> 8) & 0xFF; + data[18] = (tv.tv_sec >> 16) & 0xFF; + data[19] = (tv.tv_sec >> 24) & 0xFF; + + tc_sha256_init(&sha_ctx); + tc_sha256_update(&sha_ctx, data, 20); + tc_sha256_final(hash, &sha_ctx); + + memcpy(nonce_out, hash, SC_NONCE_SIZE); +} + +sc_status_t sc_encrypt(sc_context_t *ctx, + const uint8_t *plaintext, + size_t plaintext_len, + uint8_t *ciphertext, + size_t *ciphertext_len) +{ + uint8_t nonce[SC_NONCE_SIZE]; + struct tc_aes_key_sched_struct sched; + struct tc_ccm_mode_struct ccm_state; + size_t total_plaintext_len = plaintext_len + SC_CRC32_SIZE; + uint8_t plaintext_with_crc[total_plaintext_len]; + uint8_t combined_output[total_plaintext_len + SC_TAG_SIZE]; + + if (!ctx || !plaintext || !ciphertext || !ciphertext_len) { + return SC_ERR_INVALID_ARG; + } + + if (!ctx->session_ready) { + return SC_ERR_NOT_INITIALIZED; + } + + if (plaintext_len == 0) { + return SC_ERR_INVALID_ARG; + } + + /* Добавляем CRC32 к данным */ + memcpy(plaintext_with_crc, plaintext, plaintext_len); + uint32_t crc = crc32_calc(plaintext, plaintext_len); + plaintext_with_crc[plaintext_len] = (crc >> 0) & 0xFF; + plaintext_with_crc[plaintext_len + 1] = (crc >> 8) & 0xFF; + plaintext_with_crc[plaintext_len + 2] = (crc >> 16) & 0xFF; + plaintext_with_crc[plaintext_len + 3] = (crc >> 24) & 0xFF; + + /* Initialize AES key schedule */ + if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) { + return SC_ERR_CRYPTO; + } + + /* Build nonce from counter */ + sc_build_nonce(ctx->tx_counter, nonce); + + /* Configure CCM mode */ + if (tc_ccm_config(&ccm_state, &sched, nonce, SC_NONCE_SIZE, SC_TAG_SIZE) != TC_CRYPTO_SUCCESS) { + return SC_ERR_CRYPTO; + } + + /* Encrypt and generate tag */ + if (tc_ccm_generation_encryption(combined_output, sizeof(combined_output), + NULL, 0, /* no associated data */ + plaintext_with_crc, total_plaintext_len, + &ccm_state) != TC_CRYPTO_SUCCESS) { + return SC_ERR_CRYPTO; + } + + /* Copy ciphertext + tag to output buffer */ + memcpy(ciphertext, combined_output, total_plaintext_len + SC_TAG_SIZE); + *ciphertext_len = total_plaintext_len + SC_TAG_SIZE; + + ctx->tx_counter++; + + return SC_OK; +} + +sc_status_t sc_decrypt(sc_context_t *ctx, + const uint8_t *ciphertext, + size_t ciphertext_len, + uint8_t *plaintext, + size_t *plaintext_len) +{ + uint8_t nonce[SC_NONCE_SIZE]; + struct tc_aes_key_sched_struct sched; + struct tc_ccm_mode_struct ccm_state; + TCCcmMode_t c = &ccm_state; + size_t total_plaintext_len = ciphertext_len - SC_TAG_SIZE; + uint8_t plaintext_with_crc[total_plaintext_len]; + + if (!ctx || !ciphertext || !plaintext || !plaintext_len) { + return SC_ERR_INVALID_ARG; + } + + if (!ctx->session_ready) { + return SC_ERR_NOT_INITIALIZED; + } + + if (ciphertext_len < SC_TAG_SIZE + SC_CRC32_SIZE) { + return SC_ERR_INVALID_ARG; + } + + /* Initialize AES key schedule */ + if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) { + return SC_ERR_CRYPTO; + } + + /* Build nonce from counter */ + sc_build_nonce(ctx->rx_counter, nonce); + + /* Configure CCM mode */ + if (tc_ccm_config(c, &sched, nonce, SC_NONCE_SIZE, SC_TAG_SIZE) != TC_CRYPTO_SUCCESS) { + return SC_ERR_CRYPTO; + } + + /* Decrypt and verify tag */ + if (tc_ccm_decryption_verification(plaintext_with_crc, total_plaintext_len, + NULL, 0, /* no associated data */ + ciphertext, ciphertext_len, + c) != TC_CRYPTO_SUCCESS) { + return SC_ERR_AUTH_FAILED; + } + + /* Проверяем CRC32 */ + size_t data_len = total_plaintext_len - SC_CRC32_SIZE; + uint32_t expected_crc = crc32_calc(plaintext_with_crc, data_len); + uint32_t received_crc = (plaintext_with_crc[data_len] << 0) | + (plaintext_with_crc[data_len + 1] << 8) | + (plaintext_with_crc[data_len + 2] << 16) | + (plaintext_with_crc[data_len + 3] << 24); + + if (expected_crc != received_crc) { + return SC_ERR_CRC_FAILED; + } + + /* Копируем данные без CRC32 */ + memcpy(plaintext, plaintext_with_crc, data_len); + *plaintext_len = data_len; + + ctx->rx_counter++; + + return SC_OK; +} + +sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8_t *public_key) { + if (!private_key || !public_key) { + return SC_ERR_INVALID_ARG; + } + + if (!curve) { + curve = uECC_secp256r1(); + } + + if (!uECC_compute_public_key(private_key, public_key, curve)) { + return SC_ERR_CRYPTO; + } + return SC_OK; +} diff --git a/!/secure_channel.h b/!/secure_channel.h new file mode 100755 index 00000000..4b8ea632 --- /dev/null +++ b/!/secure_channel.h @@ -0,0 +1,68 @@ +// secure_channel.h +#ifndef SECURE_CHANNEL_H +#define SECURE_CHANNEL_H + +#include +#include + +// Размеры ключей +#define SC_PRIVKEY_SIZE 32 +#define SC_PUBKEY_SIZE 64 +#define SC_HASH_SIZE 32 +#define SC_NONCE_SIZE 13 // CCM requires exactly 13 bytes +#define SC_SHARED_SECRET_SIZE SC_HASH_SIZE +#define SC_SESSION_KEY_SIZE 16 +#define SC_TAG_SIZE 8 +#define SC_CRC32_SIZE 4 + +// Коды возврата +#define SC_OK 0 +#define SC_ERR_INVALID_ARG -1 +#define SC_ERR_CRYPTO -2 +#define SC_ERR_NOT_INITIALIZED -3 +#define SC_ERR_AUTH_FAILED -4 +#define SC_ERR_CRC_FAILED -5 + +#define SC_PEER_PUBKEY_BIN 0 +#define SC_PEER_PUBKEY_HEX 1 + +// Типы +typedef int sc_status_t; +typedef struct secure_channel sc_context_t; + +struct SC_MYKEYS { + /* Локальные ключи */ + uint8_t private_key[SC_PRIVKEY_SIZE]; + uint8_t public_key[SC_PUBKEY_SIZE]; + }; + +// Контекст защищенного канала +struct secure_channel { + struct SC_MYKEYS* pk; + /* Ключи пира (после key exchange) */ + uint8_t peer_public_key[SC_PUBKEY_SIZE]; + uint8_t session_key[SC_SESSION_KEY_SIZE]; /* Derived session key */ + + /* Nonces для отправки и приема */ + uint8_t send_nonce[SC_NONCE_SIZE]; + uint8_t recv_nonce[SC_NONCE_SIZE]; + + uint8_t initialized; + uint8_t peer_key_set; + uint8_t session_ready; + uint64_t tx_counter; + uint64_t rx_counter; +}; + +// Функции инициализации +sc_status_t sc_init_ctx(sc_context_t *ctx, struct SC_MYKEYS *mykeys); +sc_status_t sc_generate_keypair(struct SC_MYKEYS *keys); +sc_status_t sc_init_local_keys(struct SC_MYKEYS *mykeys, const char *public_key, const char *private_key); +sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_key, int mode);// mode: 0-bin 1-hex key format +sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8_t *public_key); + +// Криптографические операции +sc_status_t sc_encrypt(sc_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t *ciphertext_len); +sc_status_t sc_decrypt(sc_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t *plaintext_len); + +#endif // SECURE_CHANNEL_H diff --git a/!/test_udp_socket.c b/!/test_udp_socket.c new file mode 100755 index 00000000..2b538413 --- /dev/null +++ b/!/test_udp_socket.c @@ -0,0 +1,342 @@ +#include "test_udp_socket.h" +#include "../lib/debug_config.h" +#include +#include +#include +#include +#include +#include + +#ifndef DEBUG_CATEGORY_TEST +#define DEBUG_CATEGORY_TEST (1 << 30) +#endif + +// Global socket registry for fd-based operations +static struct test_udp_socket* g_socket_registry[1024] = {NULL}; +pthread_mutex_t g_registry_mutex = PTHREAD_MUTEX_INITIALIZER; // Make it non-static for external access +static int g_next_fd = 1000; // Start with high fd numbers to avoid conflicts + +// Helper function to allocate packet +static struct test_udp_packet* allocate_packet(const uint8_t* data, size_t len, + const struct sockaddr* addr, socklen_t addr_len) { + struct test_udp_packet* packet = calloc(1, sizeof(struct test_udp_packet)); + if (!packet) return NULL; + + packet->data = malloc(len); + if (!packet->data) { + free(packet); + return NULL; + } + + memcpy(packet->data, data, len); + packet->len = len; + + if (addr && addr_len > 0 && addr_len <= sizeof(struct sockaddr_storage)) { + memcpy(&packet->addr, addr, addr_len); + packet->addr_len = addr_len; + } else { + packet->addr_len = 0; + } + + return packet; +} + +// Helper function to free packet +static void free_packet(struct test_udp_packet* packet) { + if (!packet) return; + if (packet->data) free(packet->data); + free(packet); +} + +// Create virtual UDP socket +struct test_udp_socket* test_udp_socket_create(int family) { + if (family != AF_INET && family != AF_INET6) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Unsupported address family: %d", family); + return NULL; + } + + struct test_udp_socket* sock = calloc(1, sizeof(struct test_udp_socket)); + if (!sock) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to allocate UDP socket"); + return NULL; + } + + // Assign virtual file descriptor + pthread_mutex_lock(&g_registry_mutex); + sock->fd = g_next_fd++; + if (g_next_fd >= 2000) g_next_fd = 1000; // Wrap around + pthread_mutex_unlock(&g_registry_mutex); + + sock->family = family; + sock->bound = false; + sock->nonblocking = false; + sock->recv_queue_head = NULL; + sock->recv_queue_tail = NULL; + sock->recv_queue_size = 0; + + // Register socket + test_udp_socket_register(sock); + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Created virtual UDP socket: fd=%d, family=%d", sock->fd, family); + return sock; +} + +// Destroy virtual UDP socket +void test_udp_socket_destroy(struct test_udp_socket* sock) { + if (!sock) return; + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Destroying virtual UDP socket: fd=%d (stats: sent=%zu/%zu, recv=%zu/%zu, errors=%zu/%zu)", + sock->fd, sock->stats.packets_sent, sock->stats.bytes_sent, + sock->stats.packets_received, sock->stats.bytes_received, + sock->stats.send_errors, sock->stats.recv_errors); + + // Unregister socket + test_udp_socket_unregister(sock); + + // Free receive queue + struct test_udp_packet* packet = sock->recv_queue_head; + while (packet) { + struct test_udp_packet* next = packet->next; + free_packet(packet); + packet = next; + } + + free(sock); +} + +// Bind virtual socket +int test_udp_socket_bind(struct test_udp_socket* sock, const struct sockaddr* addr, socklen_t len) { + if (!sock || !addr || len == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Invalid parameters for socket bind"); + return -1; + } + + if (sock->bound) { + DEBUG_WARN(DEBUG_CATEGORY_TEST, "Socket already bound"); + return 0; + } + + if (len > sizeof(struct sockaddr_storage)) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Address too large: %d", len); + return -1; + } + + memcpy(&sock->local_addr, addr, len); + sock->bound = true; + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Bound virtual UDP socket fd=%d", sock->fd); + return 0; +} + +// Set socket options (limited implementation for testing) +int test_udp_socket_setsockopt(struct test_udp_socket* sock, int level, int optname, + const void *optval, socklen_t optlen) { + if (!sock) return -1; + + // For testing purposes, just log the option setting + DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "setsockopt on fd=%d: level=%d, optname=%d, optlen=%d", + sock->fd, level, optname, optlen); + + // Handle some common options + if (level == SOL_SOCKET) { + switch (optname) { + case SO_REUSEADDR: + case SO_REUSEPORT: + // Always allow for testing + return 0; + case SO_BROADCAST: + return 0; + default: + break; + } + } + + return 0; // Pretend success for most options +} + +// Get socket options +int test_udp_socket_getsockopt(struct test_udp_socket* sock, int level, int optname, + void *optval, socklen_t *optlen) { + if (!sock || !optval || !optlen) return -1; + + // For testing purposes, return reasonable defaults + if (level == SOL_SOCKET) { + switch (optname) { + case SO_ERROR: + *(int*)optval = 0; + *optlen = sizeof(int); + return 0; + default: + break; + } + } + + return -1; // Not implemented +} + +// Set non-blocking mode +int test_udp_socket_set_nonblocking(struct test_udp_socket* sock, bool nonblocking) { + if (!sock) return -1; + + sock->nonblocking = nonblocking; + DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "Set nonblocking on fd=%d: %s", sock->fd, nonblocking ? "true" : "false"); + return 0; +} + +// Send packet using virtual socket +ssize_t test_udp_socket_sendto(struct test_udp_socket* sock, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addr_len) { + if (!sock || !buf || len == 0) { + return -1; + } + + const uint8_t* data = (const uint8_t*)buf; + + // Call packet sent callback if available + if (sock->packet_sent) { + sock->packet_sent(data, len, dest_addr, addr_len, sock->context); + } + + sock->stats.packets_sent++; + sock->stats.bytes_sent += len; + + DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "Sent packet on fd=%d: %zu bytes to family=%d", + sock->fd, len, dest_addr ? dest_addr->sa_family : -1); + return len; +} + +// Receive packet from virtual socket +ssize_t test_udp_socket_recvfrom(struct test_udp_socket* sock, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addr_len) { + if (!sock || !buf || len == 0) { + return -1; + } + + uint8_t* buffer = (uint8_t*)buf; + + // Check if we have packets in queue + pthread_mutex_lock(&g_registry_mutex); + struct test_udp_packet* packet = sock->recv_queue_head; + if (!packet) { + pthread_mutex_unlock(&g_registry_mutex); + errno = EAGAIN; + return -1; + } + + // Remove packet from queue + sock->recv_queue_head = packet->next; + if (!sock->recv_queue_head) { + sock->recv_queue_tail = NULL; + } + sock->recv_queue_size--; + pthread_mutex_unlock(&g_registry_mutex); + + // Copy packet data + size_t copy_len = packet->len < len ? packet->len : len; + memcpy(buffer, packet->data, copy_len); + + // Copy source address if requested + if (src_addr && addr_len && packet->addr_len > 0) { + socklen_t copy_addr_len = packet->addr_len < *addr_len ? packet->addr_len : *addr_len; + memcpy(src_addr, &packet->addr, copy_addr_len); + *addr_len = copy_addr_len; + } + + sock->stats.packets_received++; + sock->stats.bytes_received += copy_len; + + DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "Received packet on fd=%d: %zu bytes", sock->fd, copy_len); + + free_packet(packet); + return copy_len; +} + +// Inject packet into receive queue (simulates incoming packet) +int test_udp_socket_inject(struct test_udp_socket* sock, + const uint8_t* data, size_t len, + const struct sockaddr* src_addr, socklen_t addr_len) { + if (!sock || !data || len == 0) { + return -1; + } + + if (sock->recv_queue_size >= TEST_UDP_MAX_QUEUE_SIZE) { + DEBUG_WARN(DEBUG_CATEGORY_TEST, "Receive queue full on fd=%d", sock->fd); + return -1; + } + + struct test_udp_packet* packet = allocate_packet(data, len, src_addr, addr_len); + if (!packet) { + return -1; + } + + pthread_mutex_lock(&g_registry_mutex); + if (sock->recv_queue_tail) { + sock->recv_queue_tail->next = packet; + } else { + sock->recv_queue_head = packet; + } + sock->recv_queue_tail = packet; + sock->recv_queue_size++; + pthread_mutex_unlock(&g_registry_mutex); + + DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "Injected packet into fd=%d: %zu bytes", sock->fd, len); + return 0; +} + +// Get virtual socket file descriptor +int test_udp_socket_get_fd(struct test_udp_socket* sock) { + return sock ? sock->fd : -1; +} + +// Get socket statistics +void test_udp_socket_get_stats(struct test_udp_socket* sock, size_t* packets_sent, + size_t* packets_received, size_t* bytes_sent, + size_t* bytes_received, size_t* send_errors, size_t* recv_errors) { + if (!sock) return; + + if (packets_sent) *packets_sent = sock->stats.packets_sent; + if (packets_received) *packets_received = sock->stats.packets_received; + if (bytes_sent) *bytes_sent = sock->stats.bytes_sent; + if (bytes_received) *bytes_received = sock->stats.bytes_received; + if (send_errors) *send_errors = sock->stats.send_errors; + if (recv_errors) *recv_errors = sock->stats.recv_errors; +} + +// Reset socket statistics +void test_udp_socket_reset_stats(struct test_udp_socket* sock) { + if (!sock) return; + + memset(&sock->stats, 0, sizeof(sock->stats)); + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Reset statistics for virtual UDP socket fd=%d", sock->fd); +} + +// Global virtual socket registry for fd-based operations +struct test_udp_socket* test_udp_socket_find_by_fd(int fd) { + pthread_mutex_lock(&g_registry_mutex); + struct test_udp_socket* sock = NULL; + if (fd >= 0 && fd < 1024) { + sock = g_socket_registry[fd]; + } + pthread_mutex_unlock(&g_registry_mutex); + return sock; +} + +void test_udp_socket_register(struct test_udp_socket* sock) { + if (!sock) return; + + pthread_mutex_lock(&g_registry_mutex); + if (sock->fd >= 0 && sock->fd < 1024) { + g_socket_registry[sock->fd] = sock; + } + pthread_mutex_unlock(&g_registry_mutex); +} + +void test_udp_socket_unregister(struct test_udp_socket* sock) { + if (!sock) return; + + pthread_mutex_lock(&g_registry_mutex); + if (sock->fd >= 0 && sock->fd < 1024) { + g_socket_registry[sock->fd] = NULL; + } + pthread_mutex_unlock(&g_registry_mutex); +} \ No newline at end of file diff --git a/!/test_udp_socket.h b/!/test_udp_socket.h new file mode 100755 index 00000000..e65436f0 --- /dev/null +++ b/!/test_udp_socket.h @@ -0,0 +1,101 @@ +#ifndef TEST_UDP_SOCKET_H +#define TEST_UDP_SOCKET_H + +#include +#include +#include +#include + +#define TEST_UDP_MAX_PACKET_SIZE 65536 +#define TEST_UDP_MAX_QUEUE_SIZE 1000 + +// Test UDP packet structure +struct test_udp_packet { + uint8_t* data; + size_t len; + struct sockaddr_storage addr; + socklen_t addr_len; + struct test_udp_packet* next; +}; + +// Virtual UDP socket for testing +struct test_udp_socket { + int fd; // Virtual file descriptor + int family; // Address family (AF_INET or AF_INET6) + struct sockaddr_storage local_addr; + bool bound; + bool nonblocking; + + // Receive queue (packets waiting to be read) + struct test_udp_packet* recv_queue_head; + struct test_udp_packet* recv_queue_tail; + size_t recv_queue_size; + + // Statistics + struct { + size_t packets_sent; + size_t packets_received; + size_t bytes_sent; + size_t bytes_received; + size_t send_errors; + size_t recv_errors; + } stats; + + // Callback for outgoing packets + void (*packet_sent)(const uint8_t* data, size_t len, + const struct sockaddr* dest_addr, socklen_t addr_len, + void* context); + + void* context; +}; + +// Create virtual UDP socket +struct test_udp_socket* test_udp_socket_create(int family); + +// Destroy virtual UDP socket +void test_udp_socket_destroy(struct test_udp_socket* sock); + +// Bind virtual socket +int test_udp_socket_bind(struct test_udp_socket* sock, const struct sockaddr* addr, socklen_t len); + +// Set socket options (limited implementation for testing) +int test_udp_socket_setsockopt(struct test_udp_socket* sock, int level, int optname, + const void *optval, socklen_t optlen); + +// Get socket options +int test_udp_socket_getsockopt(struct test_udp_socket* sock, int level, int optname, + void *optval, socklen_t *optlen); + +// Set non-blocking mode +int test_udp_socket_set_nonblocking(struct test_udp_socket* sock, bool nonblocking); + +// Send packet using virtual socket +ssize_t test_udp_socket_sendto(struct test_udp_socket* sock, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addr_len); + +// Receive packet from virtual socket +ssize_t test_udp_socket_recvfrom(struct test_udp_socket* sock, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addr_len); + +// Inject packet into receive queue (simulates incoming packet) +int test_udp_socket_inject(struct test_udp_socket* sock, + const uint8_t* data, size_t len, + const struct sockaddr* src_addr, socklen_t addr_len); + +// Get virtual socket file descriptor +int test_udp_socket_get_fd(struct test_udp_socket* sock); + +// Get socket statistics +void test_udp_socket_get_stats(struct test_udp_socket* sock, size_t* packets_sent, + size_t* packets_received, size_t* bytes_sent, + size_t* bytes_received, size_t* send_errors, size_t* recv_errors); + +// Reset socket statistics +void test_udp_socket_reset_stats(struct test_udp_socket* sock); + +// Global virtual socket registry for fd-based operations +struct test_udp_socket* test_udp_socket_find_by_fd(int fd); +void test_udp_socket_register(struct test_udp_socket* sock); +void test_udp_socket_unregister(struct test_udp_socket* sock); + +#endif // TEST_UDP_SOCKET_H \ No newline at end of file diff --git a/!/test_virtual_tun.c b/!/test_virtual_tun.c new file mode 100755 index 00000000..5194227c --- /dev/null +++ b/!/test_virtual_tun.c @@ -0,0 +1,190 @@ +#include "test_virtual_tun.h" +#include "../lib/debug_config.h" +#include +#include +#include +#include +#include +#include + +#ifndef DEBUG_CATEGORY_TEST +#define DEBUG_CATEGORY_TEST (1 << 30) +#endif + +// Create virtual TUN with bidirectional pipes +struct virtual_tun* virtual_tun_create(const char* ifname) { + if (!ifname) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "NULL interface name provided"); + return NULL; + } + + struct virtual_tun* vtun = calloc(1, sizeof(struct virtual_tun)); + if (!vtun) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to allocate virtual TUN"); + return NULL; + } + + // Copy interface name + strncpy(vtun->ifname, ifname, sizeof(vtun->ifname) - 1); + vtun->ifname[sizeof(vtun->ifname) - 1] = '\0'; + vtun->enabled = true; + + // Create pipes for bidirectional communication + // read_pipe: [0] = read end, [1] = write end (for packets from network) + // write_pipe: [0] = read end, [1] = write end (for packets to network) + if (pipe(vtun->read_pipe) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create read pipe: %s", strerror(errno)); + free(vtun); + return NULL; + } + + if (pipe(vtun->write_pipe) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create write pipe: %s", strerror(errno)); + close(vtun->read_pipe[0]); + close(vtun->read_pipe[1]); + free(vtun); + return NULL; + } + + // Set non-blocking mode on pipe ends + for (int i = 0; i < 2; i++) { + int flags = fcntl(vtun->read_pipe[i], F_GETFL, 0); + if (flags == -1 || fcntl(vtun->read_pipe[i], F_SETFL, flags | O_NONBLOCK) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_TEST, "Failed to set non-blocking on read pipe[%d]: %s", i, strerror(errno)); + } + + flags = fcntl(vtun->write_pipe[i], F_GETFL, 0); + if (flags == -1 || fcntl(vtun->write_pipe[i], F_SETFL, flags | O_NONBLOCK) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_TEST, "Failed to set non-blocking on write pipe[%d]: %s", i, strerror(errno)); + } + } + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Virtual TUN created: %s (read_fd=%d, write_fd=%d)", + vtun->ifname, vtun->read_pipe[0], vtun->write_pipe[1]); + + return vtun; +} + +// Cleanup virtual TUN +void virtual_tun_destroy(struct virtual_tun* vtun) { + if (!vtun) return; + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Destroying virtual TUN: %s (stats: sent=%zu packets/%zu bytes, recv=%zu packets/%zu bytes)", + vtun->ifname, vtun->stats.packets_sent, vtun->stats.bytes_sent, + vtun->stats.packets_received, vtun->stats.bytes_received); + + // Close all pipe file descriptors + if (vtun->read_pipe[0] >= 0) close(vtun->read_pipe[0]); + if (vtun->read_pipe[1] >= 0) close(vtun->read_pipe[1]); + if (vtun->write_pipe[0] >= 0) close(vtun->write_pipe[0]); + if (vtun->write_pipe[1] >= 0) close(vtun->write_pipe[1]); + + free(vtun); +} + +// Get file descriptor for reading (simulates TUN device read) +int virtual_tun_get_read_fd(struct virtual_tun* vtun) { + if (!vtun || !vtun->enabled) return -1; + return vtun->read_pipe[0]; // Read end of read pipe +} + +// Get file descriptor for writing (simulates TUN device write) +int virtual_tun_get_write_fd(struct virtual_tun* vtun) { + if (!vtun || !vtun->enabled) return -1; + return vtun->write_pipe[1]; // Write end of write pipe +} + +// Inject packet into virtual TUN (simulates packet from network) +int virtual_tun_inject_packet(struct virtual_tun* vtun, const uint8_t* packet, size_t len) { + if (!vtun || !vtun->enabled || !packet || len == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Invalid parameters for packet injection"); + return -1; + } + + if (len > VIRTUAL_TUN_MAX_PACKET_SIZE) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Packet too large: %zu > %d", len, VIRTUAL_TUN_MAX_PACKET_SIZE); + return -1; + } + + // Write packet to read pipe (simulates packet arriving from network) + ssize_t written = write(vtun->read_pipe[1], packet, len); + if (written < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to inject packet: %s", strerror(errno)); + } + return -1; + } + + vtun->stats.packets_received++; + vtun->stats.bytes_received += written; + + DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "Injected packet into %s: %zu bytes", vtun->ifname, written); + return 0; +} + +// Read packet from virtual TUN (captures packets going to network) +ssize_t virtual_tun_read_packet(struct virtual_tun* vtun, uint8_t* buffer, size_t max_len) { + if (!vtun || !vtun->enabled || !buffer || max_len == 0) { + return -1; + } + + // Read from write pipe (captures packets going to network) + ssize_t bytes_read = read(vtun->write_pipe[0], buffer, max_len); + if (bytes_read < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to read packet: %s", strerror(errno)); + } + return -1; + } + + if (bytes_read > 0) { + vtun->stats.packets_sent++; + vtun->stats.bytes_sent += bytes_read; + DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "Read packet from %s: %zu bytes", vtun->ifname, bytes_read); + } + + return bytes_read; +} + +// Write packet to virtual TUN (sends packet to network) +ssize_t virtual_tun_write_packet(struct virtual_tun* vtun, const uint8_t* packet, size_t len) { + if (!vtun || !vtun->enabled || !packet || len == 0) { + return -1; + } + + if (len > VIRTUAL_TUN_MAX_PACKET_SIZE) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Packet too large: %zu > %d", len, VIRTUAL_TUN_MAX_PACKET_SIZE); + return -1; + } + + // Write packet to write pipe (sends to network) + ssize_t written = write(vtun->write_pipe[1], packet, len); + if (written < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to write packet: %s", strerror(errno)); + } + return -1; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "Wrote packet to %s: %zu bytes", vtun->ifname, written); + return written; +} + +// Get virtual TUN statistics +void virtual_tun_get_stats(struct virtual_tun* vtun, size_t* packets_sent, size_t* packets_received, + size_t* bytes_sent, size_t* bytes_received) { + if (!vtun) return; + + if (packets_sent) *packets_sent = vtun->stats.packets_sent; + if (packets_received) *packets_received = vtun->stats.packets_received; + if (bytes_sent) *bytes_sent = vtun->stats.bytes_sent; + if (bytes_received) *bytes_received = vtun->stats.bytes_received; +} + +// Reset virtual TUN statistics +void virtual_tun_reset_stats(struct virtual_tun* vtun) { + if (!vtun) return; + + memset(&vtun->stats, 0, sizeof(vtun->stats)); + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Reset statistics for virtual TUN: %s", vtun->ifname); +} \ No newline at end of file diff --git a/!/test_virtual_tun.h b/!/test_virtual_tun.h new file mode 100755 index 00000000..8f41b5a9 --- /dev/null +++ b/!/test_virtual_tun.h @@ -0,0 +1,55 @@ +#ifndef TEST_VIRTUAL_TUN_H +#define TEST_VIRTUAL_TUN_H + +#include +#include +#include +#include + +#define VIRTUAL_TUN_MAX_PACKET_SIZE 65536 + +struct virtual_tun { + int read_pipe[2]; // [0] = read end, [1] = write end + int write_pipe[2]; // [0] = read end, [1] = write end + char ifname[32]; // Virtual interface name + bool enabled; + void* test_context; // For callbacks + + // Statistics + struct { + size_t packets_sent; + size_t packets_received; + size_t bytes_sent; + size_t bytes_received; + } stats; +}; + +// Create virtual TUN with bidirectional pipes +struct virtual_tun* virtual_tun_create(const char* ifname); + +// Cleanup virtual TUN +void virtual_tun_destroy(struct virtual_tun* vtun); + +// Get file descriptor for reading (simulates TUN device) +int virtual_tun_get_read_fd(struct virtual_tun* vtun); + +// Get file descriptor for writing (simulates TUN device) +int virtual_tun_get_write_fd(struct virtual_tun* vtun); + +// Inject packet into virtual TUN (simulates packet from network) +int virtual_tun_inject_packet(struct virtual_tun* vtun, const uint8_t* packet, size_t len); + +// Read packet from virtual TUN (captures packets going to network) +ssize_t virtual_tun_read_packet(struct virtual_tun* vtun, uint8_t* buffer, size_t max_len); + +// Write packet to virtual TUN (sends packet to network) +ssize_t virtual_tun_write_packet(struct virtual_tun* vtun, const uint8_t* packet, size_t len); + +// Get virtual TUN statistics +void virtual_tun_get_stats(struct virtual_tun* vtun, size_t* packets_sent, size_t* packets_received, + size_t* bytes_sent, size_t* bytes_received); + +// Reset virtual TUN statistics +void virtual_tun_reset_stats(struct virtual_tun* vtun); + +#endif // TEST_VIRTUAL_TUN_H \ No newline at end of file diff --git a/!/tun_if.c b/!/tun_if.c new file mode 100755 index 00000000..7e36fb58 --- /dev/null +++ b/!/tun_if.c @@ -0,0 +1,339 @@ +// tun_if.c - TUN interface management implementation +#define _POSIX_C_SOURCE 200809L +#include "tun_if.h" +#include "../lib/debug_config.h" +#include "routing.h" +#include "../lib/u_async.h" +#include "etcp_connections.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Create TUN device +static int create_tun_device(char *ifname, size_t ifname_len) { + struct ifreq ifr; + int fd; + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to open /dev/net/tun: %s", strerror(errno)); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + + if (ifname && ifname_len > 0 && ifname[0] != '\0') { + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + } + + if (ioctl(fd, TUNSETIFF, &ifr) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to configure TUN device: %s", strerror(errno)); + close(fd); + return -1; + } + + if (ifname && ifname_len > 0) { + strncpy(ifname, ifr.ifr_name, ifname_len - 1); + ifname[ifname_len - 1] = '\0'; + } + + return fd; +} + +// Run system command +static int run_command(const char *cmd) { + int ret = system(cmd); + if (ret == -1) { + perror("system"); + return -1; + } + if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Command failed: %s", cmd); + return -1; + } + return 0; +} + +// Parse IP address and mask +static int parse_ip_mask(const char *ip_addr, char *ip, size_t ip_len, int *mask) { + if (!ip_addr || !ip || !mask) return -1; + + char *slash = strchr(ip_addr, '/'); + if (!slash) { + // Default mask /32 + *mask = 32; + strncpy(ip, ip_addr, ip_len - 1); + ip[ip_len - 1] = '\0'; + } else { + size_t ip_size = slash - ip_addr; + if (ip_size >= ip_len) return -1; + strncpy(ip, ip_addr, ip_size); + ip[ip_size] = '\0'; + + char *endptr; + long mask_val = strtol(slash + 1, &endptr, 10); + if (*endptr != '\0' || mask_val < 0 || mask_val > 32) return -1; + *mask = (int)mask_val; + } + + return 0; +} + +int tun_create(struct tun_config *config) { + if (!config) { + errno = EINVAL; + return -1; + } + + // Create TUN device + int fd = create_tun_device(config->ifname, sizeof(config->ifname)); + if (fd < 0) { + return -1; + } + + config->fd = fd; + + // Configure IP if specified + if (config->ip_addr[0] != '\0') { + if (tun_set_ip(config->ifname, config->ip_addr) < 0) { + close(fd); + return -1; + } + } + + // Set MTU if specified + if (config->mtu > 0) { + if (tun_set_mtu(config->ifname, config->mtu) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_TUN, "Failed to set MTU %d on %s: %s", config->mtu, config->ifname, strerror(errno)); + } + } + + // Bring interface up + if (tun_set_up(config->ifname) < 0) { + close(fd); + return -1; + } + + config->is_up = 1; + return 0; +} + +int tun_set_ip(const char *ifname, const char *ip_addr) { + if (!ifname || !ip_addr) { + errno = EINVAL; + return -1; + } + + char ip[64]; + int mask; + if (parse_ip_mask(ip_addr, ip, sizeof(ip), &mask) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid IP address format: %s", ip_addr); + errno = EINVAL; + return -1; + } + + char cmd[256]; + snprintf(cmd, sizeof(cmd), "ip addr add %s dev %s", ip_addr, ifname); + if (run_command(cmd) < 0) { + return -1; + } + + return 0; +} + +int tun_set_up(const char *ifname) { + if (!ifname) { + errno = EINVAL; + return -1; + } + + char cmd[256]; + snprintf(cmd, sizeof(cmd), "ip link set %s up", ifname); + if (run_command(cmd) < 0) { + return -1; + } + + return 0; +} + +int tun_set_mtu(const char *ifname, int mtu) { + if (!ifname || mtu <= 0) { + errno = EINVAL; + return -1; + } + + char cmd[256]; + snprintf(cmd, sizeof(cmd), "ip link set %s mtu %d", ifname, mtu); + if (run_command(cmd) < 0) { + return -1; + } + + return 0; +} + +ssize_t tun_read(int fd, uint8_t *buffer, size_t size) { + if (fd < 0 || !buffer || size == 0) { + errno = EINVAL; + return -1; + } + + ssize_t nread = read(fd, buffer, size); + if (nread < 0) { + perror("tun_read"); + } + + return nread; +} + +ssize_t tun_write(int fd, const uint8_t *buffer, size_t size) { + if (fd < 0 || !buffer || size == 0) { + errno = EINVAL; + return -1; + } + + ssize_t nwritten = write(fd, buffer, size); + if (nwritten < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to write to TUN device fd=%d: %s", fd, strerror(errno)); + } + + return nwritten; +} + +void tun_close(struct tun_config *config) { + if (!config) return; + + if (config->fd >= 0) { + close(config->fd); + config->fd = -1; + } + + config->is_up = 0; +} + +int tun_get_config(const char *ifname, struct tun_config *config) { + if (!ifname || !config) { + errno = EINVAL; + return -1; + } + + // TODO: Implement reading current interface configuration + // This would require parsing ip addr show output + + memset(config, 0, sizeof(*config)); + strncpy(config->ifname, ifname, sizeof(config->ifname) - 1); + config->fd = -1; + + return 0; +} + +// Extract destination IPv4 address from packet +static uint32_t tun_get_dest_ip(const uint8_t *packet, size_t len) { + if (len < 20) return 0; // Minimum IPv4 header size + // Check IP version (first nibble) + uint8_t version = (packet[0] >> 4) & 0x0F; + if (version != 4) return 0; + // Destination IP is at offset 16 + uint32_t dest_ip; + memcpy(&dest_ip, packet + 16, 4); + return dest_ip; +} + +// Extract destination IPv4 address from packet +static uint32_t get_dest_ip(const uint8_t *packet, size_t len) { + if (len < 20) return 0; // Minimum IPv4 header size + // Check IP version (first nibble) + uint8_t version = (packet[0] >> 4) & 0x0F; + if (version != 4) return 0; + // Destination IP is at offset 16 + uint32_t dest_ip; + memcpy(&dest_ip, packet + 16, 4); + return dest_ip; +} + + +// Callback for TUN device read events +void tun_read_callback(int fd, void* user_arg) { + struct UTUN_INSTANCE *instance = (struct UTUN_INSTANCE*)user_arg; + uint8_t buffer[MAX_PACKET_SIZE]; + + // Read from TUN device + ssize_t nread = tun_read(fd, buffer, sizeof(buffer)); + if (nread < 0) { + if (errno == EINTR) return; + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to read from TUN device %s: %s", instance->tun.ifname, strerror(errno)); + return; + } + + if (nread > 0) { + // Route packet based on destination IP + uint32_t dest_ip = get_dest_ip(buffer, nread); + struct route_entry route; + + if (routing_table_lookup(instance->routing_table, dest_ip, &route)) { + // Found route, send to next hop connection + if (route.next_hop) { + struct sockaddr_in dest_addr; + memset(&dest_addr, 0, sizeof(dest_addr)); + dest_addr.sin_family = AF_INET; + dest_addr.sin_addr.s_addr = route.next_hop_ip; +// todo: отправлять в модуль роутинга все входящие пакеты. когда этот модуль будет. модуль роутинга это не роутинг таблица (routing.h) +// if (etcp_connections_send(route.next_hop, buffer, nread, (struct sockaddr*)&dest_addr, sizeof(dest_addr)) < 0) { +// DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to send packet via route"); +// } + } else { + // Local route - no forwarding needed + DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "Local packet, no forwarding"); + } + } else { + // No route found, drop packet + char ip_str[16]; + ip_to_string(dest_ip, ip_str); + DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "No route for destination IP %s", ip_str); + } + } +} + +// Register sockets with uasync +int utun_instance_register_sockets(struct UTUN_INSTANCE *instance) { + if (!instance || !instance->ua || instance->tun.fd < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Invalid instance or TUN fd"); + return -1; + } + + // Register TUN file descriptor + instance->tun_socket_id = uasync_add_socket(instance->ua, instance->tun.fd, + tun_read_callback, NULL, NULL, instance); + if (!instance->tun_socket_id) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to register TUN socket"); + return -1; + } + + DEBUG_INFO(DEBUG_CATEGORY_TUN, "Registered TUN socket (fd=%d)", instance->tun.fd); + return 0; +} + +// Unregister sockets +void utun_instance_unregister_sockets(struct UTUN_INSTANCE *instance) { + if (!instance || !instance->ua) return; + + if (instance->tun_socket_id) { + uasync_remove_socket(instance->ua, instance->tun_socket_id); + instance->tun_socket_id = NULL; + DEBUG_INFO(DEBUG_CATEGORY_TUN, "Unregistered TUN socket: %s", instance->tun.ifname); + } +} + diff --git a/!/tun_if.h b/!/tun_if.h new file mode 100755 index 00000000..37f8954b --- /dev/null +++ b/!/tun_if.h @@ -0,0 +1,107 @@ +// tun_if.h - TUN interface management for utun +#ifndef TUN_IF_H +#define TUN_IF_H + +#include +#include +#include + +// Forward declarations +struct UTUN_INSTANCE; + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_PACKET_SIZE 1500 + +// TUN interface configuration +struct tun_config { + char ifname[16]; // Interface name (e.g., "tun12") + char ip_addr[64]; // IP address with mask (e.g., "10.0.0.1/24") + int mtu; // MTU size + int fd; // File descriptor + uint8_t is_up; // 1 if interface is up + // Statistics + uint64_t bytes_read; // Bytes read from TUN + uint64_t bytes_written; // Bytes written to TUN + uint32_t packets_read; // Packets read from TUN + uint32_t packets_written; // Packets written to TUN + uint32_t read_errors; // Read errors + uint32_t write_errors; // Write errors +}; + +/** + * @brief Create and configure TUN interface + * @param config TUN configuration (ifname can be empty for auto) + * @return 0 on success, -1 on error + */ +int tun_create(struct tun_config *config); + +/** + * @brief Configure IP address on TUN interface + * @param ifname Interface name + * @param ip_addr IP address with mask (e.g., "10.0.0.1/24") + * @return 0 on success, -1 on error + */ +int tun_set_ip(const char *ifname, const char *ip_addr); + +/** + * @brief Bring TUN interface up + * @param ifname Interface name + * @return 0 on success, -1 on error + */ +int tun_set_up(const char *ifname); + +/** + * @brief Set MTU on TUN interface + * @param ifname Interface name + * @param mtu MTU value + * @return 0 on success, -1 on error + */ +int tun_set_mtu(const char *ifname, int mtu); + +/** + * @brief Read packet from TUN interface + * @param fd TUN file descriptor + * @param buffer Buffer to store packet + * @param size Buffer size + * @return Number of bytes read, -1 on error + */ +ssize_t tun_read(int fd, uint8_t *buffer, size_t size); + +/** + * @brief Write packet to TUN interface + * @param fd TUN file descriptor + * @param buffer Packet data + * @param size Packet size + * @return Number of bytes written, -1 on error + */ +ssize_t tun_write(int fd, const uint8_t *buffer, size_t size); + +/** + * @brief Close TUN interface + * @param config TUN configuration + */ +void tun_close(struct tun_config *config); + +/** + * @brief Get current TUN configuration + * @param ifname Interface name + * @param config Output configuration + * @return 0 on success, -1 on error + */ +int tun_get_config(const char *ifname, struct tun_config *config); + +int utun_instance_register_sockets(struct UTUN_INSTANCE *instance); +void utun_instance_unregister_sockets(struct UTUN_INSTANCE *instance); + +// TUN callback function +void tun_read_callback(int fd, void* user_arg); + + +#ifdef __cplusplus +} +#endif + +#endif /* TUN_IF_H */ \ No newline at end of file diff --git a/!/utun.c b/!/utun.c new file mode 100755 index 00000000..13aa09c7 --- /dev/null +++ b/!/utun.c @@ -0,0 +1,321 @@ +// utun.c - Main application for utun VPN tunnel +#define _DEFAULT_SOURCE +#define _POSIX_C_SOURCE 200809L +#include "config_parser.h" +#include "etcp_connections.h" +#include "tun_if.h" +#include "secure_channel.h" +#include "routing.h" +#include "utun_instance.h" +#include "u_async.h" +#include "debug_config.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +/* + +Архитектура: +main -> init utun instnaces -> mainloop() + +utun instances: можно stop() - он ывзывает закрытие всех etcp (etcp_close) которые вызовут закрытие подключений), вызывает закрытие всех etcp сокетов + + + +*/ + + + +// Global wakeup pipe write fd for signal handler +static int g_wakeup_pipe_write_fd = -1; + +#define DEFAULT_CONFIG "utun.conf" +#define DEFAULT_PIDFILE "/var/run/utun.pid" + +// Command line arguments +typedef struct { + char *config_file; + char *pid_file; + char *log_file; + char *debug_config; + int foreground; + int help; +} cmd_args_t; + +// Global state + + + + + +// Parse subnet string +static int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_length) { + if (!subnet_str || !network || !prefix_length) return -1; + char ip[64]; + int prefix; + if (sscanf(subnet_str, "%[^/]/%d", ip, &prefix) != 2) return -1; + if (prefix < 0 || prefix > 32) return -1; + struct in_addr addr; + if (inet_pton(AF_INET, ip, &addr) != 1) return -1; + *network = addr.s_addr; + *prefix_length = (uint8_t)prefix; + return 0; +} + +// Extract destination IPv4 address from packet +static uint32_t get_dest_ip(const uint8_t *packet, size_t len) { + if (len < 20) return 0; // Minimum IPv4 header size + // Check IP version (first nibble) + uint8_t version = (packet[0] >> 4) & 0x0F; + if (version != 4) return 0; + // Destination IP is at offset 16 + uint32_t dest_ip; + memcpy(&dest_ip, packet + 16, 4); + return dest_ip; +} + + +// Parse command line arguments +static void parse_args(int argc, char *argv[], cmd_args_t *args) { + memset(args, 0, sizeof(*args)); + args->config_file = DEFAULT_CONFIG; + args->pid_file = DEFAULT_PIDFILE; + args->log_file = NULL; + args->debug_config = NULL; + args->foreground = 0; + args->help = 0; + + static struct option long_options[] = { + {"config", required_argument, 0, 'c'}, + {"pidfile", required_argument, 0, 'p'}, + {"log", required_argument, 0, 'l'}, + {"debug", required_argument, 0, 'd'}, + {"foreground", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int opt; + int option_index = 0; + + while ((opt = getopt_long(argc, argv, "c:p:l:d:fh", + long_options, &option_index)) != -1) { + switch (opt) { + case 'c': + args->config_file = optarg; + break; + case 'p': + args->pid_file = optarg; + break; + case 'l': + args->log_file = optarg; + break; + case 'f': + args->foreground = 1; + break; + case 'd': + args->debug_config = optarg; + break; + case 'h': + args->help = 1; + break; + default: + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown option: %c", opt); + exit(1); + } + } +} + +// Print usage +static void print_usage(const char *progname) { + printf("Usage: %s [OPTIONS]\n", progname); + printf("Secure VPN tunnel over UDP with TUN interface\n\n"); + printf("Options:\n"); + printf(" -c, --config FILE Configuration file (default: %s)\n", DEFAULT_CONFIG); + printf(" -p, --pidfile FILE PID file (default: %s)\n", DEFAULT_PIDFILE); + printf(" -l, --log FILE Log file (default: stderr)\n"); + printf(" -d, --debug CONFIG Debug configuration (e.g., \"etcp:debug,routing:info\")\n"); + printf(" -f, --foreground Run in foreground (don't daemonize)\n"); + printf(" -h, --help Show this help\n"); + printf("\nExamples:\n"); + printf(" %s -c myconfig.conf\n", progname); + printf(" %s --config server.conf --pidfile /var/run/utun.pid\n", progname); +} + +// Write PID file +static int write_pidfile(const char *pidfile) { + if (!pidfile) return -1; + + FILE *fp = fopen(pidfile, "w"); + if (!fp) { + perror("fopen pidfile"); + return -1; + } + + fprintf(fp, "%d\n", getpid()); + fclose(fp); + return 0; +} + +// Remove PID file +static void remove_pidfile(const char *pidfile) { + if (pidfile) { + unlink(pidfile); + } +} + +// Daemonize process +static int daemonize(void) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + + if (pid > 0) { + // Parent exits + exit(0); + } + + // Child becomes session leader + if (setsid() < 0) { + perror("setsid"); + return -1; + } + + // Close standard file descriptors + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + // Redirect to /dev/null + int fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) close(fd); + } + + return 0; +} + +// Open log file +static FILE* open_logfile(const char *logfile) { + if (!logfile) return stderr; + + FILE *fp = fopen(logfile, "a"); + if (!fp) { + perror("fopen logfile"); + return stderr; + } + + // Set line buffering + setlinebuf(fp); + return fp; +} + + +// Main function + +static volatile sig_atomic_t g_running = 1; +struct UASYNC* main_ua = NULL; + +static void signal_handler(int sig) { + (void)sig; + g_running = 0; + if (main_ua) { + uasync_wakeup(main_ua); + } +} + +int main(int argc, char *argv[]) { + cmd_args_t args; + parse_args(argc, argv, &args); + + if (args.help) { + print_usage(argv[0]); + return 0; + } + + // Initialize global debug system early if configured from command line + if (args.debug_config) { + debug_config_init(); + if (debug_parse_config(args.debug_config) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug configuration: %s", args.debug_config); + return 1; + } + } + + // Create uasync instance + struct UASYNC* ua = uasync_create(); + main_ua=ua; + if (!ua) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to create uasync instance"); + return 1; + } + + // Create and initialize instance + struct UTUN_INSTANCE *instance = utun_instance_create(ua, args.config_file, args.log_file); + if (!instance) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to create UTUN instance"); + return 1; + } + + // Print config for debugging + if (args.foreground) { + print_config(instance->config); + } + + // Initialize all components (TUN, routing, connections) + if (utun_instance_init(instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to initialize instance"); + utun_instance_destroy(instance); + return 1; + } + + // Register sockets with uasync + if (utun_instance_register_sockets(instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to register sockets"); + utun_instance_destroy(instance); + return 1; + } + + // Setup signal handlers + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGHUP, signal_handler); + + // Daemonize if not in foreground + if (!args.foreground) { + if (daemonize() < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to daemonize"); + utun_instance_destroy(instance); + return 1; + } + } + + // Write PID file + if (write_pidfile(args.pid_file) < 0) { + utun_instance_destroy(instance); + return 1; + } + + while (instance->running) uasync_poll(ua, 100); + + // Cleanup + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Shutdown"); + + utun_instance_unregister_sockets(instance); + utun_instance_destroy(instance); + remove_pidfile(args.pid_file); + + return 0; +} diff --git a/!/utun.conf b/!/utun.conf new file mode 100755 index 00000000..7c60c68b --- /dev/null +++ b/!/utun.conf @@ -0,0 +1,42 @@ +[global] +tun_ip=10.0.0.1 +mtu=1500 # MTU for all connections (0 = use default 1500) +control_ip=127.0.0.1 +control_port=12345 +net_debug=0 + +my_node_id=61be9d4cd3c60c2d +my_private_key=1313912e5d34768983b0e06530a48c77816d228a5b5605e1ab3dc443d107a3dc +my_public_key= + + +[routing] +allowed_subnet=10.0.0.0/24 +allowed_subnet=10.22.0.0/16 +allowed_subnet=10.23.0.0/16 +my_subnet=10.23.5.0/24 +my_subnet=10.23.6.0/24 + +# мои адреса и каналы +[server: lo0_test] +addr=127.0.0.1:1330 +#so_mark=100 +#netif=eth0 +type=nat # public / nat / private + +[server: lan1] +addr=192.168.29.117:1333 +so_mark=100 +netif=eth0 +type=public # public / nat / private + +[client: client_test1] + +# линки +link=lo0_test:192.168.0.20:1234 +#link=wired1_fast:1.2.3.4:1234 +link=lan1:192.168.0.20:1234 +#link=wireless_bkp:1.2.3.4:1234 + +keepalive=1 +peer_public_key=deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbee \ No newline at end of file diff --git a/!/utun_instance.c b/!/utun_instance.c new file mode 100755 index 00000000..fc29a12f --- /dev/null +++ b/!/utun_instance.c @@ -0,0 +1,388 @@ +// utun_instance.c - Root instance implementation +#include "utun_instance.h" +#include "config_parser.h" +#include "config_updater.h" +#include "tun_if.h" +#include "routing.h" +#include "etcp_connections.h" +#include "etcp.h" +#include "utun_test_hooks.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include +#include +#include +#include +#include +#include + + + +// Forward declarations +static uint32_t get_dest_ip(const uint8_t *packet, size_t len); + + + +// Global instance for signal handlers +static struct UTUN_INSTANCE *g_instance = NULL; + +// Create and initialize root instance with extended flags +struct UTUN_INSTANCE* utun_instance_create_ex(struct UASYNC* ua, const char *config_file, const char *log_file, uint32_t flags) { + struct UTUN_INSTANCE *instance = calloc(1, sizeof(struct UTUN_INSTANCE)); + if (!instance) return NULL; + + // Initialize basic fields + instance->running = 0; + instance->log_fp = NULL; + instance->ua = ua; + + // Note: Global debug system is initialized from command line arguments if provided + // If not initialized via command line, instance-specific logging can be set up here + // The first initialization wins - either global (from main) or instance-specific + + // Ensure keys and node_id exist in config + if (config_ensure_keys_and_node_id(config_file) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to ensure keys and node_id in config: %s", config_file); + free(instance); + return NULL; + } + + // Load configuration + instance->config = parse_config(config_file); + if (!instance->config) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to load config from %s", config_file); + free(instance); + return NULL; + } + + // Open log file only if not using global debug system output + if (log_file && !g_debug_config.output_file) { + instance->log_fp = fopen(log_file, "a"); + if (!instance->log_fp) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to open log file %s: %s", log_file, strerror(errno)); + } + } + // Set node_id from config + instance->node_id = instance->config->global.my_node_id; + + // Set my keys + if (sc_init_local_keys(&instance->my_keys, instance->config->global.my_public_key_hex, instance->config->global.my_private_key_hex)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to initialize local keys"); + } + + + instance->pkt_pool=memory_pool_init(PACKET_DATA_SIZE+100); + + // Conditional TUN initialization based on flags + if (!(flags & UTUN_CREATE_NO_TUN)) { + int tun_result; + + // Use test hook if available and in test mode + if ((flags & UTUN_CREATE_TEST_MODE) && g_utun_test_hooks && g_utun_test_hooks->tun_create_override) { + tun_result = g_utun_test_hooks->tun_create_override(&instance->tun); + DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN creation using test hook: result=%d", tun_result); + } else { + tun_result = tun_create(&instance->tun); + } + + if (tun_result < 0) { + if (flags & UTUN_CREATE_ALLOW_TUN_FAILURE) { + DEBUG_WARN(DEBUG_CATEGORY_TUN, "TUN creation failed, continuing without TUN (ALLOW_TUN_FAILURE flag)"); + instance->tun.fd = -1; // Mark as disabled + } else { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TUN device"); + free(instance); + return NULL; + } + } else { + // TUN created successfully, configure it + // Configure TUN device IP (BSD: use config IP as-is, Linux/Win: IP/32) + char tun_ip_str[64]; + char ip_buffer[64]; + + // Convert struct IP to string using inet_ntop + if (instance->config->global.tun_ip.family == AF_INET) { + inet_ntop(AF_INET, &instance->config->global.tun_ip.addr.v4, ip_buffer, sizeof(ip_buffer)); + } else { + inet_ntop(AF_INET6, &instance->config->global.tun_ip.addr.v6, ip_buffer, sizeof(ip_buffer)); + } + + #ifdef __linux__ + snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer); + #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + // BSD systems: use config IP as-is, peer IP will be 192.0.2.1 + snprintf(tun_ip_str, sizeof(tun_ip_str), "%s", ip_buffer); + #else + snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer); // Default to /32 + #endif + + if (tun_set_ip(instance->tun.ifname, tun_ip_str) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN IP: %s", tun_ip_str); + tun_close(&instance->tun); + free(instance); + return NULL; + } + + // Set MTU (default 1500) + int mtu = instance->config->global.mtu > 0 ? instance->config->global.mtu : 1500; + if (tun_set_mtu(instance->tun.ifname, mtu) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN MTU: %d", mtu); + tun_close(&instance->tun); + free(instance); + return NULL; + } + + if (tun_set_up(instance->tun.ifname) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to bring up TUN interface"); + tun_close(&instance->tun); + free(instance); + return NULL; + } + + DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s with IP %s", + instance->tun.ifname, tun_ip_str); + } + } else { + DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization skipped (NO_TUN flag)"); + instance->tun.fd = -1; // Explicitly disabled + } + + // Create routing table + instance->routing_table = routing_table_create(); + if (!instance->routing_table) { + DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "Failed to create routing table"); + tun_close(&instance->tun); + free(instance); + return NULL; + } + // Initialize connections from configuration - moved to utun_instance_init + // to avoid double initialization + /* + if (init_connections(instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize connections"); + // Cleanup will be handled by utun_instance_destroy + return NULL; + } + */ + + return instance; +} + +// Destroy instance and cleanup resources +void utun_instance_destroy(struct UTUN_INSTANCE *instance) { + if (!instance) return; + + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Starting cleanup for instance %p", instance); + + // Диагностика ресурсов ДО cleanup + utun_instance_diagnose_leaks(instance, "BEFORE_CLEANUP"); + + // Stop running if not already + instance->running = 0; + + // Unregister all sockets from uasync BEFORE destroying ETCP components + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Unregistering sockets from uasync"); + utun_instance_unregister_sockets(instance); + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Socket unregistration complete"); + + + // Cleanup ETCP sockets and connections FIRST (before destroying uasync) + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Cleaning up ETCP sockets and connections"); + struct ETCP_SOCKET* sock = instance->etcp_sockets; + while (sock) { + struct ETCP_SOCKET* next = sock->next; + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Removing socket %p, fd=%d", sock, sock->fd); + etcp_socket_remove(sock); // Полный cleanup сокета + sock = next; + } + instance->etcp_sockets = NULL; + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] ETCP sockets cleanup complete"); + + struct ETCP_CONN* conn = instance->connections; + while (conn) { + struct ETCP_CONN* next = conn->next; + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Closing connection %p", conn); + etcp_connection_close(conn); // Закрыть соединение + conn = next; + } + instance->connections = NULL; + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] ETCP connections cleanup complete"); + + // Cleanup other components + if (instance->routing_table) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Destroying routing table"); + routing_table_destroy(instance->routing_table); + instance->routing_table = NULL; + } + + // Cleanup TUN + if (instance->tun.fd >= 0) { + DEBUG_INFO(DEBUG_CATEGORY_TUN, "Closing TUN interface: %s", instance->tun.ifname); + tun_close(&instance->tun); + } + + // Cleanup config + if (instance->config) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing configuration"); + free_config(instance->config); + instance->config = NULL; + } + + // Close log file + if (instance->log_fp) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Closing log file"); + fclose(instance->log_fp); + instance->log_fp = NULL; + } + + // Cleanup packet pool (ensure no leak if stop wasn't called) + if (instance->pkt_pool) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Destroying packet pool"); + memory_pool_destroy(instance->pkt_pool); + instance->pkt_pool = NULL; + } + + // FINALLY destroy uasync (after all resources are cleaned up) + if (instance->ua) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Destroying uasync instance"); + uasync_destroy(instance->ua); + instance->ua = NULL; + } + + // Clear global instance + if (g_instance == instance) { + g_instance = NULL; + } + + // Free the instance memory + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing instance memory"); + free(instance); + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Instance destroyed completely"); +} + +// Stop instance +void utun_instance_stop(struct UTUN_INSTANCE *instance) { + if (!instance) return; + instance->running = 0; + // Wakeup main loop using built-in uasync wakeup + if (instance->ua) { + memory_pool_destroy(instance->pkt_pool); + uasync_wakeup(instance->ua); + } + + +} + +int utun_instance_init(struct UTUN_INSTANCE *instance) { + if (!instance) return -1; + + // Register TUN socket with uasync + if (instance->tun.fd >= 0) { + instance->tun_socket_id = uasync_add_socket(instance->ua, instance->tun.fd, + tun_read_callback, NULL, NULL, instance); + if (!instance->tun_socket_id) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to register TUN socket with uasync"); + return -1; + } + DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface registered: %s (fd=%d)", + instance->tun.ifname, instance->tun.fd); + } + + // Initialize connections + if (init_connections(instance) < 0) { + return -1; + } + + return 0; +} + +// Диагностическая функция для анализа утечек +void utun_instance_diagnose_leaks(struct UTUN_INSTANCE *instance, const char *phase) { + if (!instance) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "[DIAGNOSE] NULL instance for phase: %s", phase); + return; + } + + struct { + int etcp_sockets_count; + int etcp_connections_count; + int etcp_links_count; + } report = {0}; + + // Подсчёт ETCP сокетов + struct ETCP_SOCKET *sock = instance->etcp_sockets; + while (sock) { + report.etcp_sockets_count++; + // Подсчёт линков в каждом сокете + for (size_t i = 0; i < sock->num_channels; i++) { + if (sock->links[i]) { + report.etcp_links_count++; + } + } + sock = sock->next; + } + + // Подсчёт ETCP соединений + struct ETCP_CONN *conn = instance->connections; + while (conn) { + report.etcp_connections_count++; + // Подсчёт линков в соединениях + struct ETCP_LINK *link = conn->links; + while (link) { + report.etcp_links_count++; + link = link->next; + } + conn = conn->next; + } + + printf("\n🔍 [UTUN_INSTANCE LEAK DIAGNOSIS] Phase: %s\n", phase); + printf(" Instance: %p\n", instance); + printf(" Node ID: %llu\n", (unsigned long long)instance->node_id); + printf(" UA instance: %p\n", instance->ua); + printf(" Running: %d\n", instance->running); + + printf("\n📊 STRUCTURE COUNTS:\n"); + printf(" ETCP Sockets: %d active\n", report.etcp_sockets_count); + printf(" ETCP Connections: %d active\n", report.etcp_connections_count); + printf(" ETCP Links: %d total\n", report.etcp_links_count); + + printf("\n🔧 RESOURCE STATUS:\n"); + printf(" Memory Pool: %s\n", instance->pkt_pool ? "ALLOCATED" : "NULL"); + printf(" TUN Socket ID: %p\n", instance->tun_socket_id); + printf(" TUN FD: %d\n", instance->tun.fd); + printf(" Connections list: %p\n", instance->connections); + printf(" ETCP Sockets list: %p\n", instance->etcp_sockets); + + printf("\n⚠️ POTENTIAL LEAKS:\n"); + if (instance->pkt_pool) { + printf(" ❌ Memory Pool not freed\n"); + } + if (instance->tun_socket_id) { + printf(" ❌ TUN socket not unregistered from uasync\n"); + } + if (report.etcp_sockets_count > 0) { + printf(" ❌ %d ETCP sockets still allocated\n", report.etcp_sockets_count); + } + if (report.etcp_connections_count > 0) { + printf(" ❌ %d ETCP connections still allocated\n", report.etcp_connections_count); + } + if (report.etcp_links_count > 0) { + printf(" ❌ %d ETCP links still allocated\n", report.etcp_links_count); + } + + printf("\n📋 RECOMMENDATIONS:\n"); + if (instance->pkt_pool) { + printf(" → Call memory_pool_destroy() before freeing instance\n"); + } + if (instance->tun_socket_id) { + printf(" → Call uasync_remove_socket() for TUN socket\n"); + } + if (report.etcp_sockets_count > 0) { + printf(" → Iterate and call etcp_socket_remove() for each socket\n"); + } + if (report.etcp_connections_count > 0) { + printf(" → Iterate and call etcp_connection_close() for each connection\n"); + } + + printf("\n"); +} diff --git a/!/utun_instance.c.backup b/!/utun_instance.c.backup new file mode 100755 index 00000000..e0dae7ea --- /dev/null +++ b/!/utun_instance.c.backup @@ -0,0 +1,314 @@ +// utun_instance.c - Root instance implementation +#include "utun_instance.h" +#include "config_parser.h" +#include "config_updater.h" +#include "tun_if.h" +#include "routing.h" +#include "etcp_connections.h" +#include "etcp.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include +#include +#include +#include +#include +#include + +// Forward declarations +static void tun_read_callback(int fd, void* user_arg); +static uint32_t get_dest_ip(const uint8_t *packet, size_t len); + +// Global instance for signal handlers +static struct UTUN_INSTANCE *g_instance = NULL; + +// Create and initialize root instance +struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config_file, const char *log_file) { + struct UTUN_INSTANCE *instance = calloc(1, sizeof(struct UTUN_INSTANCE)); + if (!instance) return NULL; + + // Initialize basic fields + instance->running = 0; + instance->log_fp = NULL; + instance->ua = ua; + + // Ensure keys and node_id exist in config + if (config_ensure_keys_and_node_id(config_file) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to ensure keys and node_id in config: %s", config_file); + free(instance); + return NULL; + } + + // Load configuration + instance->config = parse_config(config_file); + if (!instance->config) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to load config from %s", config_file); + free(instance); + return NULL; + } + + // Open log file + if (log_file) { + instance->log_fp = fopen(log_file, "a"); + if (!instance->log_fp) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to open log file %s: %s", log_file, strerror(errno)); + } + } + // Set node_id from config + instance->node_id = instance->config->global.my_node_id; + + // Set my keys + if (sc_init_local_keys(&instance->my_keys, instance->config->global.my_public_key_hex, instance->config->global.my_private_key_hex)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to initialize local keys"); + } + + + instance->pkt_pool=memory_pool_init(PACKET_DATA_SIZE+100); +/* + // Initialize TUN device + if (tun_create(&instance->tun) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TUN device"); + return -1; + } + + // Configure TUN device + if (tun_set_ip(instance->tun.ifname, instance->config->global.tun_ip) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN IP"); + tun_close(&instance->tun); + return -1; + } + + if (instance->config->global.mtu > 0) { + if (tun_set_mtu(instance->tun.ifname, instance->config->global.mtu) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN MTU"); + tun_close(&instance->tun); + return -1; + } + } + + if (tun_set_up(instance->tun.ifname) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to bring up TUN interface"); + tun_close(&instance->tun); + return -1; + } + + // Create routing table + instance->routing_table = routing_table_create(); + if (!instance->routing_table) { + DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "Failed to create routing table"); + return -1; + } +*/ + // Initialize connections from configuration - moved to utun_instance_init + // to avoid double initialization + /* + if (init_connections(instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize connections"); + // Cleanup will be handled by utun_instance_destroy + return NULL; + } + */ + + return instance; +} + +// Destroy instance and cleanup resources +void utun_instance_destroy(struct UTUN_INSTANCE *instance) { + if (!instance) return; + + printf("[INSTANCE_DESTROY] Starting cleanup for instance %p\n", instance); + + // Диагностика ресурсов ДО cleanup + utun_instance_diagnose_leaks(instance, "BEFORE_CLEANUP"); + + // Stop running if not already + instance->running = 0; + + // Cleanup ETCP sockets and connections FIRST (before destroying uasync) + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Cleaning up ETCP sockets and connections"); + struct ETCP_SOCKET* sock = instance->etcp_sockets; + while (sock) { + struct ETCP_SOCKET* next = sock->next; + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Removing socket %p, fd=%d", sock, sock->fd); + etcp_socket_remove(sock); // Полный cleanup сокета + sock = next; + } + instance->etcp_sockets = NULL; + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] ETCP sockets cleanup complete"); + + struct ETCP_CONN* conn = instance->connections; + while (conn) { + struct ETCP_CONN* next = conn->next; + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Closing connection %p", conn); + etcp_connection_close(conn); // Закрыть соединение + conn = next; + } + instance->connections = NULL; + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] ETCP connections cleanup complete"); + + // Cleanup other components + if (instance->routing_table) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Destroying routing table"); + routing_table_destroy(instance->routing_table); + instance->routing_table = NULL; + } + + // Cleanup TUN + if (instance->tun.fd >= 0) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Closing TUN interface"); + tun_close(&instance->tun); + } + + // Cleanup config + if (instance->config) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing configuration"); + free_config(instance->config); + instance->config = NULL; + } + + // Close log file + if (instance->log_fp) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Closing log file"); + fclose(instance->log_fp); + instance->log_fp = NULL; + } + + // Cleanup packet pool (ensure no leak if stop wasn't called) + if (instance->pkt_pool) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Destroying packet pool"); + memory_pool_destroy(instance->pkt_pool); + instance->pkt_pool = NULL; + } + + // FINALLY destroy uasync (after all resources are cleaned up) + if (instance->ua) { + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Destroying uasync instance"); + uasync_destroy(instance->ua); + instance->ua = NULL; + } + + // Clear global instance + if (g_instance == instance) { + g_instance = NULL; + } + + // Free the instance memory + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing instance memory"); + free(instance); + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Instance destroyed completely"); +} + +// Stop instance +void utun_instance_stop(struct UTUN_INSTANCE *instance) { + if (!instance) return; + instance->running = 0; + // Wakeup main loop using built-in uasync wakeup + if (instance->ua) { + memory_pool_destroy(instance->pkt_pool); + uasync_wakeup(instance->ua); + } + + +} + +int utun_instance_init(struct UTUN_INSTANCE *instance) { + if (!instance) return -1; + + // Initialize connections + if (init_connections(instance) < 0) { + return -1; + } + + return 0; +} + +// Диагностическая функция для анализа утечек +void utun_instance_diagnose_leaks(struct UTUN_INSTANCE *instance, const char *phase) { + if (!instance) { + printf("[DIAGNOSE] NULL instance for phase: %s\n", phase); + return; + } + + struct { + int etcp_sockets_count; + int etcp_connections_count; + int etcp_links_count; + } report = {0}; + + // Подсчёт ETCP сокетов + struct ETCP_SOCKET *sock = instance->etcp_sockets; + while (sock) { + report.etcp_sockets_count++; + // Подсчёт линков в каждом сокете + for (size_t i = 0; i < sock->num_channels; i++) { + if (sock->links[i]) { + report.etcp_links_count++; + } + } + sock = sock->next; + } + + // Подсчёт ETCP соединений + struct ETCP_CONN *conn = instance->connections; + while (conn) { + report.etcp_connections_count++; + // Подсчёт линков в соединениях + struct ETCP_LINK *link = conn->links; + while (link) { + report.etcp_links_count++; + link = link->next; + } + conn = conn->next; + } + + printf("\n🔍 [UTUN_INSTANCE LEAK DIAGNOSIS] Phase: %s\n", phase); + printf(" Instance: %p\n", instance); + printf(" Node ID: %llu\n", (unsigned long long)instance->node_id); + printf(" UA instance: %p\n", instance->ua); + printf(" Running: %d\n", instance->running); + + printf("\n📊 STRUCTURE COUNTS:\n"); + printf(" ETCP Sockets: %d active\n", report.etcp_sockets_count); + printf(" ETCP Connections: %d active\n", report.etcp_connections_count); + printf(" ETCP Links: %d total\n", report.etcp_links_count); + + printf("\n🔧 RESOURCE STATUS:\n"); + printf(" Memory Pool: %s\n", instance->pkt_pool ? "ALLOCATED" : "NULL"); + printf(" TUN Socket ID: %p\n", instance->tun_socket_id); + printf(" TUN FD: %d\n", instance->tun.fd); + printf(" Connections list: %p\n", instance->connections); + printf(" ETCP Sockets list: %p\n", instance->etcp_sockets); + + printf("\n⚠️ POTENTIAL LEAKS:\n"); + if (instance->pkt_pool) { + printf(" ❌ Memory Pool not freed\n"); + } + if (instance->tun_socket_id) { + printf(" ❌ TUN socket not unregistered from uasync\n"); + } + if (report.etcp_sockets_count > 0) { + printf(" ❌ %d ETCP sockets still allocated\n", report.etcp_sockets_count); + } + if (report.etcp_connections_count > 0) { + printf(" ❌ %d ETCP connections still allocated\n", report.etcp_connections_count); + } + if (report.etcp_links_count > 0) { + printf(" ❌ %d ETCP links still allocated\n", report.etcp_links_count); + } + + printf("\n📋 RECOMMENDATIONS:\n"); + if (instance->pkt_pool) { + printf(" → Call memory_pool_destroy() before freeing instance\n"); + } + if (instance->tun_socket_id) { + printf(" → Call uasync_remove_socket() for TUN socket\n"); + } + if (report.etcp_sockets_count > 0) { + printf(" → Iterate and call etcp_socket_remove() for each socket\n"); + } + if (report.etcp_connections_count > 0) { + printf(" → Iterate and call etcp_connection_close() for each connection\n"); + } + + printf("\n"); +} diff --git a/!/utun_instance.c1 b/!/utun_instance.c1 new file mode 100755 index 00000000..2cb5bda3 --- /dev/null +++ b/!/utun_instance.c1 @@ -0,0 +1,230 @@ +// utun_instance.c - Root instance implementation +#include "utun_instance.h" +#include "config_parser.h" +#include "config_updater.h" +#include "tun_if.h" +#include "routing.h" +#include "etcp_connections.h" +#include "etcp.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include +#include +#include +#include +#include +#include + +// Forward declarations +static void tun_read_callback(int fd, void* user_arg); +static uint32_t get_dest_ip(const uint8_t *packet, size_t len); + +// Global instance for signal handlers +static struct UTUN_INSTANCE *g_instance = NULL; + +// Create and initialize root instance +struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config_file, const char *log_file) { + struct UTUN_INSTANCE *instance = calloc(1, sizeof(struct UTUN_INSTANCE)); + if (!instance) return NULL; + + // Initialize basic fields + instance->running = 0; + instance->log_fp = NULL; + instance->ua = ua; + + // Ensure keys and node_id exist in config + if (config_ensure_keys_and_node_id(config_file) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to ensure keys and node_id in config: %s", config_file); + free(instance); + return NULL; + } + + // Load configuration + instance->config = parse_config(config_file); + if (!instance->config) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to load config from %s", config_file); + free(instance); + return NULL; + } + + // Open log file + if (log_file) { + instance->log_fp = fopen(log_file, "a"); + if (!instance->log_fp) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to open log file %s: %s", log_file, strerror(errno)); + } + } + // Set node_id from config + instance->node_id = instance->config->global.my_node_id; + + // Set my keys + if (sc_init_local_keys(&instance->my_keys, instance->config->global.my_public_key_hex, instance->config->global.my_private_key_hex)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to initialize local keys"); + } + + + instance->pkt_pool=memory_pool_init(PACKET_DATA_SIZE+100); +/* + // Initialize TUN device + if (tun_create(&instance->tun) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TUN device"); + return -1; + } + + // Configure TUN device + if (tun_set_ip(instance->tun.ifname, instance->config->global.tun_ip) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN IP"); + tun_close(&instance->tun); + return -1; + } + + if (instance->config->global.mtu > 0) { + if (tun_set_mtu(instance->tun.ifname, instance->config->global.mtu) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN MTU"); + tun_close(&instance->tun); + return -1; + } + } + + if (tun_set_up(instance->tun.ifname) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to bring up TUN interface"); + tun_close(&instance->tun); + return -1; + } + + // Create routing table + instance->routing_table = routing_table_create(); + if (!instance->routing_table) { + DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "Failed to create routing table"); + return -1; + } +*/ + // Initialize connections from configuration - moved to utun_instance_init + // to avoid double initialization + /* + if (init_connections(instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize connections"); + // Cleanup will be handled by utun_instance_destroy + return NULL; + } + */ + + return instance; +} + +// Destroy instance and cleanup resources +void utun_instance_destroy(struct UTUN_INSTANCE *instance) { + if (!instance) return; + + printf("[INSTANCE_DESTROY] Starting cleanup for instance %p\n", instance); + + // Stop running + instance->running = 0; + + // Cleanup ETCP sockets and connections FIRST (before destroying uasync) + printf("[INSTANCE_DESTROY] Cleaning up ETCP sockets and connections\n"); + if (instance->etcp_sockets) { + printf("[INSTANCE_DESTROY] Found ETCP sockets to cleanup\n"); + struct ETCP_SOCKET* sock = instance->etcp_sockets; + while (sock) { + struct ETCP_SOCKET* next = sock->next; + printf("[INSTANCE_DESTROY] Removing socket %p, fd=%d\n", sock, sock->fd); + etcp_socket_remove(sock); // Полный cleanup сокета + sock = next; + } + instance->etcp_sockets = NULL; + printf("[INSTANCE_DESTROY] ETCP sockets cleanup complete\n"); + } + + if (instance->connections) { + printf("[INSTANCE_DESTROY] Found ETCP connections to cleanup\n"); + struct ETCP_CONN* conn = instance->connections; + while (conn) { + struct ETCP_CONN* next = conn->next; + printf("[INSTANCE_DESTROY] Closing connection %p\n", conn); + etcp_connection_close(conn); // Закрыть соединение + conn = next; + } + instance->connections = NULL; + printf("[INSTANCE_DESTROY] ETCP connections cleanup complete\n"); + } + + // Cleanup other components + if (instance->routing_table) { + routing_table_destroy(instance->routing_table); + } + + // Cleanup TUN + if (instance->tun.fd >= 0) { + tun_close(&instance->tun); + } + + // Cleanup config + if (instance->config) { + free_config(instance->config); + } + + // Close log file + if (instance->log_fp) { + fclose(instance->log_fp); + } + + // FINALLY destroy uasync (after all resources are cleaned up) + printf("[INSTANCE_DESTROY] Destroying uasync instance\n"); + if (instance->ua) { + uasync_destroy(instance->ua); + } + + // Free the instance memory + printf("[INSTANCE_DESTROY] Freeing instance memory\n"); + free(instance); + printf("[INSTANCE_DESTROY] Instance destroyed completely\n"); + + // Cleanup TUN + if (instance->tun.fd >= 0) { + tun_close(&instance->tun); + } + + // Cleanup config + if (instance->config) { + free_config(instance->config); + } + + // Close log file + if (instance->log_fp) { + fclose(instance->log_fp); + } + + // uasync cleanup will handle its built-in wakeup pipe + + // Clear global instance + if (g_instance == instance) { + g_instance = NULL; + } + + free(instance); +} + +// Stop instance +void utun_instance_stop(struct UTUN_INSTANCE *instance) { + if (!instance) return; + instance->running = 0; + // Wakeup main loop using built-in uasync wakeup + if (instance->ua) { + memory_pool_destroy(instance->pkt_pool); + uasync_wakeup(instance->ua); + } + + +} + +int utun_instance_init(struct UTUN_INSTANCE *instance) { + if (!instance) return -1; + + // Initialize connections + if (init_connections(instance) < 0) { + return -1; + } + + return 0; +} diff --git a/!/utun_instance.h b/!/utun_instance.h new file mode 100755 index 00000000..57b9e5d6 --- /dev/null +++ b/!/utun_instance.h @@ -0,0 +1,72 @@ +#ifndef UTUN_INSTANCE_H +#define UTUN_INSTANCE_H + +#include +#include +#include +#include "../lib/memory_pool.h" +#include "secure_channel.h" +#include "tun_if.h" + +// Forward declarations +struct utun_config; +struct uasync_s; +struct routing_table; +struct ETCP_CONN; +struct ETCP_SOCKET; + +// uTun instance configuration +struct UTUN_INSTANCE { + // Configuration (moved from utun_state) + struct utun_config *config; + + // TUN interface + struct tun_config tun; + void *tun_socket_id; // Socket ID from uasync_add_socket + + // Identification + uint64_t node_id; + + struct SC_MYKEYS my_keys; + + // Main async context + struct UASYNC* ua; + + // State + int running; + FILE *log_fp; + + struct memory_pool* pkt_pool; + // Routing + struct routing_table *routing_table; + + // Connections + struct ETCP_CONN* connections;// linked-list + int connections_count; // Number of connections + + // Active sockets + struct ETCP_SOCKET* etcp_sockets;// linked-list +}; + +// Instance creation flags +#define UTUN_CREATE_ALLOW_TUN_FAILURE 0x0001 // Continue if TUN creation fails +#define UTUN_CREATE_NO_TUN 0x0002 // Skip TUN initialization entirely +#define UTUN_CREATE_TEST_MODE 0x0004 // Enable test hooks and virtual interfaces +#define UTUN_CREATE_NO_SOCKET_BIND 0x0008 // Skip socket binding (for test injection) + +// Functions +struct UTUN_INSTANCE* utun_instance_create_ex(struct UASYNC* ua, const char* config_file, const char* log_file, uint32_t flags); + +// Backward compatibility wrapper +static inline struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char* config_file, const char* log_file) { + return utun_instance_create_ex(ua, config_file, log_file, 0); +} +void utun_instance_destroy(struct UTUN_INSTANCE* instance); +int utun_instance_init(struct UTUN_INSTANCE *instance); +void utun_instance_run(struct UTUN_INSTANCE *instance); +void utun_instance_stop(struct UTUN_INSTANCE *instance); + +// Diagnostic function for memory leak analysis +void utun_instance_diagnose_leaks(struct UTUN_INSTANCE* instance, const char* phase); + +#endif // UTUN_INSTANCE_H diff --git a/!/utun_test_framework.c b/!/utun_test_framework.c new file mode 100755 index 00000000..6678f2dc --- /dev/null +++ b/!/utun_test_framework.c @@ -0,0 +1,586 @@ +#include "utun_test_framework.h" +#include "utun_instance.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include "../lib/ll_queue.h" +#include "test_udp_socket.h" +#include +#include +#include +#include +#include +#include +#include + +// External mutex from test_udp_socket.c +extern pthread_mutex_t g_registry_mutex; + +#ifndef DEBUG_CATEGORY_TEST +#define DEBUG_CATEGORY_TEST (1 << 30) +#endif + +// Default test configurations +const struct test_instance_config TEST_SERVER_CONFIG = { + .config_file = "test_server.conf", + .node_id = 0x1111222233334444, + .port_base = 40000, + .enable_tun = false, + .enable_socket_hooks = true, + .enable_packet_capture = true, + .log_file = NULL +}; + +const struct test_instance_config TEST_CLIENT_CONFIG = { + .config_file = "test_client.conf", + .node_id = 0x8888777766665555, + .port_base = 40100, + .enable_tun = false, + .enable_socket_hooks = true, + .enable_packet_capture = true, + .log_file = NULL +}; + +// Forward declarations for internal functions +static int test_tun_create_hook(void* priv_data); +static ssize_t test_tun_read_hook(int fd, void *buf, size_t count, void* priv_data); +static ssize_t test_tun_write_hook(int fd, const void *buf, size_t count, void* priv_data); +static int test_socket_create_hook(int domain, int type, int protocol, void* context); +static void test_packet_capture_hook(struct UTUN_INSTANCE* instance, const uint8_t* packet, size_t len, + const char* direction, void* context); +static void test_cleanup_captured_packets(struct test_instance* test); + +// Create test instance with virtual components +struct test_instance* test_create_instance(const struct test_instance_config* config) { + if (!config) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "NULL configuration provided"); + return NULL; + } + + struct test_instance* test = calloc(1, sizeof(struct test_instance)); + if (!test) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to allocate test instance"); + return NULL; + } + + // Copy configuration + test->config = *config; + + // Create uasync instance + test->ua = uasync_create(); + if (!test->ua) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create uasync instance"); + free(test); + return NULL; + } + + // Setup test hooks based on configuration + if (config->enable_tun || config->enable_socket_hooks || config->enable_packet_capture) { + test->hooks.test_context = test; + + if (config->enable_tun) { + // Create virtual TUN + char vtun_name[32]; + snprintf(vtun_name, sizeof(vtun_name), "vtun%ld", config->node_id & 0xFFFF); + test->vtun = virtual_tun_create(vtun_name); + if (!test->vtun) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create virtual TUN"); + uasync_destroy(test->ua); + free(test); + return NULL; + } + + test->hooks.tun_create_override = test_tun_create_hook; + test->hooks.tun_read_override = test_tun_read_hook; + test->hooks.tun_write_override = test_tun_write_hook; + } + + if (config->enable_socket_hooks) { + test->hooks.socket_create_override = test_socket_create_hook; + test->hooks.sendto_override = NULL; // Will be handled by virtual UDP sockets + test->hooks.recvfrom_override = NULL; // Will be handled by virtual UDP sockets + } + + if (config->enable_packet_capture) { + test->hooks.packet_captured = test_packet_capture_hook; + } + + // Install hooks + #ifdef TEST_BUILD + utun_test_hooks_set(&test->hooks); + test->hooks_installed = true; + #endif + } + + // Allocate packet capture buffer + if (config->enable_packet_capture) { + test->captured_packets.capacity = 1000; + test->captured_packets.packets = calloc(test->captured_packets.capacity, sizeof(uint8_t*)); + test->captured_packets.lengths = calloc(test->captured_packets.capacity, sizeof(size_t)); + test->captured_packets.directions = calloc(test->captured_packets.capacity, sizeof(char*)); + + if (!test->captured_packets.packets || !test->captured_packets.lengths || !test->captured_packets.directions) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to allocate packet capture buffers"); + if (test->vtun) virtual_tun_destroy(test->vtun); + uasync_destroy(test->ua); + free(test); + return NULL; + } + } + + // Allocate UDP socket tracking + test->udp_socket_capacity = 16; + test->udp_sockets = calloc(test->udp_socket_capacity, sizeof(struct test_udp_socket*)); + if (!test->udp_sockets) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to allocate UDP socket tracking"); + test_cleanup_captured_packets(test); + if (test->vtun) virtual_tun_destroy(test->vtun); + uasync_destroy(test->ua); + free(test); + return NULL; + } + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Created test instance: node_id=0x%016lX, config=%s", + config->node_id, config->config_file); + return test; +} + +// Destroy test instance +void test_destroy_instance(struct test_instance* test) { + if (!test) return; + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Destroying test instance: node_id=0x%016lX", + test->config.node_id); + + // Stop instance if running + test_stop_instance(test); + + // Clear hooks if installed + if (test->hooks_installed) { + #ifdef TEST_BUILD + utun_test_hooks_clear(); + #endif + test->hooks_installed = false; + } + + // Destroy virtual components + if (test->vtun) { + virtual_tun_destroy(test->vtun); + } + + // Destroy UDP sockets + for (int i = 0; i < test->udp_socket_count; i++) { + if (test->udp_sockets[i]) { + test_udp_socket_destroy(test->udp_sockets[i]); + } + } + free(test->udp_sockets); + + // Cleanup captured packets + test_cleanup_captured_packets(test); + + // Destroy uasync + if (test->ua) { + uasync_destroy(test->ua); + } + + // Destroy UTUN instance + if (test->utun) { + utun_instance_destroy(test->utun); + } + + free(test); +} + +// Start test instance +int test_start_instance(struct test_instance* test) { + if (!test) return -1; + if (test->utun) return 0; // Already started + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Starting test instance: node_id=0x%016lX", test->config.node_id); + + // Determine flags for instance creation + uint32_t flags = 0; + if (!test->config.enable_tun) { + flags |= UTUN_CREATE_NO_TUN; + } + if (test->config.enable_socket_hooks || test->config.enable_packet_capture) { + flags |= UTUN_CREATE_TEST_MODE; + } + if (test->config.enable_socket_hooks) { + flags |= UTUN_CREATE_NO_SOCKET_BIND; + } + if (test->config.enable_tun) { + flags |= UTUN_CREATE_ALLOW_TUN_FAILURE; // Allow virtual TUN to fail gracefully + } + + // Create UTUN instance + test->utun = utun_instance_create_ex(test->ua, test->config.config_file, test->config.log_file, flags); + if (!test->utun) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create UTUN instance"); + return -1; + } + + // Initialize instance + if (utun_instance_init(test->utun) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to initialize UTUN instance"); + utun_instance_destroy(test->utun); + test->utun = NULL; + return -1; + } + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Test instance started successfully"); + return 0; +} + +// Stop test instance +void test_stop_instance(struct test_instance* test) { + if (!test || !test->utun) return; + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Stopping test instance: node_id=0x%016lX", test->config.node_id); + + utun_instance_stop(test->utun); +} + +// Inject packet into TUN interface +int test_inject_tun_packet(struct test_instance* test, const uint8_t* packet, size_t len) { + if (!test || !test->vtun || !packet || len == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Invalid parameters for TUN packet injection"); + return -1; + } + + return virtual_tun_inject_packet(test->vtun, packet, len); +} + +// Inject UDP packet +int test_inject_udp_packet(struct test_instance* test, int socket_fd, + const uint8_t* packet, size_t len, + const struct sockaddr* src_addr, socklen_t addr_len) { + if (!test || !packet || len == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Invalid parameters for UDP packet injection"); + return -1; + } + + struct test_udp_socket* sock = test_udp_socket_find_by_fd(socket_fd); + if (!sock) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Socket fd=%d not found", socket_fd); + return -1; + } + + return test_udp_socket_inject(sock, packet, len, src_addr, addr_len); +} + +// Get captured packets +size_t test_get_captured_packets(struct test_instance* test, + const char* direction, + uint8_t*** packets, size_t** lengths) { + if (!test || !direction || !packets || !lengths) return 0; + + // Count packets matching direction + size_t count = 0; + for (size_t i = 0; i < test->captured_packets.count; i++) { + if (strcmp(test->captured_packets.directions[i], direction) == 0) { + count++; + } + } + + if (count == 0) return 0; + + // Allocate result arrays + *packets = calloc(count, sizeof(uint8_t*)); + *lengths = calloc(count, sizeof(size_t)); + if (!*packets || !*lengths) { + free(*packets); + free(*lengths); + return 0; + } + + // Fill result arrays + size_t result_idx = 0; + for (size_t i = 0; i < test->captured_packets.count; i++) { + if (strcmp(test->captured_packets.directions[i], direction) == 0) { + (*packets)[result_idx] = test->captured_packets.packets[i]; + (*lengths)[result_idx] = test->captured_packets.lengths[i]; + result_idx++; + } + } + + return count; +} + +// Get captured packet count +size_t test_get_captured_packet_count(struct test_instance* test, const char* direction) { + if (!test || !direction) return 0; + + size_t count = 0; + for (size_t i = 0; i < test->captured_packets.count; i++) { + if (strcmp(test->captured_packets.directions[i], direction) == 0) { + count++; + } + } + return count; +} + +// Clear captured packets +void test_clear_captured_packets(struct test_instance* test) { + if (!test) return; + + for (size_t i = 0; i < test->captured_packets.count; i++) { + if (test->captured_packets.packets[i]) { + free(test->captured_packets.packets[i]); + } + if (test->captured_packets.directions[i]) { + free(test->captured_packets.directions[i]); + } + } + test->captured_packets.count = 0; +} + +// Wait for packet with timeout +int test_wait_for_packet(struct test_instance* test, const char* direction, int timeout_ms) { + if (!test || !direction || timeout_ms <= 0) return -1; + + size_t initial_count = test_get_captured_packet_count(test, direction); + size_t current_count = initial_count; + + struct timeval start, current; + gettimeofday(&start, NULL); + + while (current_count == initial_count) { + gettimeofday(¤t, NULL); + int elapsed_ms = (current.tv_sec - start.tv_sec) * 1000 + (current.tv_usec - start.tv_usec) / 1000; + + if (elapsed_ms >= timeout_ms) { + DEBUG_WARN(DEBUG_CATEGORY_TEST, "Timeout waiting for %s packet after %dms", direction, timeout_ms); + return -1; + } + + usleep(10000); // Sleep 10ms + current_count = test_get_captured_packet_count(test, direction); + } + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Detected %s packet after %dms", direction, + (current.tv_sec - start.tv_sec) * 1000 + (current.tv_usec - start.tv_usec) / 1000); + return 0; +} + +// Two-instance test runner +int test_run_two_instances(const struct test_instance_config* server_config, + const struct test_instance_config* client_config, + void (*test_scenario)(struct test_instance* server, + struct test_instance* client), + int timeout_ms) { + if (!server_config || !client_config || !test_scenario) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Invalid parameters for two-instance test"); + return -1; + } + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Starting two-instance test"); + + // Create instances + struct test_instance* server = test_create_instance(server_config); + struct test_instance* client = test_create_instance(client_config); + + if (!server || !client) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create test instances"); + test_destroy_instance(server); + test_destroy_instance(client); + return -1; + } + + // Start instances + if (test_start_instance(server) < 0 || test_start_instance(client) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to start test instances"); + test_destroy_instance(server); + test_destroy_instance(client); + return -1; + } + + // Give instances time to initialize + sleep(1); + + // Run test scenario + int result = 0; + if (timeout_ms > 0) { + // TODO: Implement timeout mechanism for test scenario + test_scenario(server, client); + } else { + test_scenario(server, client); + } + + // Cleanup + test_destroy_instance(server); + test_destroy_instance(client); + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Two-instance test completed: %s", result == 0 ? "SUCCESS" : "FAILED"); + return result; +} + +// Get test instance statistics +void test_get_instance_stats(struct test_instance* test, size_t* tun_packets_sent, + size_t* tun_packets_received, size_t* udp_packets_sent, + size_t* udp_packets_received) { + if (!test) return; + + if (test->vtun) { + virtual_tun_get_stats(test->vtun, tun_packets_sent, tun_packets_received, NULL, NULL); + } + + // Aggregate UDP socket statistics + size_t udp_sent = 0, udp_received = 0; + for (int i = 0; i < test->udp_socket_count; i++) { + if (test->udp_sockets[i]) { + size_t sent, recv; + test_udp_socket_get_stats(test->udp_sockets[i], &sent, &recv, NULL, NULL, NULL, NULL); + udp_sent += sent; + udp_received += recv; + } + } + + if (udp_packets_sent) *udp_packets_sent = udp_sent; + if (udp_packets_received) *udp_packets_received = udp_received; +} + +// Utility functions +const char* test_get_direction_name(const char* direction) { + if (!direction) return "unknown"; + + if (strcmp(direction, "tun_in") == 0) return "TUN incoming"; + if (strcmp(direction, "tun_out") == 0) return "TUN outgoing"; + if (strcmp(direction, "udp_in") == 0) return "UDP incoming"; + if (strcmp(direction, "udp_out") == 0) return "UDP outgoing"; + return direction; +} + +int test_compare_packets(const uint8_t* pkt1, size_t len1, const uint8_t* pkt2, size_t len2) { + if (!pkt1 || !pkt2) return -1; + if (len1 != len2) return -1; + return memcmp(pkt1, pkt2, len1); +} + +void test_dump_packet(const char* prefix, const uint8_t* packet, size_t len) { + if (!prefix || !packet || len == 0) return; + + printf("%s: %zu bytes\n", prefix, len); + for (size_t i = 0; i < len; i++) { + printf("%02X ", packet[i]); + if ((i + 1) % 16 == 0) printf("\n"); + } + if (len % 16 != 0) printf("\n"); +} + +// Internal hook implementations +static int test_tun_create_hook(void* priv_data) { + struct test_instance* test = (struct test_instance*)priv_data; + if (!test || !test->vtun) return -1; + + // Return the read fd as the TUN device fd + return virtual_tun_get_read_fd(test->vtun); +} + +static ssize_t test_tun_read_hook(int fd, void *buf, size_t count, void* priv_data) { + struct test_instance* test = (struct test_instance*)priv_data; + if (!test || !test->vtun) return -1; + + return virtual_tun_read_packet(test->vtun, buf, count); +} + +static ssize_t test_tun_write_hook(int fd, const void *buf, size_t count, void* priv_data) { + struct test_instance* test = (struct test_instance*)priv_data; + if (!test || !test->vtun) return -1; + + ssize_t result = virtual_tun_write_packet(test->vtun, buf, count); + + // Capture packet if enabled + if (test->config.enable_packet_capture && result > 0) { + test_packet_capture_hook(test->utun, buf, result, "tun_out", test); + } + + return result; +} + +static int test_socket_create_hook(int domain, int type, int protocol, void* context) { + struct test_instance* test = (struct test_instance*)context; + if (!test || !test->config.enable_socket_hooks) return -1; + + // Create virtual UDP socket + struct test_udp_socket* sock = test_udp_socket_create(domain); + if (!sock) return -1; + + // Add to tracking + pthread_mutex_lock(&g_registry_mutex); + if (test->udp_socket_count >= test->udp_socket_capacity) { + // Expand array + int new_capacity = test->udp_socket_capacity * 2; + struct test_udp_socket** new_sockets = realloc(test->udp_sockets, + new_capacity * sizeof(struct test_udp_socket*)); + if (!new_sockets) { + pthread_mutex_unlock(&g_registry_mutex); + test_udp_socket_destroy(sock); + return -1; + } + test->udp_sockets = new_sockets; + test->udp_socket_capacity = new_capacity; + } + test->udp_sockets[test->udp_socket_count++] = sock; + pthread_mutex_unlock(&g_registry_mutex); + + return test_udp_socket_get_fd(sock); +} + +static void test_packet_capture_hook(struct UTUN_INSTANCE* instance, const uint8_t* packet, size_t len, + const char* direction, void* context) { + struct test_instance* test = (struct test_instance*)context; + if (!test || !test->config.enable_packet_capture || !packet || len == 0) return; + + // Expand capture buffer if needed + if (test->captured_packets.count >= test->captured_packets.capacity) { + size_t new_capacity = test->captured_packets.capacity * 2; + uint8_t** new_packets = realloc(test->captured_packets.packets, + new_capacity * sizeof(uint8_t*)); + size_t* new_lengths = realloc(test->captured_packets.lengths, + new_capacity * sizeof(size_t)); + char** new_directions = realloc(test->captured_packets.directions, + new_capacity * sizeof(char*)); + + if (!new_packets || !new_lengths || !new_directions) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to expand packet capture buffer"); + return; + } + + test->captured_packets.packets = new_packets; + test->captured_packets.lengths = new_lengths; + test->captured_packets.directions = new_directions; + test->captured_packets.capacity = new_capacity; + } + + // Store packet + uint8_t* packet_copy = malloc(len); + if (!packet_copy) return; + + memcpy(packet_copy, packet, len); + test->captured_packets.packets[test->captured_packets.count] = packet_copy; + test->captured_packets.lengths[test->captured_packets.count] = len; + test->captured_packets.directions[test->captured_packets.count] = strdup(direction); + test->captured_packets.count++; + + DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "Captured packet: %s, %zu bytes", direction, len); +} + +static void test_cleanup_captured_packets(struct test_instance* test) { + if (!test) return; + + for (size_t i = 0; i < test->captured_packets.count; i++) { + if (test->captured_packets.packets[i]) { + free(test->captured_packets.packets[i]); + } + if (test->captured_packets.directions[i]) { + free(test->captured_packets.directions[i]); + } + } + + free(test->captured_packets.packets); + free(test->captured_packets.lengths); + free(test->captured_packets.directions); + + memset(&test->captured_packets, 0, sizeof(test->captured_packets)); +} \ No newline at end of file diff --git a/!/utun_test_framework.h b/!/utun_test_framework.h new file mode 100755 index 00000000..fc17025e --- /dev/null +++ b/!/utun_test_framework.h @@ -0,0 +1,114 @@ +#ifndef UTUN_TEST_FRAMEWORK_H +#define UTUN_TEST_FRAMEWORK_H + +#include +#include +#include +#include "utun_test_hooks.h" +#include "test_virtual_tun.h" +#include "test_udp_socket.h" + +// Forward declarations +struct UTUN_INSTANCE; +struct UASYNC; + +// Test instance configuration +struct test_instance_config { + const char* config_file; + uint64_t node_id; + int port_base; + bool enable_tun; + bool enable_socket_hooks; + bool enable_packet_capture; + const char* log_file; +}; + +// Test instance with virtual components +struct test_instance { + struct UTUN_INSTANCE* utun; + struct UASYNC* ua; + + // Virtual components + struct virtual_tun* vtun; + struct test_udp_socket** udp_sockets; + int udp_socket_count; + int udp_socket_capacity; + + // Packet capture + struct { + uint8_t** packets; + size_t* lengths; + char** directions; // "tun_in", "tun_out", "udp_in", "udp_out" + size_t count; + size_t capacity; + } captured_packets; + + // Test configuration + struct test_instance_config config; + + // Test context + void* user_context; + + // Test hooks (for internal use) + struct utun_test_hooks hooks; + bool hooks_installed; +}; + +// Create test instance with virtual components +struct test_instance* test_create_instance(const struct test_instance_config* config); + +// Destroy test instance +void test_destroy_instance(struct test_instance* test); + +// Start test instance (initialize and run) +int test_start_instance(struct test_instance* test); + +// Stop test instance +void test_stop_instance(struct test_instance* test); + +// Inject packet into TUN interface +int test_inject_tun_packet(struct test_instance* test, const uint8_t* packet, size_t len); + +// Inject UDP packet +int test_inject_udp_packet(struct test_instance* test, int socket_fd, + const uint8_t* packet, size_t len, + const struct sockaddr* src_addr, socklen_t addr_len); + +// Get captured packets +size_t test_get_captured_packets(struct test_instance* test, + const char* direction, // "tun_in", "tun_out", "udp_in", "udp_out" + uint8_t*** packets, size_t** lengths); + +// Get captured packet count +size_t test_get_captured_packet_count(struct test_instance* test, const char* direction); + +// Clear captured packets +void test_clear_captured_packets(struct test_instance* test); + +// Wait for packet with timeout (milliseconds) +int test_wait_for_packet(struct test_instance* test, + const char* direction, + int timeout_ms); + +// Two-instance test runner +int test_run_two_instances(const struct test_instance_config* server_config, + const struct test_instance_config* client_config, + void (*test_scenario)(struct test_instance* server, + struct test_instance* client), + int timeout_ms); + +// Get test instance statistics +void test_get_instance_stats(struct test_instance* test, size_t* tun_packets_sent, + size_t* tun_packets_received, size_t* udp_packets_sent, + size_t* udp_packets_received); + +// Utility functions +const char* test_get_direction_name(const char* direction); +int test_compare_packets(const uint8_t* pkt1, size_t len1, const uint8_t* pkt2, size_t len2); +void test_dump_packet(const char* prefix, const uint8_t* packet, size_t len); + +// Default test configurations +extern const struct test_instance_config TEST_SERVER_CONFIG; +extern const struct test_instance_config TEST_CLIENT_CONFIG; + +#endif // UTUN_TEST_FRAMEWORK_H \ No newline at end of file diff --git a/!/utun_test_hooks.c b/!/utun_test_hooks.c new file mode 100755 index 00000000..36a07b59 --- /dev/null +++ b/!/utun_test_hooks.c @@ -0,0 +1,30 @@ +#include "utun_test_hooks.h" +#include "utun_instance.h" +#include "../lib/debug_config.h" + +// Add test category if not already defined +#ifndef DEBUG_CATEGORY_TEST +#define DEBUG_CATEGORY_TEST (1 << 30) // High bit for test category +#endif + +// Global test hooks - NULL in production +struct utun_test_hooks* g_utun_test_hooks = NULL; + +#ifdef TEST_BUILD +void utun_test_hooks_set(struct utun_test_hooks* hooks) { + if (!hooks) { + DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Attempt to set NULL test hooks"); + return; + } + + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Setting test hooks: sendto=%p, recvfrom=%p, tun_create=%p", + hooks->sendto_override, hooks->recvfrom_override, hooks->tun_create_override); + + g_utun_test_hooks = hooks; +} + +void utun_test_hooks_clear(void) { + DEBUG_INFO(DEBUG_CATEGORY_TEST, "Clearing test hooks (was: %p)", g_utun_test_hooks); + g_utun_test_hooks = NULL; +} +#endif \ No newline at end of file diff --git a/!/utun_test_hooks.h b/!/utun_test_hooks.h new file mode 100755 index 00000000..b487dad9 --- /dev/null +++ b/!/utun_test_hooks.h @@ -0,0 +1,126 @@ +#ifndef UTUN_TEST_HOOKS_H +#define UTUN_TEST_HOOKS_H + +#include +#include +#include +#include +#include + +// Forward declarations +struct UTUN_INSTANCE; +struct ETCP_SOCKET; + +// UDP packet interception callbacks +typedef ssize_t (*utun_sendto_hook_t)(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen, + void* context); +typedef ssize_t (*utun_recvfrom_hook_t)(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen, + void* context); + +// TUN virtual interface callbacks +typedef int (*tun_create_hook_t)(void* priv_data); +typedef ssize_t (*tun_read_hook_t)(int fd, void *buf, size_t count, void* priv_data); +typedef ssize_t (*tun_write_hook_t)(int fd, const void *buf, size_t count, void* priv_data); + +// Socket creation callback +typedef int (*socket_create_hook_t)(int domain, int type, int protocol, void* context); + +// Test packet capture callback +typedef void (*packet_capture_hook_t)(struct UTUN_INSTANCE* instance, + const uint8_t* packet, size_t len, + const char* direction, // "tun_in", "tun_out", "udp_in", "udp_out" + void* context); + +struct utun_test_hooks { + // UDP socket hooks + utun_sendto_hook_t sendto_override; + utun_recvfrom_hook_t recvfrom_override; + + // TUN hooks + tun_create_hook_t tun_create_override; + tun_read_hook_t tun_read_override; + tun_write_hook_t tun_write_override; + + // Socket creation hook + socket_create_hook_t socket_create_override; + + // Packet capture + packet_capture_hook_t packet_captured; + + // Context for callbacks + void* test_context; +}; + +// Global test hooks - NULL in production, set only by test code +extern struct utun_test_hooks* g_utun_test_hooks; + +// Test-aware wrapper functions - inline for zero overhead when NULL +static inline ssize_t utun_sendto_hook(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) { + if (g_utun_test_hooks && g_utun_test_hooks->sendto_override) { + return g_utun_test_hooks->sendto_override(sockfd, buf, len, flags, dest_addr, addrlen, + g_utun_test_hooks->test_context); + } + return sendto(sockfd, buf, len, flags, dest_addr, addrlen); +} + +static inline ssize_t utun_recvfrom_hook(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) { + if (g_utun_test_hooks && g_utun_test_hooks->recvfrom_override) { + return g_utun_test_hooks->recvfrom_override(sockfd, buf, len, flags, src_addr, addrlen, + g_utun_test_hooks->test_context); + } + return recvfrom(sockfd, buf, len, flags, src_addr, addrlen); +} + +static inline int tun_create_hook(void* priv_data) { + if (g_utun_test_hooks && g_utun_test_hooks->tun_create_override) { + return g_utun_test_hooks->tun_create_override(priv_data); + } + return -1; // Default: no TUN available in test mode +} + +static inline ssize_t tun_read_hook(int fd, void *buf, size_t count, void* priv_data) { + if (g_utun_test_hooks && g_utun_test_hooks->tun_read_override) { + return g_utun_test_hooks->tun_read_override(fd, buf, count, priv_data); + } + return read(fd, buf, count); +} + +static inline ssize_t tun_write_hook(int fd, const void *buf, size_t count, void* priv_data) { + if (g_utun_test_hooks && g_utun_test_hooks->tun_write_override) { + return g_utun_test_hooks->tun_write_override(fd, buf, count, priv_data); + } + return write(fd, buf, count); +} + +static inline int socket_create_hook(int domain, int type, int protocol) { + if (g_utun_test_hooks && g_utun_test_hooks->socket_create_override) { + return g_utun_test_hooks->socket_create_override(domain, type, protocol, + g_utun_test_hooks->test_context); + } + return socket(domain, type, protocol); +} + +static inline void packet_capture_hook(struct UTUN_INSTANCE* instance, + const uint8_t* packet, size_t len, + const char* direction) { + if (g_utun_test_hooks && g_utun_test_hooks->packet_captured) { + g_utun_test_hooks->packet_captured(instance, packet, len, direction, + g_utun_test_hooks->test_context); + } +} + +// Functions to set/clear test hooks (only available in test builds) +#ifdef TEST_BUILD +void utun_test_hooks_set(struct utun_test_hooks* hooks); +void utun_test_hooks_clear(void); +#else +// In production builds, these are no-ops for safety +static inline void utun_test_hooks_set(struct utun_test_hooks* hooks) { (void)hooks; } +static inline void utun_test_hooks_clear(void) {} +#endif + +#endif // UTUN_TEST_HOOKS_H \ No newline at end of file diff --git a/!/utun_test_socket_api.c b/!/utun_test_socket_api.c new file mode 100644 index 00000000..eb627145 --- /dev/null +++ b/!/utun_test_socket_api.c @@ -0,0 +1,117 @@ +#include "utun_test_socket_api.h" +#include +#include +#include + +void utun_test_packet_queue_init(struct utun_test_packet_queue* queue) { + if (!queue) return; + queue->head = NULL; + queue->tail = NULL; + queue->count = 0; +} + +void utun_test_packet_queue_cleanup(struct utun_test_packet_queue* queue) { + if (!queue) return; + + struct utun_test_packet* pkt = queue->head; + while (pkt) { + struct utun_test_packet* next = pkt->next; + free(pkt->data); + free(pkt); + pkt = next; + } + + queue->head = NULL; + queue->tail = NULL; + queue->count = 0; +} + +void utun_test_packet_queue_push(struct utun_test_packet_queue* queue, + const uint8_t* data, size_t len, + const struct sockaddr* addr, socklen_t addrlen) { + if (!queue || !data) return; + + struct utun_test_packet* pkt = calloc(1, sizeof(*pkt)); + if (!pkt) return; + + pkt->data = malloc(len); + if (!pkt->data) { + free(pkt); + return; + } + + memcpy(pkt->data, data, len); + pkt->len = len; + + if (addr && addrlen > 0 && addrlen <= sizeof(pkt->addr)) { + memcpy(&pkt->addr, addr, addrlen); + pkt->addrlen = addrlen; + } + + pkt->next = NULL; + + if (queue->tail) { + queue->tail->next = pkt; + } else { + queue->head = pkt; + } + queue->tail = pkt; + queue->count++; +} + +struct utun_test_packet* utun_test_packet_queue_pop(struct utun_test_packet_queue* queue) { + if (!queue || !queue->head) return NULL; + + struct utun_test_packet* pkt = queue->head; + queue->head = pkt->next; + + if (!queue->head) { + queue->tail = NULL; + } + + queue->count--; + pkt->next = NULL; + + return pkt; +} + +size_t utun_test_packet_queue_count(struct utun_test_packet_queue* queue) { + return queue ? queue->count : 0; +} + +ssize_t utun_test_default_on_send(const uint8_t* data, size_t len, + const struct sockaddr* dest_addr, socklen_t dest_addrlen, + void* user_data) { + struct utun_test_socket_api* api = (struct utun_test_socket_api*)user_data; + if (!api || !data || len == 0) return -1; + + utun_test_packet_queue_push(&api->send_queue, data, len, dest_addr, dest_addrlen); + return len; +} + +ssize_t utun_test_default_on_recv(uint8_t* buffer, size_t buffer_size, + struct sockaddr* src_addr, socklen_t* src_addrlen, + void* user_data) { + struct utun_test_socket_api* api = (struct utun_test_socket_api*)user_data; + if (!api || !buffer || buffer_size == 0) return -1; + + struct utun_test_packet* pkt = utun_test_packet_queue_pop(&api->recv_queue); + if (!pkt) { + errno = EAGAIN; + return -1; + } + + size_t to_copy = (pkt->len < buffer_size) ? pkt->len : buffer_size; + memcpy(buffer, pkt->data, to_copy); + + if (src_addr && src_addrlen && *src_addrlen > 0) { + socklen_t copy_len = (pkt->addrlen < *src_addrlen) ? pkt->addrlen : *src_addrlen; + memcpy(src_addr, &pkt->addr, copy_len); + *src_addrlen = copy_len; + } + + free(pkt->data); + free(pkt); + + return to_copy; +} \ No newline at end of file diff --git a/!/utun_test_socket_api.h b/!/utun_test_socket_api.h new file mode 100644 index 00000000..ce0445c6 --- /dev/null +++ b/!/utun_test_socket_api.h @@ -0,0 +1,52 @@ +#ifndef UTUN_TEST_SOCKET_API_H +#define UTUN_TEST_SOCKET_API_H + +#include +#include + +struct utun_test_packet { + uint8_t* data; + size_t len; + struct sockaddr_storage addr; + socklen_t addrlen; + struct utun_test_packet* next; +}; + +struct utun_test_packet_queue { + struct utun_test_packet* head; + struct utun_test_packet* tail; + size_t count; +}; + +struct utun_test_socket_api { + struct utun_test_packet_queue send_queue; + struct utun_test_packet_queue recv_queue; + + ssize_t (*on_send)(const uint8_t* data, size_t len, + const struct sockaddr* dest_addr, socklen_t dest_addrlen, + void* user_data); + + ssize_t (*on_recv)(uint8_t* buffer, size_t buffer_size, + struct sockaddr* src_addr, socklen_t* src_addrlen, + void* user_data); + + void* user_data; +}; + +void utun_test_packet_queue_init(struct utun_test_packet_queue* queue); +void utun_test_packet_queue_cleanup(struct utun_test_packet_queue* queue); +void utun_test_packet_queue_push(struct utun_test_packet_queue* queue, + const uint8_t* data, size_t len, + const struct sockaddr* addr, socklen_t addrlen); +struct utun_test_packet* utun_test_packet_queue_pop(struct utun_test_packet_queue* queue); +size_t utun_test_packet_queue_count(struct utun_test_packet_queue* queue); + +ssize_t utun_test_default_on_send(const uint8_t* data, size_t len, + const struct sockaddr* dest_addr, socklen_t dest_addrlen, + void* user_data); + +ssize_t utun_test_default_on_recv(uint8_t* buffer, size_t buffer_size, + struct sockaddr* src_addr, socklen_t* src_addrlen, + void* user_data); + +#endif \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 0a1d02f4..4d499e2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -211,6 +211,7 @@ Crypto: Fixed CCM nonce size to 13 bytes, all crypto tests passing - Старайся чтобы функция сделала логически завершенную операцию полностью - это упрощает код и понимание. - нельзя ничего восстанавливать из репозитория не прашивая - Когда пишешь код всегда загружай в контекст и мысленно разберись как работают все функции/струтуры, назначение переменных которые используешь. +- Для отладки не printf а DEBUG_* тех задания для реализации в каталоге /doc. /doc/etcp_protocol.txt - основной протокол (похож на TCP+QUIC, поддеиживает шифрования, load balancing multi-link, работу а неустойчивых каналах, утилизацию полосы и недопущение перегрузки каналов связи) diff --git a/doc/etcp_config.txt b/doc/etcp_config.txt new file mode 100755 index 00000000..06fd521e --- /dev/null +++ b/doc/etcp_config.txt @@ -0,0 +1,10 @@ +В конфиге явно прописываются сокеты с ip и портом. +Через эти сокеты идёт весь трафик, случайные порты клиент не использует - только эти сокеты. +Соответственно, возможен только один линк между двумя пирами по одному каналу. + +виртуальные сокеты - плохо т.к. используются ip/port для поиск подключения + + +план: + +1. доработать тест чтобы после init соединения еще и посылал пакеты diff --git a/doc/etcp_protocol.txt b/doc/etcp_protocol.txt index befa98b1..c96f7eb9 100644 --- a/doc/etcp_protocol.txt +++ b/doc/etcp_protocol.txt @@ -74,7 +74,7 @@ bandwidth по каждому линку адаптивно подстраива - RECV_Timestamp: это timestamp времени приёма последнего принятого пакета (т.е. отправляем время "когда мы получили этот пакет по локальном урмени") - по нему другая сторона сможет просчитать относительное время задержки в одну сторону (время туда без обратно). если последний пакет по этому каналу принят сильно давно (прошло более 30000 единиц времени) то channel timestamp не добавляем. [СПОРНО: порог 30000 (3 сек) arbitrary; может потребовать настройки или обоснования; также риск clock skew между нодами] 3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя): - [0x00] [ID high][ID low] [данные...] - Данные пакета + [0x00] [ID 4 байта] [данные...] - Данные пакета - ID: 16-битный циклический номер пакета. [СПОРНО: 16-bit может привести к быстрому overflow в long-lived сессиях; рассмотреть 32-bit] Пакет может содержать несколько опциональных секций (например, ACK + данные). [СПОРНО: нет явных length полей для секций; parsing implicit, риск ошибок при variable длине; добавить length?] цель протокола: diff --git a/lib/debug_config.c b/lib/debug_config.c index 952f7fef..c24c39c3 100644 --- a/lib/debug_config.c +++ b/lib/debug_config.c @@ -8,29 +8,14 @@ #include #include #include -#include #include #include - -/* Global debug configuration */ -debug_config_t g_debug_config = { - .level = DEBUG_LEVEL_ERROR, - .categories = DEBUG_CATEGORY_ALL, - .timestamp_enabled = 1, - .function_name_enabled = 1, - .file_line_enabled = 1, - .color_enabled = 1, - .max_output_per_second = 0, - .output_file = NULL -}; - -/* Output file handle */ -FILE* debug_output_file = NULL; +#include +#include /* Rate limiting */ static size_t output_count = 0; static time_t last_output_time = 0; -static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER; /* ANSI color codes */ static const char* color_red = "\033[31m"; @@ -41,12 +26,117 @@ static const char* color_magenta = "\033[35m"; static const char* color_cyan = "\033[36m"; static const char* color_reset = "\033[0m"; +/* Get category by name */ +static debug_category_t get_category_by_name(const char* name) { + struct { + const char* n; + debug_category_t c; + } map[] = { + {"none", DEBUG_CATEGORY_NONE}, + {"uasync", DEBUG_CATEGORY_UASYNC}, + {"ll_queue", DEBUG_CATEGORY_LL_QUEUE}, + {"connection", DEBUG_CATEGORY_CONNECTION}, + {"etcp", DEBUG_CATEGORY_ETCP}, + {"crypto", DEBUG_CATEGORY_CRYPTO}, + {"memory", DEBUG_CATEGORY_MEMORY}, + {"timing", DEBUG_CATEGORY_TIMING}, + {"config", DEBUG_CATEGORY_CONFIG}, + {"tun", DEBUG_CATEGORY_TUN}, + {"routing", DEBUG_CATEGORY_ROUTING}, + {"timers", DEBUG_CATEGORY_TIMERS}, + {"all", DEBUG_CATEGORY_ALL}, + {NULL, 0} + }; + + for (int i = 0; map[i].n; i++) { + if (strcasecmp(map[i].n, name) == 0) { + return map[i].c; + } + } + return DEBUG_CATEGORY_NONE; +} + +/* Get level by name */ +static debug_level_t get_level_by_name(const char* name) { + struct { + const char* n; + debug_level_t l; + } map[] = { + {"none", DEBUG_LEVEL_NONE}, + {"error", DEBUG_LEVEL_ERROR}, + {"warn", DEBUG_LEVEL_WARN}, + {"info", DEBUG_LEVEL_INFO}, + {"debug", DEBUG_LEVEL_DEBUG}, + {"trace", DEBUG_LEVEL_TRACE}, + {NULL, 0} + }; + + for (int i = 0; map[i].n; i++) { + if (strcasecmp(map[i].n, name) == 0) { + return map[i].l; + } + } + return DEBUG_LEVEL_NONE; +} + +/* Global debug configuration */ +debug_config_t g_debug_config; +FILE* debug_output_file = NULL; + +/* Forward declaration for global_config structure */ +struct global_config { + char log_file[256]; + char debug_level[32]; + uint32_t debug_categories; + int enable_timestamp; + int enable_function_names; + int enable_file_lines; + int enable_colors; +}; + +/* Apply debug settings from configuration if not overridden by CLI */ +void debug_apply_config(const struct global_config *config, int cli_override_level, int cli_override_categories) { + if (!config) return; + + // Apply log file if specified and not overridden + if (config->log_file[0] != '\0') { + debug_set_output_file(config->log_file); + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set log file to: %s", config->log_file); + } + + // Apply debug level from config if not overridden by CLI + if (!cli_override_level && config->debug_level[0] != '\0') { + debug_level_t level = DEBUG_LEVEL_INFO; // default + if (strcasecmp(config->debug_level, "error") == 0) level = DEBUG_LEVEL_ERROR; + else if (strcasecmp(config->debug_level, "warn") == 0) level = DEBUG_LEVEL_WARN; + else if (strcasecmp(config->debug_level, "info") == 0) level = DEBUG_LEVEL_INFO; + else if (strcasecmp(config->debug_level, "debug") == 0) level = DEBUG_LEVEL_DEBUG; + else if (strcasecmp(config->debug_level, "trace") == 0) level = DEBUG_LEVEL_TRACE; + + debug_set_level(level); + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set debug level from config: %s", config->debug_level); + } + + // Apply debug categories from config if not overridden by CLI + if (!cli_override_categories && config->debug_categories != 0) { + debug_set_categories(config->debug_categories); + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Set debug categories from config: 0x%X", config->debug_categories); + } + + // Apply output options from config + debug_enable_timestamp(config->enable_timestamp); + debug_enable_function_name(config->enable_function_names); + debug_enable_file_line(config->enable_file_lines); + debug_enable_color(config->enable_colors); + + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied debug configuration from file"); +} + /* Initialize debug system with default settings */ void debug_config_init(void) { - pthread_mutex_lock(&debug_mutex); - - g_debug_config.level = DEBUG_LEVEL_ERROR; + g_debug_config.default_level = DEBUG_LEVEL_ERROR; g_debug_config.categories = DEBUG_CATEGORY_ALL; + memset(g_debug_config.category_levels, 0, sizeof(g_debug_config.category_levels)); g_debug_config.timestamp_enabled = 1; g_debug_config.function_name_enabled = 1; g_debug_config.file_line_enabled = 1; @@ -54,110 +144,83 @@ void debug_config_init(void) { g_debug_config.max_output_per_second = 0; g_debug_config.output_file = NULL; - if (debug_output_file && debug_output_file != stderr && debug_output_file != stdout) { + if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { fclose(debug_output_file); } - debug_output_file = stderr; + debug_output_file = stdout; output_count = 0; last_output_time = 0; - - pthread_mutex_unlock(&debug_mutex); } /* Set debug level */ void debug_set_level(debug_level_t level) { - pthread_mutex_lock(&debug_mutex); - g_debug_config.level = level; - pthread_mutex_unlock(&debug_mutex); + g_debug_config.default_level = level; } /* Enable/disable specific categories */ void debug_enable_category(debug_category_t category) { - pthread_mutex_lock(&debug_mutex); g_debug_config.categories |= category; - pthread_mutex_unlock(&debug_mutex); } void debug_disable_category(debug_category_t category) { - pthread_mutex_lock(&debug_mutex); g_debug_config.categories &= ~category; - pthread_mutex_unlock(&debug_mutex); } void debug_set_categories(uint32_t categories) { - pthread_mutex_lock(&debug_mutex); g_debug_config.categories = categories; - pthread_mutex_unlock(&debug_mutex); } /* Configure output options */ void debug_enable_timestamp(int enable) { - pthread_mutex_lock(&debug_mutex); g_debug_config.timestamp_enabled = enable; - pthread_mutex_unlock(&debug_mutex); } void debug_enable_function_name(int enable) { - pthread_mutex_lock(&debug_mutex); g_debug_config.function_name_enabled = enable; - pthread_mutex_unlock(&debug_mutex); } void debug_enable_file_line(int enable) { - pthread_mutex_lock(&debug_mutex); g_debug_config.file_line_enabled = enable; - pthread_mutex_unlock(&debug_mutex); } void debug_enable_color(int enable) { - pthread_mutex_lock(&debug_mutex); g_debug_config.color_enabled = enable; - pthread_mutex_unlock(&debug_mutex); } void debug_set_rate_limit(size_t max_per_second) { - pthread_mutex_lock(&debug_mutex); g_debug_config.max_output_per_second = max_per_second; - pthread_mutex_unlock(&debug_mutex); } void debug_set_output_file(const char* file_path) { - pthread_mutex_lock(&debug_mutex); - if (file_path == NULL) { - if (debug_output_file && debug_output_file != stderr && debug_output_file != stdout) { + if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { fclose(debug_output_file); } - debug_output_file = stderr; + debug_output_file = stdout; g_debug_config.output_file = NULL; } else { FILE* new_file = fopen(file_path, "a"); if (new_file) { - if (debug_output_file && debug_output_file != stderr && debug_output_file != stdout) { + if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { fclose(debug_output_file); } debug_output_file = new_file; g_debug_config.output_file = file_path; } } - - pthread_mutex_unlock(&debug_mutex); } /* Check if debug output should be shown for given level and category */ int debug_should_output(debug_level_t level, debug_category_t category) { - pthread_mutex_lock(&debug_mutex); - /* Check if category is enabled */ if (!(g_debug_config.categories & category)) { - pthread_mutex_unlock(&debug_mutex); return 0; } /* Check if level is sufficient */ - if (level > g_debug_config.level) { - pthread_mutex_unlock(&debug_mutex); + debug_level_t eff_level = debug_get_effective_level(category); + if (level > eff_level) { return 0; } @@ -170,22 +233,33 @@ int debug_should_output(debug_level_t level, debug_category_t category) { } if (output_count >= g_debug_config.max_output_per_second) { - pthread_mutex_unlock(&debug_mutex); return 0; } output_count++; } - pthread_mutex_unlock(&debug_mutex); return 1; } /* Get current debug level for a category */ debug_level_t debug_get_effective_level(debug_category_t category) { - pthread_mutex_lock(&debug_mutex); - debug_level_t level = g_debug_config.level; - pthread_mutex_unlock(&debug_mutex); - return level; + if (category == DEBUG_CATEGORY_ALL || category == DEBUG_CATEGORY_NONE) { + return g_debug_config.default_level; + } + + /* Assume single category bit */ + uint32_t cat = category; + if (__builtin_popcount(cat) != 1) { + return DEBUG_LEVEL_NONE; /* Not supported for multiple */ + } + + int index = __builtin_ctz(cat); + if (index >= NUM_DEBUG_CATEGORIES) { + return DEBUG_LEVEL_NONE; + } + + debug_level_t lev = g_debug_config.category_levels[index]; + return (lev == DEBUG_LEVEL_NONE) ? g_debug_config.default_level : lev; } /* Get color for debug level */ @@ -221,63 +295,141 @@ void debug_output(debug_level_t level, debug_category_t category, const char* function, const char* file, int line, const char* format, ...) { - pthread_mutex_lock(&debug_mutex); +#define BUFFER_SIZE 4096 + char buffer[BUFFER_SIZE]; + int offset = 0; + size_t remaining = BUFFER_SIZE; va_list args; - FILE* output = debug_output_file ? debug_output_file : stderr; + va_start(args, format); + + FILE* output = debug_output_file ? debug_output_file : stdout; - /* Print timestamp if enabled */ + /* Add timestamp if enabled */ if (g_debug_config.timestamp_enabled) { time_t now = time(NULL); struct tm* tm_info = localtime(&now); char time_str[32]; strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); - fprintf(output, "[%s] ", time_str); + offset += snprintf(buffer + offset, remaining, "[%s] ", time_str); + remaining = BUFFER_SIZE - offset; } - /* Print level and color */ + /* Add level and color */ + const char* color = get_level_color(level); + const char* level_name = get_level_name(level); if (g_debug_config.color_enabled) { - fprintf(output, "%s[%s]%s ", get_level_color(level), get_level_name(level), color_reset); + offset += snprintf(buffer + offset, remaining, "%s[%s]%s ", color, level_name, color_reset); } else { - fprintf(output, "[%s] ", get_level_name(level)); + offset += snprintf(buffer + offset, remaining, "[%s] ", level_name); } + remaining = BUFFER_SIZE - offset; - /* Print category */ - fprintf(output, "[%d] ", category); + /* Add category */ + offset += snprintf(buffer + offset, remaining, "[%d] ", category); + remaining = BUFFER_SIZE - offset; - /* Print function name if enabled */ + /* Add function name if enabled */ if (g_debug_config.function_name_enabled && function) { - fprintf(output, "%s() ", function); + offset += snprintf(buffer + offset, remaining, "%s() ", function); + remaining = BUFFER_SIZE - offset; } - /* Print file:line if enabled */ + /* Add file:line if enabled */ if (g_debug_config.file_line_enabled && file) { - fprintf(output, "(%s:%d) ", file, line); + offset += snprintf(buffer + offset, remaining, "(%s:%d) ", file, line); + remaining = BUFFER_SIZE - offset; + } + + /* Add the actual message */ + offset += vsnprintf(buffer + offset, remaining, format, args); + remaining = BUFFER_SIZE - offset; + + /* Add newline */ + if (remaining > 1) { + buffer[offset] = '\n'; + buffer[offset + 1] = '\0'; + } else { + buffer[BUFFER_SIZE - 2] = '\n'; + buffer[BUFFER_SIZE - 1] = '\0'; } - /* Print the actual message */ - va_start(args, format); - vfprintf(output, format, args); va_end(args); - fprintf(output, "\n"); + /* Output the entire line at once */ + fprintf(output, "%s", buffer); fflush(output); - - pthread_mutex_unlock(&debug_mutex); +#undef BUFFER_SIZE } -/* Stub implementations for advanced features */ +/* Parse debug configuration from string */ int debug_parse_config(const char* config_string) { - /* Simple parser - for now just return success */ - return 0; -} + if (!config_string || !*config_string) { + return 0; + } + + char* str = strdup(config_string); + if (!str) { + return -1; + } + + char* token = strtok(str, ","); + while (token) { + /* Trim leading spaces */ + while (isspace(*token)) { + token++; + } + + char* colon = strchr(token, ':'); + if (!colon) { + free(str); + return -1; + } + + *colon = '\0'; + char* cat_name = token; + char* level_name = colon + 1; + + /* Trim leading spaces in level_name */ + while (isspace(*level_name)) { + level_name++; + } + + /* Find category */ + debug_category_t cat = get_category_by_name(cat_name); + if (cat == DEBUG_CATEGORY_NONE) { + free(str); + return -1; + } + + /* Find level */ + debug_level_t lev = get_level_by_name(level_name); + if (lev == DEBUG_LEVEL_NONE) { + free(str); + return -1; + } -int debug_parse_config_file(const char* file_path) { - /* Stub implementation */ + if (cat == DEBUG_CATEGORY_ALL) { + g_debug_config.default_level = lev; + } else { + g_debug_config.categories |= cat; + int index = __builtin_ctz(cat); + if (index < NUM_DEBUG_CATEGORIES) { + g_debug_config.category_levels[index] = lev; + } + } + + token = strtok(NULL, ","); + } + + free(str); return 0; } -int debug_enable_config_reload(const char* file_path, int interval_seconds) { - /* Stub implementation */ - return 0; +/* Apply debug settings from configuration string values */ +void debug_apply_config_values(const char *log_file, const char *debug_level_str, uint32_t debug_categories, + int enable_timestamp, int enable_function_names, int enable_file_lines, int enable_colors, + int cli_override_level, int cli_override_categories) { + // This function would be implemented to apply individual config values + // For now, it's a placeholder that could be expanded based on specific needs } \ No newline at end of file diff --git a/lib/debug_config.c1 b/lib/debug_config.c1 new file mode 100755 index 00000000..0cf1fdfb --- /dev/null +++ b/lib/debug_config.c1 @@ -0,0 +1,390 @@ +/** + * Debug configuration implementation + * Runtime debug configuration system for flexible debug output control + */ + +#include "debug_config.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global debug configuration */ +debug_config_t g_debug_config = { + .default_level = DEBUG_LEVEL_ERROR, + .categories = DEBUG_CATEGORY_ALL, + .category_levels = {0}, + .timestamp_enabled = 1, + .function_name_enabled = 1, + .file_line_enabled = 1, + .color_enabled = 1, + .max_output_per_second = 0, + .output_file = NULL +}; + +/* Output file handle */ +FILE* debug_output_file = NULL; + +/* Rate limiting */ +static size_t output_count = 0; +static time_t last_output_time = 0; + +/* ANSI color codes */ +static const char* color_red = "\033[31m"; +static const char* color_yellow = "\033[33m"; +static const char* color_green = "\033[32m"; +static const char* color_blue = "\033[34m"; +static const char* color_magenta = "\033[35m"; +static const char* color_cyan = "\033[36m"; +static const char* color_reset = "\033[0m"; + +/* Get category by name */ +static debug_category_t get_category_by_name(const char* name) { + struct { + const char* n; + debug_category_t c; + } map[] = { + {"none", DEBUG_CATEGORY_NONE}, + {"uasync", DEBUG_CATEGORY_UASYNC}, + {"ll_queue", DEBUG_CATEGORY_LL_QUEUE}, + {"connection", DEBUG_CATEGORY_CONNECTION}, + {"etcp", DEBUG_CATEGORY_ETCP}, + {"crypto", DEBUG_CATEGORY_CRYPTO}, + {"memory", DEBUG_CATEGORY_MEMORY}, + {"timing", DEBUG_CATEGORY_TIMING}, + {"config", DEBUG_CATEGORY_CONFIG}, + {"tun", DEBUG_CATEGORY_TUN}, + {"routing", DEBUG_CATEGORY_ROUTING}, + {"timers", DEBUG_CATEGORY_TIMERS}, + {"all", DEBUG_CATEGORY_ALL}, + {NULL, 0} + }; + + for (int i = 0; map[i].n; i++) { + if (strcasecmp(map[i].n, name) == 0) { + return map[i].c; + } + } + return DEBUG_CATEGORY_NONE; +} + +/* Get level by name */ +static debug_level_t get_level_by_name(const char* name) { + struct { + const char* n; + debug_level_t l; + } map[] = { + {"none", DEBUG_LEVEL_NONE}, + {"error", DEBUG_LEVEL_ERROR}, + {"warn", DEBUG_LEVEL_WARN}, + {"info", DEBUG_LEVEL_INFO}, + {"debug", DEBUG_LEVEL_DEBUG}, + {"trace", DEBUG_LEVEL_TRACE}, + {NULL, 0} + }; + + for (int i = 0; map[i].n; i++) { + if (strcasecmp(map[i].n, name) == 0) { + return map[i].l; + } + } + return DEBUG_LEVEL_NONE; +} + +/* Initialize debug system with default settings */ +void debug_config_init(void) { + g_debug_config.default_level = DEBUG_LEVEL_ERROR; + g_debug_config.categories = DEBUG_CATEGORY_ALL; + memset(g_debug_config.category_levels, 0, sizeof(g_debug_config.category_levels)); + g_debug_config.timestamp_enabled = 1; + g_debug_config.function_name_enabled = 1; + g_debug_config.file_line_enabled = 1; + g_debug_config.color_enabled = 1; + g_debug_config.max_output_per_second = 0; + g_debug_config.output_file = NULL; + + if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { + fclose(debug_output_file); + } + debug_output_file = stdout; + + output_count = 0; + last_output_time = 0; +} + +/* Set debug level */ +void debug_set_level(debug_level_t level) { + g_debug_config.default_level = level; +} + +/* Enable/disable specific categories */ +void debug_enable_category(debug_category_t category) { + g_debug_config.categories |= category; +} + +void debug_disable_category(debug_category_t category) { + g_debug_config.categories &= ~category; +} + +void debug_set_categories(uint32_t categories) { + g_debug_config.categories = categories; +} + +/* Configure output options */ +void debug_enable_timestamp(int enable) { + g_debug_config.timestamp_enabled = enable; +} + +void debug_enable_function_name(int enable) { + g_debug_config.function_name_enabled = enable; +} + +void debug_enable_file_line(int enable) { + g_debug_config.file_line_enabled = enable; +} + +void debug_enable_color(int enable) { + g_debug_config.color_enabled = enable; +} + +void debug_set_rate_limit(size_t max_per_second) { + g_debug_config.max_output_per_second = max_per_second; +} + +void debug_set_output_file(const char* file_path) { + if (file_path == NULL) { + if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { + fclose(debug_output_file); + } + debug_output_file = stdout; + g_debug_config.output_file = NULL; + } else { + FILE* new_file = fopen(file_path, "a"); + if (new_file) { + if (debug_output_file && debug_output_file != stdout && debug_output_file != stderr) { + fclose(debug_output_file); + } + debug_output_file = new_file; + g_debug_config.output_file = file_path; + } + } +} + +/* Check if debug output should be shown for given level and category */ +int debug_should_output(debug_level_t level, debug_category_t category) { + /* Check if category is enabled */ + if (!(g_debug_config.categories & category)) { + return 0; + } + + /* Check if level is sufficient */ + debug_level_t eff_level = debug_get_effective_level(category); + if (level > eff_level) { + return 0; + } + + /* Rate limiting check */ + if (g_debug_config.max_output_per_second > 0) { + time_t current_time = time(NULL); + if (current_time != last_output_time) { + output_count = 0; + last_output_time = current_time; + } + + if (output_count >= g_debug_config.max_output_per_second) { + return 0; + } + output_count++; + } + + return 1; +} + +/* Get current debug level for a category */ +debug_level_t debug_get_effective_level(debug_category_t category) { + if (category == DEBUG_CATEGORY_ALL || category == DEBUG_CATEGORY_NONE) { + return g_debug_config.default_level; + } + + /* Assume single category bit */ + uint32_t cat = category; + if (__builtin_popcount(cat) != 1) { + return DEBUG_LEVEL_NONE; /* Not supported for multiple */ + } + + int index = __builtin_ctz(cat); + if (index >= NUM_DEBUG_CATEGORIES) { + return DEBUG_LEVEL_NONE; + } + + debug_level_t lev = g_debug_config.category_levels[index]; + return (lev == DEBUG_LEVEL_NONE) ? g_debug_config.default_level : lev; +} + +/* Get color for debug level */ +static const char* get_level_color(debug_level_t level) { + if (!g_debug_config.color_enabled) { + return ""; + } + + switch (level) { + case DEBUG_LEVEL_ERROR: return color_red; + case DEBUG_LEVEL_WARN: return color_yellow; + case DEBUG_LEVEL_INFO: return color_green; + case DEBUG_LEVEL_DEBUG: return color_blue; + case DEBUG_LEVEL_TRACE: return color_magenta; + default: return ""; + } +} + +/* Get level name */ +static const char* get_level_name(debug_level_t level) { + switch (level) { + case DEBUG_LEVEL_ERROR: return "ERROR"; + case DEBUG_LEVEL_WARN: return "WARN"; + case DEBUG_LEVEL_INFO: return "INFO"; + case DEBUG_LEVEL_DEBUG: return "DEBUG"; + case DEBUG_LEVEL_TRACE: return "TRACE"; + default: return "UNKNOWN"; + } +} + +/* Format and output debug message */ +void debug_output(debug_level_t level, debug_category_t category, + const char* function, const char* file, int line, + const char* format, ...) { + +#define BUFFER_SIZE 4096 + char buffer[BUFFER_SIZE]; + int offset = 0; + size_t remaining = BUFFER_SIZE; + + va_list args; + va_start(args, format); + + FILE* output = debug_output_file ? debug_output_file : stdout; + + /* Add timestamp if enabled */ + if (g_debug_config.timestamp_enabled) { + time_t now = time(NULL); + struct tm* tm_info = localtime(&now); + char time_str[32]; + strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); + offset += snprintf(buffer + offset, remaining, "[%s] ", time_str); + remaining = BUFFER_SIZE - offset; + } + + /* Add level and color */ + const char* color = get_level_color(level); + const char* level_name = get_level_name(level); + if (g_debug_config.color_enabled) { + offset += snprintf(buffer + offset, remaining, "%s[%s]%s ", color, level_name, color_reset); + } else { + offset += snprintf(buffer + offset, remaining, "[%s] ", level_name); + } + remaining = BUFFER_SIZE - offset; + + /* Add category */ + offset += snprintf(buffer + offset, remaining, "[%d] ", category); + remaining = BUFFER_SIZE - offset; + + /* Add function name if enabled */ + if (g_debug_config.function_name_enabled && function) { + offset += snprintf(buffer + offset, remaining, "%s() ", function); + remaining = BUFFER_SIZE - offset; + } + + /* Add file:line if enabled */ + if (g_debug_config.file_line_enabled && file) { + offset += snprintf(buffer + offset, remaining, "(%s:%d) ", file, line); + remaining = BUFFER_SIZE - offset; + } + + /* Add the actual message */ + offset += vsnprintf(buffer + offset, remaining, format, args); + remaining = BUFFER_SIZE - offset; + + /* Add newline */ + if (remaining > 1) { + buffer[offset] = '\n'; + buffer[offset + 1] = '\0'; + } else { + buffer[BUFFER_SIZE - 2] = '\n'; + buffer[BUFFER_SIZE - 1] = '\0'; + } + + va_end(args); + + /* Output the entire line at once */ + fprintf(output, "%s", buffer); + fflush(output); +#undef BUFFER_SIZE +} + +/* Parse debug configuration from string */ +int debug_parse_config(const char* config_string) { + if (!config_string || !*config_string) { + return 0; + } + + char* str = strdup(config_string); + if (!str) { + return -1; + } + + char* token = strtok(str, ","); + while (token) { + /* Trim leading spaces */ + while (isspace(*token)) { + token++; + } + + char* colon = strchr(token, ':'); + if (!colon) { + free(str); + return -1; + } + + *colon = '\0'; + char* cat_name = token; + char* level_name = colon + 1; + + /* Trim leading spaces in level_name */ + while (isspace(*level_name)) { + level_name++; + } + + /* Find category */ + debug_category_t cat = get_category_by_name(cat_name); + if (cat == DEBUG_CATEGORY_NONE) { + free(str); + return -1; + } + + /* Find level */ + debug_level_t lev = get_level_by_name(level_name); + if (lev == DEBUG_LEVEL_NONE) { + free(str); + return -1; + } + + if (cat == DEBUG_CATEGORY_ALL) { + g_debug_config.default_level = lev; + } else { + g_debug_config.categories |= cat; + int index = __builtin_ctz(cat); + if (index < NUM_DEBUG_CATEGORIES) { + g_debug_config.category_levels[index] = lev; + } + } + + token = strtok(NULL, ","); + } + + free(str); + return 0; +} diff --git a/lib/debug_config.h b/lib/debug_config.h index 9f0ea712..bce40ed8 100644 --- a/lib/debug_config.h +++ b/lib/debug_config.h @@ -1,145 +1,142 @@ -/** -#include - * Runtime debug configuration system - * Provides flexible control over debug output without recompilation - */ - -#ifndef DEBUG_CONFIG_H -#define DEBUG_CONFIG_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Debug levels */ -typedef enum { - DEBUG_LEVEL_NONE = 0, // No debug output - DEBUG_LEVEL_ERROR = 1, // Errors only - DEBUG_LEVEL_WARN = 2, // Warnings and errors - DEBUG_LEVEL_INFO = 3, // Info, warnings, errors - DEBUG_LEVEL_DEBUG = 4, // Full debug output - DEBUG_LEVEL_TRACE = 5 // Trace everything -} debug_level_t; - -/* Debug categories - can be combined with bitwise OR */ -typedef enum { - DEBUG_CATEGORY_NONE = 0, - DEBUG_CATEGORY_UASYNC = 1 << 0, // u_async module - DEBUG_CATEGORY_LL_QUEUE = 1 << 1, // ll_queue module - DEBUG_CATEGORY_CONNECTION = 1 << 2, // connection module - DEBUG_CATEGORY_ETCP = 1 << 3, // etcp module - DEBUG_CATEGORY_CRYPTO = 1 << 4, // crypto operations - DEBUG_CATEGORY_MEMORY = 1 << 5, // memory management - DEBUG_CATEGORY_TIMING = 1 << 6, // timing/performance - DEBUG_CATEGORY_CONFIG = 1 << 7, // configuration parsing - DEBUG_CATEGORY_TUN = 1 << 8, // TUN interface - DEBUG_CATEGORY_ROUTING = 1 << 9, // routing table - DEBUG_CATEGORY_TIMERS = 1 << 10, // timer management - DEBUG_CATEGORY_ALL = 0xFFFFFFFF -} debug_category_t; - -/* Debug configuration structure */ -typedef struct { - debug_level_t level; // Global debug level - uint32_t categories; // Enabled categories (bitmask) - int timestamp_enabled; // Include timestamps in output - int function_name_enabled; // Include function names - int file_line_enabled; // Include file:line info - int color_enabled; // Use ANSI colors (if terminal supports) - size_t max_output_per_second; // Rate limiting (0 = unlimited) - const char* output_file; // NULL = stderr, otherwise file path -} debug_config_t; - -/* Global debug configuration */ -extern debug_config_t g_debug_config; - -/* Initialize debug system with default settings */ -void debug_config_init(void); - -/* Set debug level */ -void debug_set_level(debug_level_t level); - -/* Enable/disable specific categories */ -void debug_enable_category(debug_category_t category); -void debug_disable_category(debug_category_t category); -void debug_set_categories(uint32_t categories); - -/* Configure output options */ -void debug_enable_timestamp(int enable); -void debug_enable_function_name(int enable); -void debug_enable_file_line(int enable); -void debug_enable_color(int enable); -void debug_set_rate_limit(size_t max_per_second); -void debug_set_output_file(const char* file_path); - -/* Parse debug configuration from string (e.g., "uasync:debug,ll_queue:info") */ -int debug_parse_config(const char* config_string); - -/* Parse debug configuration from file */ -int debug_parse_config_file(const char* file_path); - -/* Enable/disable automatic config file reloading */ -int debug_enable_config_reload(const char* file_path, int interval_seconds); - -#include -extern FILE* debug_output_file; - -/* Check if debug output should be shown for given level and category */ -int debug_should_output(debug_level_t level, debug_category_t category); - -/* Get current debug level for a category */ -debug_level_t debug_get_effective_level(debug_category_t category); - -/* Format and output debug message (internal use by macros) */ -void debug_output(debug_level_t level, debug_category_t category, - const char* function, const char* file, int line, - const char* format, ...); - -/* Parse debug configuration from file */ -int debug_parse_config_file(const char* file_path); - -/* Enable/disable automatic config file reloading */ -int debug_enable_config_reload(const char* file_path, int interval_seconds); -/* Format and output debug message (internal use by macros) */ -void debug_output(debug_level_t level, debug_category_t category, - const char* function, const char* file, int line, - const char* format, ...); - -/* Convenience macros for debug output */ -#define DEBUG_ERROR(category, fmt, ...) \ - do { if (debug_should_output(DEBUG_LEVEL_ERROR, category)) { \ - debug_output(DEBUG_LEVEL_ERROR, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ - } } while(0) - -#define DEBUG_WARN(category, fmt, ...) \ - do { if (debug_should_output(DEBUG_LEVEL_WARN, category)) { \ - debug_output(DEBUG_LEVEL_WARN, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ - } } while(0) - -#define DEBUG_INFO(category, fmt, ...) \ - do { if (debug_should_output(DEBUG_LEVEL_INFO, category)) { \ - debug_output(DEBUG_LEVEL_INFO, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ - } } while(0) - -#define DEBUG_DEBUG(category, fmt, ...) \ - do { if (debug_should_output(DEBUG_LEVEL_DEBUG, category)) { \ - debug_output(DEBUG_LEVEL_DEBUG, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ - } } while(0) - -#define DEBUG_TRACE(category, fmt, ...) \ - do { if (debug_should_output(DEBUG_LEVEL_TRACE, category)) { \ - debug_output(DEBUG_LEVEL_TRACE, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ - } } while(0) - +/** + * Runtime debug configuration system + * Provides flexible control over debug output without recompilation + */ + +#ifndef DEBUG_CONFIG_H +#define DEBUG_CONFIG_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Number of categories (bits 0-10) */ +#define NUM_DEBUG_CATEGORIES 11 + +/* Debug levels */ +typedef enum { + DEBUG_LEVEL_NONE = 0, // No debug output + DEBUG_LEVEL_ERROR = 1, // Errors only + DEBUG_LEVEL_WARN = 2, // Warnings and errors + DEBUG_LEVEL_INFO = 3, // Info, warnings, errors + DEBUG_LEVEL_DEBUG = 4, // Full debug output + DEBUG_LEVEL_TRACE = 5 // Trace everything +} debug_level_t; + +/* Debug categories - can be combined with bitwise OR */ +typedef enum { + DEBUG_CATEGORY_NONE = 0, + DEBUG_CATEGORY_UASYNC = 1 << 0, // u_async module + DEBUG_CATEGORY_LL_QUEUE = 1 << 1, // ll_queue module + DEBUG_CATEGORY_CONNECTION = 1 << 2, // connection module + DEBUG_CATEGORY_ETCP = 1 << 3, // etcp module + DEBUG_CATEGORY_CRYPTO = 1 << 4, // crypto operations + DEBUG_CATEGORY_MEMORY = 1 << 5, // memory management + DEBUG_CATEGORY_TIMING = 1 << 6, // timing/performance + DEBUG_CATEGORY_CONFIG = 1 << 7, // configuration parsing + DEBUG_CATEGORY_TUN = 1 << 8, // TUN interface + DEBUG_CATEGORY_ROUTING = 1 << 9, // routing table + DEBUG_CATEGORY_TIMERS = 1 << 10, // timer management + DEBUG_CATEGORY_ALL = 0xFFFFFFFF +} debug_category_t; + +/* Debug configuration structure */ +typedef struct { + debug_level_t default_level; // Default debug level + uint32_t categories; // Enabled categories (bitmask) + debug_level_t category_levels[NUM_DEBUG_CATEGORIES]; // Per-category levels (NONE means use default) + int timestamp_enabled; // Include timestamps in output + int function_name_enabled; // Include function names + int file_line_enabled; // Include file:line info + int color_enabled; // Use ANSI colors (if terminal supports) + size_t max_output_per_second; // Rate limiting (0 = unlimited) + const char* output_file; // NULL = stdout, otherwise file path +} debug_config_t; + +/* Global debug configuration */ +extern debug_config_t g_debug_config; + +/* Initialize debug system with default settings */ +void debug_config_init(void); + +/* Set debug level */ +void debug_set_level(debug_level_t level); + +/* Enable/disable specific categories */ +void debug_enable_category(debug_category_t category); +void debug_disable_category(debug_category_t category); +void debug_set_categories(uint32_t categories); + +/* Configure output options */ +void debug_enable_timestamp(int enable); +void debug_enable_function_name(int enable); +void debug_enable_file_line(int enable); +void debug_enable_color(int enable); +void debug_set_rate_limit(size_t max_per_second); +void debug_set_output_file(const char* file_path); + +/* Parse debug configuration from string (e.g., "uasync:debug,ll_queue:info") */ +int debug_parse_config(const char* config_string); + +#include +extern FILE* debug_output_file; + +/* Check if debug output should be shown for given level and category */ +int debug_should_output(debug_level_t level, debug_category_t category); + +/* Get current debug level for a category */ +debug_level_t debug_get_effective_level(debug_category_t category); + +/* Format and output debug message (internal use by macros) */ +void debug_output(debug_level_t level, debug_category_t category, + const char* function, const char* file, int line, + const char* format, ...); + +/* Convenience macros for debug output */ +#define DEBUG_ERROR(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_ERROR, category)) { \ + debug_output(DEBUG_LEVEL_ERROR, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + +#define DEBUG_WARN(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_WARN, category)) { \ + debug_output(DEBUG_LEVEL_WARN, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + +#define DEBUG_INFO(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_INFO, category)) { \ + debug_output(DEBUG_LEVEL_INFO, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + +#define DEBUG_DEBUG(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_DEBUG, category)) { \ + debug_output(DEBUG_LEVEL_DEBUG, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + +#define DEBUG_TRACE(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_TRACE, category)) { \ + debug_output(DEBUG_LEVEL_TRACE, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + /* Backward compatibility - default to ERROR level if not specified */ #define DEBUG_OUTPUT(fmt, ...) DEBUG_ERROR(DEBUG_CATEGORY_ALL, fmt, ##__VA_ARGS__) -#ifdef __cplusplus -} -#endif - -#endif // DEBUG_CONFIG_H \ No newline at end of file +/* Apply debug settings from configuration string values */ +void debug_apply_config_values(const char *log_file, const char *debug_level_str, uint32_t debug_categories, + int enable_timestamp, int enable_function_names, int enable_file_lines, int enable_colors, + int cli_override_level, int cli_override_categories); + +/* Apply debug settings from configuration string values */ +void debug_apply_config_values(const char *log_file, const char *debug_level_str, uint32_t debug_categories, + int enable_timestamp, int enable_function_names, int enable_file_lines, int enable_colors, + int cli_override_level, int cli_override_categories); + +#ifdef __cplusplus +} +#endif + +#endif // DEBUG_CONFIG_H diff --git a/lib/debug_config.h1 b/lib/debug_config.h1 new file mode 100755 index 00000000..d4bc6618 --- /dev/null +++ b/lib/debug_config.h1 @@ -0,0 +1,132 @@ +/** + * Runtime debug configuration system + * Provides flexible control over debug output without recompilation + */ + +#ifndef DEBUG_CONFIG_H +#define DEBUG_CONFIG_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Number of categories (bits 0-10) */ +#define NUM_DEBUG_CATEGORIES 11 + +/* Debug levels */ +typedef enum { + DEBUG_LEVEL_NONE = 0, // No debug output + DEBUG_LEVEL_ERROR = 1, // Errors only + DEBUG_LEVEL_WARN = 2, // Warnings and errors + DEBUG_LEVEL_INFO = 3, // Info, warnings, errors + DEBUG_LEVEL_DEBUG = 4, // Full debug output + DEBUG_LEVEL_TRACE = 5 // Trace everything +} debug_level_t; + +/* Debug categories - can be combined with bitwise OR */ +typedef enum { + DEBUG_CATEGORY_NONE = 0, + DEBUG_CATEGORY_UASYNC = 1 << 0, // u_async module + DEBUG_CATEGORY_LL_QUEUE = 1 << 1, // ll_queue module + DEBUG_CATEGORY_CONNECTION = 1 << 2, // connection module + DEBUG_CATEGORY_ETCP = 1 << 3, // etcp module + DEBUG_CATEGORY_CRYPTO = 1 << 4, // crypto operations + DEBUG_CATEGORY_MEMORY = 1 << 5, // memory management + DEBUG_CATEGORY_TIMING = 1 << 6, // timing/performance + DEBUG_CATEGORY_CONFIG = 1 << 7, // configuration parsing + DEBUG_CATEGORY_TUN = 1 << 8, // TUN interface + DEBUG_CATEGORY_ROUTING = 1 << 9, // routing table + DEBUG_CATEGORY_TIMERS = 1 << 10, // timer management + DEBUG_CATEGORY_ALL = 0xFFFFFFFF +} debug_category_t; + +/* Debug configuration structure */ +typedef struct { + debug_level_t default_level; // Default debug level + uint32_t categories; // Enabled categories (bitmask) + debug_level_t category_levels[NUM_DEBUG_CATEGORIES]; // Per-category levels (NONE means use default) + int timestamp_enabled; // Include timestamps in output + int function_name_enabled; // Include function names + int file_line_enabled; // Include file:line info + int color_enabled; // Use ANSI colors (if terminal supports) + size_t max_output_per_second; // Rate limiting (0 = unlimited) + const char* output_file; // NULL = stdout, otherwise file path +} debug_config_t; + +/* Global debug configuration */ +extern debug_config_t g_debug_config; + +/* Initialize debug system with default settings */ +void debug_config_init(void); + +/* Set debug level */ +void debug_set_level(debug_level_t level); + +/* Enable/disable specific categories */ +void debug_enable_category(debug_category_t category); +void debug_disable_category(debug_category_t category); +void debug_set_categories(uint32_t categories); + +/* Configure output options */ +void debug_enable_timestamp(int enable); +void debug_enable_function_name(int enable); +void debug_enable_file_line(int enable); +void debug_enable_color(int enable); +void debug_set_rate_limit(size_t max_per_second); +void debug_set_output_file(const char* file_path); + +/* Parse debug configuration from string (e.g., "uasync:debug,ll_queue:info") */ +int debug_parse_config(const char* config_string); + +#include +extern FILE* debug_output_file; + +/* Check if debug output should be shown for given level and category */ +int debug_should_output(debug_level_t level, debug_category_t category); + +/* Get current debug level for a category */ +debug_level_t debug_get_effective_level(debug_category_t category); + +/* Format and output debug message (internal use by macros) */ +void debug_output(debug_level_t level, debug_category_t category, + const char* function, const char* file, int line, + const char* format, ...); + +/* Convenience macros for debug output */ +#define DEBUG_ERROR(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_ERROR, category)) { \ + debug_output(DEBUG_LEVEL_ERROR, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + +#define DEBUG_WARN(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_WARN, category)) { \ + debug_output(DEBUG_LEVEL_WARN, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + +#define DEBUG_INFO(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_INFO, category)) { \ + debug_output(DEBUG_LEVEL_INFO, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + +#define DEBUG_DEBUG(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_DEBUG, category)) { \ + debug_output(DEBUG_LEVEL_DEBUG, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + +#define DEBUG_TRACE(category, fmt, ...) \ + do { if (debug_should_output(DEBUG_LEVEL_TRACE, category)) { \ + debug_output(DEBUG_LEVEL_TRACE, category, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } } while(0) + +/* Backward compatibility - default to ERROR level if not specified */ +#define DEBUG_OUTPUT(fmt, ...) DEBUG_ERROR(DEBUG_CATEGORY_ALL, fmt, ##__VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif // DEBUG_CONFIG_H diff --git a/lib/ll_queue.c b/lib/ll_queue.c index c39b5078..35edf8ac 100644 --- a/lib/ll_queue.c +++ b/lib/ll_queue.c @@ -1,4 +1,4 @@ -// ll_queue.c +// ll_queue.c - Упрощенная архитектура: разделение создания элементов и работы с очередью #include #include #include @@ -7,122 +7,41 @@ #include "u_async.h" #include "debug_config.h" -// Предварительное объявление для отложенного возобновления +// Предварительные объявления внутренних функций static void queue_resume_timeout_cb(void* arg); - -// Проверить и запустить ожидающие коллбэки -static void check_waiters(struct ll_queue* q) { - if (!q || !q->waiters) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", - q->count, q->total_bytes); - - // Сначала собрать список коллбэков для вызова, чтобы избежать проблем с модификацией списка во время итерации - struct queue_waiter* to_call = NULL; - struct queue_waiter* to_call_tail = NULL; - - struct queue_waiter** pprev = &q->waiters; - struct queue_waiter* waiter = q->waiters; - - while (waiter) { - struct queue_waiter* next = waiter->next; - - // Проверить условие: не больше max_packets и не больше max_bytes - // max_bytes = 0 означает "не проверять байты" - if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)", - q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, - waiter->max_bytes == 0 ? "disabled" : "enabled"); - // Удалить waiter из списка - *pprev = next; - // Добавить в список для вызова - waiter->next = NULL; - if (to_call_tail) { - to_call_tail->next = waiter; - } else { - to_call = waiter; - } - to_call_tail = waiter; - } else { - // Условие не выполнено - оставить в списке - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)", - q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, - waiter->max_bytes == 0 ? "disabled" : "enabled"); - pprev = &waiter->next; - } - waiter = next; - } - - // Теперь вызвать коллбэки - waiter = to_call; - while (waiter) { - struct queue_waiter* next = waiter->next; - waiter->callback(q, waiter->callback_arg); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - waiter = next; - } -} - -// ==================== Унифицированная система версий позиций ==================== - -// Унифицированная проверка версии позиции с автоматическим пересканированием -static int queue_pos_ensure_valid(struct ll_queue_pos* pos) { - if (!pos || !pos->queue) return 0; - - // Если позиция уже валидна и версия совпадает, всё хорошо - if (pos->valid && pos->version == pos->queue->version) { - return 1; - } - - // Несовпадение версии или позиция невалидна - нужно пересканировать - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "Position version %lu != queue version %lu, rescanning...", - (unsigned long)pos->version, (unsigned long)pos->queue->version); - - // Сохранить целевой индекс (где мы хотим быть) - int target_index = pos->index; - - // Обновить версию позиции до текущей - pos->version = pos->queue->version; - - // Обработать краевой случай: индекс больше не существует из-за изменения размера очереди - if (target_index >= pos->queue->count) { - // Позиция выходит за пределы очереди - инвалидируем её - pos->valid = 0; - return 0; - } - - // Простой подход: сбросить и перейти к тому же индексу - // Это найдёт элемент, который теперь находится в целевом индексе - queue_pos_reset(pos); - return queue_pos_seek(pos, target_index); +static void check_waiters(struct ll_queue* q); +static void add_to_hash(struct ll_queue* q, struct ll_entry* entry); +static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry); + +// Вспомогательная функция для преобразования данных в запись +static inline struct ll_entry* data_to_entry(void* data) { + if (!data) return NULL; + return ((struct ll_entry*)data) - 1; } // ==================== Управление очередью ==================== -struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) { +struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size) { if (!ua) return NULL; struct ll_queue* q = calloc(1, sizeof(struct ll_queue)); if (!q) return NULL; - q->head = NULL; - q->tail = NULL; - q->count = 0; - q->total_bytes = 0; - q->size_limit = -1; // По умолчанию без ограничения - q->callback = NULL; - q->callback_arg = NULL; - q->callback_suspended = 0; // Коллбэки разрешены изначально - q->resume_timeout_id = NULL; q->ua = ua; - q->waiters = NULL; - q->pool=pool; - q->version = 0; - + q->size_limit = -1; // Без ограничения по умолчанию + q->hash_size = hash_size; + + // Создать хеш-таблицу если нужно + if (hash_size > 0) { + q->hash_table = calloc(hash_size, sizeof(struct ll_entry*)); + if (!q->hash_table) { + free(q); + return NULL; + } + } + + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_new: created queue %p, hash_size=%zu", q, hash_size); + return q; } @@ -132,39 +51,20 @@ void queue_free(struct ll_queue* q) { DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d", q, q->head, q->tail, q->count); - // Освободить все элементы (уменьшить счетчик ссылок) - struct ll_entry* entry = q->head; - int entry_count = 0; - while (entry) { - struct ll_entry* next = entry->next; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d", - entry, entry_count++, entry->ref_count); - entry->ref_count--; // Дополнительно уменьшить за присутствие в очереди - queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0 - entry = next; - } + // ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно + // Это упрощает архитектуру и предотвращает double-free - // Освободить все ожидающие коллбэки - struct queue_waiter* waiter = q->waiters; - int waiter_count = 0; - while (waiter) { - struct queue_waiter* next = waiter->next; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - waiter = next; + // Освободить хеш-таблицу + if (q->hash_table) { + free(q->hash_table); } - // Отменить отложенное возобновление если запланировано + // Отменить отложенное возобновление if (q->resume_timeout_id) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id); uasync_cancel_timeout(q->ua, q->resume_timeout_id); + q->resume_timeout_id = NULL; } - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q); free(q); } @@ -178,31 +78,26 @@ void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) static void queue_resume_timeout_cb(void* arg) { struct ll_queue* q = (struct ll_queue*)arg; - if (!q || !q->callback) return; + if (!q) return; - // Очистить ID таймаута (таймаут сработал) q->resume_timeout_id = NULL; - // Разрешить коллбэки - q->callback_suspended = 0; - - // Если в очереди есть элементы, вызвать коллбэк с первым элементом - // Обработчик должен извлечь этот элемент вызовом queue_entry_get() - if (q->head) { - q->callback(q, q->head, q->callback_arg); + // Вызвать коллбэк если есть элементы и коллбэки разрешены + if (q->head && !q->callback_suspended && q->callback) { + void* data = (void*)(q->head + 1); + q->callback(q, data, q->callback_arg); } } void queue_resume_callback(struct ll_queue* q) { - if (!q || !q->callback) return; + if (!q) return; - // Если уже есть отложенное возобновление, ничего не делать - if (q->resume_timeout_id) { - return; - } + q->callback_suspended = 0; - // Запланировать отложенное возобновление через uasync - q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb); + // Если есть элементы, запланировать вызов коллбэка + if (q->head && q->callback && !q->resume_timeout_id) { + q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb); + } } void queue_set_size_limit(struct ll_queue* q, int lim) { @@ -212,91 +107,135 @@ void queue_set_size_limit(struct ll_queue* q, int lim) { // ==================== Управление элементами ==================== -struct ll_entry* queue_entry_new(size_t data_size) { - // Выделить память под структуру + область данных +void* queue_data_new(size_t data_size) { struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size); if (!entry) return NULL; - entry->next = NULL; - entry->prev = NULL; // Инициализировать prev для двусвязного списка + memset(entry, 0, sizeof(struct ll_entry) + data_size); entry->size = data_size; - entry->ref_count = 1; // Начальный счетчик ссылок - // Область данных оставить неинициализированной для производительности + entry->ref_count = 1; // Единственная ссылка - на сам элемент + entry->pool = NULL; // Выделено через malloc - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d", - entry, data_size, entry->ref_count); + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_new: created entry %p, size=%zu", entry, data_size); - return entry; + return (void*)(entry + 1); } -void queue_entry_free(struct ll_entry* entry) { - if (!entry) return; +void* queue_data_new_from_pool(struct memory_pool* pool) { + if (!pool) return NULL; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d", - entry, entry->ref_count); + struct ll_entry* entry = memory_pool_alloc(pool); + if (!entry) return NULL; - if (entry->ref_count <= 0) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d", - entry, entry->ref_count); - return; // Предотвратить double-free - } + memset(entry, 0, pool->object_size); + entry->size = pool->object_size - sizeof(struct ll_entry); + entry->ref_count = 1; // Единственная ссылка - на сам элемент + entry->pool = pool; // Выделено из пула - entry->ref_count--; - if (entry->ref_count == 0) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry); - free(entry); - } else { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p", - entry->ref_count, entry); + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_new_from_pool: created entry %p from pool %p", entry, pool); + + return (void*)(entry + 1); +} + +void queue_data_free(void* data) { + if (!data) return; + + struct ll_entry* entry = data_to_entry(data); + + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_free: freeing entry %p, ref_count=%d", entry, entry->ref_count); + + if (--entry->ref_count <= 0) { + if (entry->pool) { + memory_pool_free(entry->pool, entry); + } else { + free(entry); + } } } // ==================== Операции с очередью ==================== -int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) return -1; +// Внутренняя функция добавления в хеш-таблицу +static void add_to_hash(struct ll_queue* q, struct ll_entry* entry) { + if (!q || q->hash_size == 0 || !entry) return; + + size_t slot = entry->id % q->hash_size; + entry->hash_next = q->hash_table[slot]; + q->hash_table[slot] = entry; +} + +// Внутренняя функция удаления из хеш-таблицы +static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry) { + if (!q || q->hash_size == 0 || !entry) return; + + size_t slot = entry->id % q->hash_size; + struct ll_entry** ptr = &q->hash_table[slot]; + while (*ptr) { + if (*ptr == entry) { + *ptr = entry->hash_next; + entry->hash_next = NULL; + return; + } + ptr = &(*ptr)->hash_next; + } +} + +// Проверить и запустить ожидающие коллбэки +static void check_waiters(struct ll_queue* q) { + if (!q) return; + + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", + q->count, q->total_bytes); + + struct queue_waiter* waiter = &q->waiter; + + if (waiter->callback) { + // Проверить условие: не больше max_packets и не больше max_bytes + // max_bytes = 0 означает "не проверять байты" + if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) { + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)", + q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, + waiter->max_bytes == 0 ? "disabled" : "enabled"); + waiter->callback(q, waiter->callback_arg); + memset(waiter, 0, sizeof(*waiter)); + } + } +} + +int queue_data_put(struct ll_queue* q, void* data, uint32_t id) { + if (!q || !data) return -1; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d", - entry, entry->size, q->count); + struct ll_entry* entry = data_to_entry(data); + entry->id = id; // Проверить лимит размера if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); + queue_data_free(data); // Освободить элемент если превышен лимит return -1; } - // Увеличить счетчик ссылок при добавлении в очередь - entry->ref_count++; - - // Добавить в хвост (FIFO) - двусвязный список + // Добавить в конец entry->next = NULL; entry->prev = q->tail; - if (q->tail) { q->tail->next = entry; } else { - q->head = entry; // Очередь была пустой + q->head = entry; } q->tail = entry; + q->count++; q->total_bytes += entry->size; + // ВАЖНО: НЕ увеличиваем ref_count - элемент просто находится в очереди + + add_to_hash(q, entry); - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", - entry, q->count, q->total_bytes, entry->ref_count); - - // Инвалидировать позиции - q->version++; - - // Если коллбэки разрешены - вызвать коллбэк - // Это запускает автоматическую обработку очереди - if (!q->callback_suspended && q->callback) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry); - - // Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию - q->callback_suspended = 1; - q->callback(q, q->head, q->callback_arg); - // Не восстанавливать здесь - восстановление происходит через queue_resume_callback + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_put: added entry %p (id=%u), count=%d", + entry, id, q->count); + + // Если очередь была пуста и коллбэки разрешены - вызвать коллбэк + if (q->count == 1 && !q->callback_suspended && q->callback) { + q->callback(q, data, q->callback_arg); } // Проверить ожидающие коллбэки @@ -305,46 +244,40 @@ int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) { return 0; } -int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) return -1; +int queue_data_put_first(struct ll_queue* q, void* data, uint32_t id) { + if (!q || !data) return -1; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d", - entry, entry->size, q->count); + struct ll_entry* entry = data_to_entry(data); + entry->id = id; // Проверить лимит размера if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); + queue_data_free(data); // Освободить элемент если превышен лимит return -1; } - // Увеличить счетчик ссылок при добавлении в очередь - entry->ref_count++; - - // Добавить в голову (LIFO, высокий приоритет) - двусвязный список + // Добавить в начало entry->next = q->head; entry->prev = NULL; - if (q->head) { q->head->prev = entry; } else { - q->tail = entry; // Очередь была пустой + q->tail = entry; } q->head = entry; + q->count++; q->total_bytes += entry->size; + // ВАЖНО: НЕ увеличиваем ref_count - элемент просто находится в очереди - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", - entry, q->count, q->total_bytes, entry->ref_count); + add_to_hash(q, entry); - // Инвалидировать позиции - q->version++; + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_put_first: added entry %p (id=%u), count=%d", + entry, id, q->count); - // Если коллбэки разрешены - вызвать коллбэк - if (!q->callback_suspended && q->callback) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry); - q->callback_suspended = 1; - q->callback(q, q->head, q->callback_arg); + // Если очередь была пуста и коллбэки разрешены - вызвать коллбэк + if (q->count == 1 && !q->callback_suspended && q->callback) { + q->callback(q, data, q->callback_arg); } // Проверить ожидающие коллбэки @@ -353,495 +286,119 @@ int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) { return 0; } -struct ll_entry* queue_entry_get(struct ll_queue* q) { +void* queue_data_get(struct ll_queue* q) { if (!q || !q->head) return NULL; struct ll_entry* entry = q->head; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d", - entry, entry->size, entry->ref_count); - // Удалить из двусвязного списка q->head = entry->next; - if (q->head) { - q->head->prev = NULL; - } else { - q->tail = NULL; // Очередь стала пустой - } + if (q->head) q->head->prev = NULL; + if (!q->head) q->tail = NULL; + q->count--; q->total_bytes -= entry->size; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu", - entry, q->count, q->total_bytes); - entry->next = NULL; - entry->prev = NULL; // Полностью отсоединить от очереди + entry->prev = NULL; + + remove_from_hash(q, entry); - // Уменьшить счетчик ссылок при извлечении из очереди - // entry->ref_count был увеличен при добавлении в очередь - // теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать - entry->ref_count--; + // ВАЖНО: НЕ уменьшаем ref_count - просто удаляем из очереди - // Инвалидировать позиции - q->version++; + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_data_get: got entry %p (id=%u), count=%d", + entry, entry->id, q->count); - // При извлечении элемента приостанавливаем коллбэки - // Это предотвращает рекурсию если во время обработки добавляются новые элементы + // Приостановить коллбэки для предотвращения рекурсии q->callback_suspended = 1; - // Проверить ожидающие коллбэки при извлечении элемента - // Это важно для waiters, которые ожидают уменьшения очереди - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count); + // Проверить ожидающие коллбэки check_waiters(q); - return entry; + return (void*)(entry + 1); } int queue_entry_count(struct ll_queue* q) { - if (!q) return 0; - return q->count; + return q ? q->count : 0; } // ==================== Асинхронное ожидание ==================== struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, - queue_threshold_callback_fn callback, void* arg) { + queue_threshold_callback_fn callback, void* arg) { if (!q || !callback) return NULL; - // Создать новый waiter - struct queue_waiter* waiter; - if (q->pool) { - waiter = (struct queue_waiter*)memory_pool_alloc(q->pool); - } else { - waiter = malloc(sizeof(struct queue_waiter)); - } - if (!waiter) return NULL; - - waiter->max_packets = max_packets; - waiter->max_bytes = max_bytes; - waiter->callback = callback; - waiter->callback_arg = arg; - waiter->next = NULL; + struct queue_waiter* waiter = &q->waiter; // Проверить условие немедленно if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) { - // Условие уже выполнено - вызвать коллбэк и освободить waiter - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback", - q->count, max_packets, q->total_bytes, max_bytes); + // Условие уже выполнено - вызвать коллбэк немедленно callback(q, arg); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } return NULL; } - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu", - q->count, q->total_bytes, max_packets, max_bytes); - - // Добавить в список ожидающих - waiter->next = q->waiters; - q->waiters = waiter; + // Установить waiter для отложенного вызова + waiter->max_packets = max_packets; + waiter->max_bytes = max_bytes; + waiter->callback = callback; + waiter->callback_arg = arg; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully"); return waiter; } void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) { - if (!q || !waiter) return; - - // Найти и удалить waiter из списка - struct queue_waiter** pprev = &q->waiters; - struct queue_waiter* w = q->waiters; - - while (w) { - if (w == waiter) { - *pprev = w->next; - if (q->pool) { - memory_pool_free(q->pool, w); - } else { - free(w); - } - return; - } - pprev = &w->next; - w = w->next; - } -} - -// ==================== Статистика и метрики ==================== - -void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) { - if (!q || !q->pool) { - if (waiter_allocations) *waiter_allocations = 0; - if (waiter_reuse) *waiter_reuse = 0; - return; - } - - memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse); -} - -// ==================== Управление позициями ==================== - -struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) { - if (!queue) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL"); - return NULL; - } - - struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos)); - if (!pos) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position"); - return NULL; - } + if (!q || !waiter || waiter != &q->waiter) return; - pos->queue = queue; - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; // Перед head - pos->valid = 1; - pos->version = queue->version; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue); - return pos; -} - -void queue_pos_free(struct ll_queue_pos* pos) { - if (!pos) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos); - free(pos); + memset(waiter, 0, sizeof(*waiter)); } -void queue_pos_reset(struct ll_queue_pos* pos) { - if (!pos) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL"); - return; - } - - // Обновить версию позиции при необходимости (вместо инвалидации) - if (pos->queue->version != pos->version) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: updating position version from %lu to %lu", - (unsigned long)pos->version, (unsigned long)pos->queue->version); - pos->version = pos->queue->version; - } - - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - pos->valid = 1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset"); -} +// ==================== Поиск и удаление по ID ==================== -int queue_pos_next(struct ll_queue_pos* pos) { - if (!pos) return 0; - - // Убедиться, что позиция валидна (автоматическое обновление при необходимости) - if (!queue_pos_ensure_valid(pos)) { - return 0; - } +void* queue_find_data_by_id(struct ll_queue* q, uint32_t id) { + if (!q || q->hash_size == 0 || !q->hash_table) return NULL; - if (!pos->current) { - // Перед head - переходим к head - if (!pos->queue->head) { - // Очередь пуста - некуда переходить - return 0; - } - pos->current = pos->queue->head; - pos->prev = NULL; - pos->index = 0; - } else if (pos->current->next) { - // Есть следующий элемент - pos->prev = pos->current; - pos->current = pos->current->next; - pos->index++; - } else { - // Достигнут конец очереди - return 0; - } + size_t slot = id % q->hash_size; + struct ll_entry* entry = q->hash_table[slot]; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_prev(struct ll_queue_pos* pos) { - if (!pos) return 0; - - // Убедиться, что позиция валидна (автоматическое обновление при необходимости) - if (!queue_pos_ensure_valid(pos)) { - return 0; - } - - if (!pos->current) { - // Уже перед head - дальше некуда - return 0; - } else if (pos->current->prev) { - // Есть предыдущий элемент - pos->current = pos->current->prev; - pos->prev = pos->current->prev; - pos->index--; - } else { - // Достигнуто начало очереди - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_seek(struct ll_queue_pos* pos, int target_index) { - if (!pos) return 0; - - // Убедиться, что позиция валидна (автоматическое обновление при необходимости) - if (!queue_pos_ensure_valid(pos)) { - return 0; - } - - if (target_index < -1 || target_index >= pos->queue->count) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)", - target_index, pos->queue->count); - return 0; - } - - // Простая реализация: начинаем с начала и идем вперед - // Можно оптимизировать позже, если потребуется - queue_pos_reset(pos); - - for (int i = 0; i <= target_index; i++) { - if (!queue_pos_next(pos)) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index); - return 0; - } - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) { - if (!pos || !pos->valid || !entry) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters"); - return -1; - } - - if (pos->queue->version != pos->version) { - pos->valid = 0; - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: position invalidated due to queue version change"); - return -1; - } - - struct ll_queue* q = pos->queue; - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - entry->ref_count++; - - if (mode == 0) { - // Вставить перед current - if (!pos->current) { - // Вставить в head - entry->next = q->head; - entry->prev = NULL; - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - } else { - // Вставить перед current - entry->next = pos->current; - entry->prev = pos->current->prev; - - if (pos->current->prev) { - pos->current->prev->next = entry; - } else { - q->head = entry; // Вставляем в начало - } - pos->current->prev = entry; - } - } else { - // Вставить после current - if (!pos->current) { - // Вставить в head (current = NULL, значит перед head) - entry->next = q->head; - entry->prev = NULL; - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - } else { - // Вставить после current - entry->next = pos->current->next; - entry->prev = pos->current; - - if (pos->current->next) { - pos->current->next->prev = entry; - } else { - q->tail = entry; // Вставляем в конец - } - pos->current->next = entry; - } - } - - q->count++; - q->total_bytes += entry->size; - - // Обновить версию очереди и позиции (вместо полной инвалидации) - q->version++; - - // Обновить текущую позицию, если она активна - if (pos->current) { - pos->version = q->version; - pos->valid = 1; // Сделать позицию снова валидной - // Корректировка индекса в зависимости от режима вставки - if (mode == 0) { - // Вставка перед текущим - индекс остается тем же - // pos->index не меняется - } else { - // Вставка после текущего - текущий элемент смещается - pos->index++; + while (entry) { + if (entry->id == id) { + return (void*)(entry + 1); } + entry = entry->hash_next; } - // Позиция остается валидной - убрали строку pos->valid = 0; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d", - entry, mode, q->count); - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; + return NULL; } -struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) { - if (!pos || !pos->valid || !pos->current) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element"); - return NULL; - } +int queue_remove_data(struct ll_queue* q, void* data) { + if (!q || !data) return -1; - // Убедиться, что позиция валидна (автоматическое обновление при необходимости) - if (!queue_pos_ensure_valid(pos)) { - return NULL; - } - - struct ll_queue* q = pos->queue; - struct ll_entry* entry = pos->current; + struct ll_entry* entry = data_to_entry(data); // Удалить из двусвязного списка if (entry->prev) { entry->prev->next = entry->next; } else { - q->head = entry->next; // Удаляем head + q->head = entry->next; } if (entry->next) { entry->next->prev = entry->prev; } else { - q->tail = entry->prev; // Удаляем tail + q->tail = entry->prev; } q->count--; q->total_bytes -= entry->size; - // Обновить позицию: перейти к следующему элементу - pos->current = entry->next; - pos->prev = entry->prev; - if (pos->current) { - // Остались элементы после удаленного - pos->index = pos->index; // Индекс остается тем же - } else { - // Удалили последний элемент - pos->index = q->count; // Указывает за конец - if (pos->index == 0) { - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - } - } - - entry->next = NULL; - entry->prev = NULL; - entry->ref_count--; // Уменьшить счетчик ссылок - - // Обновить версию очереди и позицию (вместо полной инвалидации) - q->version++; - if (pos->current) { - pos->version = q->version; - pos->valid = 1; // Сделать позицию снова валидной - // pos->index уже обновлен выше - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d", - entry, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return entry; -} - -int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters"); - return -1; - } - - // Проверить, что элемент действительно в этой очереди - struct ll_entry* curr = q->head; - while (curr) { - if (curr == entry) { - break; - } - curr = curr->next; - } - - if (!curr) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry); - return -1; - } - - // Удалить из двусвязного списка - if (entry->prev) { - entry->prev->next = entry->next; - } else { - q->head = entry->next; // Удаляем head - } - - if (entry->next) { - entry->next->prev = entry->prev; - } else { - q->tail = entry->prev; // Удаляем tail - } - - q->count--; - q->total_bytes -= entry->size; + // ВАЖНО: НЕ уменьшаем ref_count - просто удаляем из очереди entry->next = NULL; entry->prev = NULL; - entry->ref_count--; - // Инвалидировать позиции - q->version++; + remove_from_hash(q, entry); - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d", - entry, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_remove_data: removed entry %p (id=%u), count=%d", + entry, entry->id, q->count); return 0; } \ No newline at end of file diff --git a/lib/ll_queue.c2 b/lib/ll_queue.c2 deleted file mode 100755 index b442ae6e..00000000 --- a/lib/ll_queue.c2 +++ /dev/null @@ -1,829 +0,0 @@ -// ll_queue.c -#include -#include -#include -#include -#include "ll_queue.h" -#include "u_async.h" -#include "debug_config.h" - -// Предварительное объявление для отложенного возобновления -static void queue_resume_timeout_cb(void* arg); - -// Проверить и запустить ожидающие коллбэки -static void check_waiters(struct ll_queue* q) { - if (!q || !q->waiters) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", - q->count, q->total_bytes); - - // Сначала собрать список коллбэков для вызова, чтобы избежать проблем с модификацией списка во время итерации - struct queue_waiter* to_call = NULL; - struct queue_waiter* to_call_tail = NULL; - - struct queue_waiter** pprev = &q->waiters; - struct queue_waiter* waiter = q->waiters; - - while (waiter) { - struct queue_waiter* next = waiter->next; - - // Проверить условие: не больше max_packets и не больше max_bytes - // max_bytes = 0 означает "не проверять байты" - if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)", - q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, - waiter->max_bytes == 0 ? "disabled" : "enabled"); - // Удалить waiter из списка - *pprev = next; - // Добавить в список для вызова - waiter->next = NULL; - if (to_call_tail) { - to_call_tail->next = waiter; - } else { - to_call = waiter; - } - to_call_tail = waiter; - } else { - // Условие не выполнено - оставить в списке - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)", - q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, - waiter->max_bytes == 0 ? "disabled" : "enabled"); - pprev = &waiter->next; - } - waiter = next; - } - - // Теперь вызвать коллбэки - waiter = to_call; - while (waiter) { - struct queue_waiter* next = waiter->next; - waiter->callback(q, waiter->callback_arg); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - waiter = next; - } -} - -// ==================== Управление очередью ==================== - -struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) { - if (!ua) return NULL; - - struct ll_queue* q = calloc(1, sizeof(struct ll_queue)); - if (!q) return NULL; - - q->head = NULL; - q->tail = NULL; - q->count = 0; - q->total_bytes = 0; - q->size_limit = -1; // По умолчанию без ограничения - q->callback = NULL; - q->callback_arg = NULL; - q->callback_suspended = 0; // Коллбэки разрешены изначально - q->resume_timeout_id = NULL; - q->ua = ua; - q->waiters = NULL; - q->pool=pool; - q->version = 0; - - return q; -} - -void queue_free(struct ll_queue* q) { - if (!q) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d", - q, q->head, q->tail, q->count); - - // Освободить все элементы (уменьшить счетчик ссылок) - struct ll_entry* entry = q->head; - int entry_count = 0; - while (entry) { - struct ll_entry* next = entry->next; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d", - entry, entry_count++, entry->ref_count); - entry->ref_count--; // Дополнительно уменьшить за присутствие в очереди - queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0 - entry = next; - } - - // Освободить все ожидающие коллбэки - struct queue_waiter* waiter = q->waiters; - int waiter_count = 0; - while (waiter) { - struct queue_waiter* next = waiter->next; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - waiter = next; - } - - // Отменить отложенное возобновление если запланировано - if (q->resume_timeout_id) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id); - uasync_cancel_timeout(q->ua, q->resume_timeout_id); - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q); - free(q); -} - -// ==================== Конфигурация очереди ==================== - -void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) { - if (!q) return; - q->callback = cbk_fn; - q->callback_arg = arg; -} - -static void queue_resume_timeout_cb(void* arg) { - struct ll_queue* q = (struct ll_queue*)arg; - if (!q || !q->callback) return; - - // Очистить ID таймаута (таймаут сработал) - q->resume_timeout_id = NULL; - - // Разрешить коллбэки - q->callback_suspended = 0; - - // Если в очереди есть элементы, вызвать коллбэк с первым элементом - // Обработчик должен извлечь этот элемент вызовом queue_entry_get() - if (q->head) { - q->callback(q, q->head, q->callback_arg); - } -} - -void queue_resume_callback(struct ll_queue* q) { - if (!q || !q->callback) return; - - // Если уже есть отложенное возобновление, ничего не делать - if (q->resume_timeout_id) { - return; - } - - // Запланировать отложенное возобновление через uasync - q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb); -} - -void queue_set_size_limit(struct ll_queue* q, int lim) { - if (!q) return; - q->size_limit = lim; -} - -// ==================== Управление элементами ==================== - -struct ll_entry* queue_entry_new(size_t data_size) { - // Выделить память под структуру + область данных - struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size); - if (!entry) return NULL; - - entry->next = NULL; - entry->prev = NULL; // Инициализировать prev для двусвязного списка - entry->size = data_size; - entry->ref_count = 1; // Начальный счетчик ссылок - // Область данных оставить неинициализированной для производительности - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d", - entry, data_size, entry->ref_count); - - return entry; -} - -void queue_entry_free(struct ll_entry* entry) { - if (!entry) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d", - entry, entry->ref_count); - - if (entry->ref_count <= 0) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d", - entry, entry->ref_count); - return; // Предотвратить double-free - } - - entry->ref_count--; - if (entry->ref_count == 0) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry); - free(entry); - } else { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p", - entry->ref_count, entry); - } -} - -// ==================== Операции с очередью ==================== - -int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) return -1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d", - entry, entry->size, q->count); - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - // Увеличить счетчик ссылок при добавлении в очередь - entry->ref_count++; - - // Добавить в хвост (FIFO) - двусвязный список - entry->next = NULL; - entry->prev = q->tail; - - if (q->tail) { - q->tail->next = entry; - } else { - q->head = entry; // Очередь была пустой - } - q->tail = entry; - q->count++; - q->total_bytes += entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", - entry, q->count, q->total_bytes, entry->ref_count); - - // Инвалидировать позиции - q->version++; - - // Если коллбэки разрешены - вызвать коллбэк - // Это запускает автоматическую обработку очереди - if (!q->callback_suspended && q->callback) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry); - - // Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию - q->callback_suspended = 1; - q->callback(q, q->head, q->callback_arg); - // Не восстанавливать здесь - восстановление происходит через queue_resume_callback - } - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} - -int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) return -1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d", - entry, entry->size, q->count); - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - // Увеличить счетчик ссылок при добавлении в очередь - entry->ref_count++; - - // Добавить в голову (LIFO, высокий приоритет) - двусвязный список - entry->next = q->head; - entry->prev = NULL; - - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - q->count++; - q->total_bytes += entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", - entry, q->count, q->total_bytes, entry->ref_count); - - // Инвалидировать позиции - q->version++; - - // Если коллбэки разрешены - вызвать коллбэк - if (!q->callback_suspended && q->callback) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry); - q->callback_suspended = 1; - q->callback(q, q->head, q->callback_arg); - } - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} - -struct ll_entry* queue_entry_get(struct ll_queue* q) { - if (!q || !q->head) return NULL; - - struct ll_entry* entry = q->head; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d", - entry, entry->size, entry->ref_count); - - // Удалить из двусвязного списка - q->head = entry->next; - if (q->head) { - q->head->prev = NULL; - } else { - q->tail = NULL; // Очередь стала пустой - } - q->count--; - q->total_bytes -= entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu", - entry, q->count, q->total_bytes); - - entry->next = NULL; - entry->prev = NULL; // Полностью отсоединить от очереди - - // Уменьшить счетчик ссылок при извлечении из очереди - // entry->ref_count был увеличен при добавлении в очередь - // теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать - entry->ref_count--; - - // Инвалидировать позиции - q->version++; - - // При извлечении элемента приостанавливаем коллбэки - // Это предотвращает рекурсию если во время обработки добавляются новые элементы - q->callback_suspended = 1; - - // Проверить ожидающие коллбэки при извлечении элемента - // Это важно для waiters, которые ожидают уменьшения очереди - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count); - check_waiters(q); - - return entry; -} - -int queue_entry_count(struct ll_queue* q) { - if (!q) return 0; - return q->count; -} - -// ==================== Асинхронное ожидание ==================== - -struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, - queue_threshold_callback_fn callback, void* arg) { - if (!q || !callback) return NULL; - - // Создать новый waiter - struct queue_waiter* waiter; - if (q->pool) { - waiter = (struct queue_waiter*)memory_pool_alloc(q->pool); - } else { - waiter = malloc(sizeof(struct queue_waiter)); - } - if (!waiter) return NULL; - - waiter->max_packets = max_packets; - waiter->max_bytes = max_bytes; - waiter->callback = callback; - waiter->callback_arg = arg; - waiter->next = NULL; - - // Проверить условие немедленно - if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) { - // Условие уже выполнено - вызвать коллбэк и освободить waiter - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback", - q->count, max_packets, q->total_bytes, max_bytes); - callback(q, arg); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - return NULL; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu", - q->count, q->total_bytes, max_packets, max_bytes); - - // Добавить в список ожидающих - waiter->next = q->waiters; - q->waiters = waiter; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully"); - return waiter; -} - -void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) { - if (!q || !waiter) return; - - // Найти и удалить waiter из списка - struct queue_waiter** pprev = &q->waiters; - struct queue_waiter* w = q->waiters; - - while (w) { - if (w == waiter) { - *pprev = w->next; - if (q->pool) { - memory_pool_free(q->pool, w); - } else { - free(w); - } - return; - } - pprev = &w->next; - w = w->next; - } -} - -// ==================== Статистика и метрики ==================== - -void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) { - if (!q || !q->pool) { - if (waiter_allocations) *waiter_allocations = 0; - if (waiter_reuse) *waiter_reuse = 0; - return; - } - - memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse); -} - -// ==================== Управление позициями ==================== - -struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) { - if (!queue) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL"); - return NULL; - } - - struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos)); - if (!pos) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position"); - return NULL; - } - - pos->queue = queue; - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; // Перед head - pos->valid = 1; - pos->version = queue->version; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue); - return pos; -} - -void queue_pos_free(struct ll_queue_pos* pos) { - if (!pos) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos); - free(pos); -} - -void queue_pos_reset(struct ll_queue_pos* pos) { - if (!pos) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL"); - return; - } - - // Обновить версию позиции при необходимости (вместо инвалидации) - if (pos->queue->version != pos->version) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: updating position version from %lu to %lu", - (unsigned long)pos->version, (unsigned long)pos->queue->version); - pos->version = pos->queue->version; - } - - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - pos->valid = 1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset"); -} - -int queue_pos_next(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: invalid position"); - return 0; - } - - if (pos->queue->version != pos->version) { - pos->valid = 0; - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: position invalidated due to queue version change"); - return 0; - } - - if (!pos->current) { - // Перед head - переходим к head - if (!pos->queue->head) { - // Очередь пуста - некуда переходить - return 0; - } - pos->current = pos->queue->head; - pos->prev = NULL; - pos->index = 0; - } else if (pos->current->next) { - // Есть следующий элемент - pos->prev = pos->current; - pos->current = pos->current->next; - pos->index++; - } else { - // Достигнут конец очереди - return 0; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_prev(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: invalid position"); - return 0; - } - - if (pos->queue->version != pos->version) { - pos->valid = 0; - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: position invalidated due to queue version change"); - return 0; - } - - if (!pos->current) { - // Уже перед head - дальше некуда - return 0; - } else if (pos->current->prev) { - // Есть предыдущий элемент - pos->current = pos->current->prev; - pos->prev = pos->current->prev; - pos->index--; - } else { - // Достигнуто начало очереди - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_seek(struct ll_queue_pos* pos, int target_index) { - if (!pos || !pos->valid) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid position"); - return 0; - } - - // Обновить версию позиции при необходимости (вместо инвалидации) - if (pos->queue->version != pos->version) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: updating position version from %lu to %lu", - (unsigned long)pos->version, (unsigned long)pos->queue->version); - pos->version = pos->queue->version; - // Сбросить позицию, так как структура очереди могла измениться - queue_pos_reset(pos); - } - - if (target_index < -1 || target_index >= pos->queue->count) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)", - target_index, pos->queue->count); - return 0; - } - - // Простая реализация: начинаем с начала и идем вперед - // Можно оптимизировать позже, если потребуется - queue_pos_reset(pos); - - for (int i = 0; i <= target_index; i++) { - if (!queue_pos_next(pos)) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index); - return 0; - } - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) { - if (!pos || !pos->valid || !entry) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters"); - return -1; - } - - if (pos->queue->version != pos->version) { - pos->valid = 0; - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: position invalidated due to queue version change"); - return -1; - } - - struct ll_queue* q = pos->queue; - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - entry->ref_count++; - - if (mode == 0) { - // Вставить перед current - if (!pos->current) { - // Вставить в head - entry->next = q->head; - entry->prev = NULL; - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - } else { - // Вставить перед current - entry->next = pos->current; - entry->prev = pos->current->prev; - - if (pos->current->prev) { - pos->current->prev->next = entry; - } else { - q->head = entry; // Вставляем в начало - } - pos->current->prev = entry; - } - } else { - // Вставить после current - if (!pos->current) { - // Вставить в head (current = NULL, значит перед head) - entry->next = q->head; - entry->prev = NULL; - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - } else { - // Вставить после current - entry->next = pos->current->next; - entry->prev = pos->current; - - if (pos->current->next) { - pos->current->next->prev = entry; - } else { - q->tail = entry; // Вставляем в конец - } - pos->current->next = entry; - } - } - - q->count++; - q->total_bytes += entry->size; - - // Обновить версию очереди и позиции (вместо полной инвалидации) - q->version++; - - // Обновить текущую позицию, если она активна - if (pos->current) { - pos->version = q->version; - pos->valid = 1; // Сделать позицию снова валидной - // Корректировка индекса в зависимости от режима вставки - if (mode == 0) { - // Вставка перед текущим - индекс остается тем же - // pos->index не меняется - } else { - // Вставка после текущего - текущий элемент смещается - pos->index++; - } - } - pos->valid = 0; // Текущая позиция теперь invalid - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d", - entry, mode, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} - -struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) { - if (!pos || !pos->valid || !pos->current) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element"); - return NULL; - } - - if (pos->queue->version != pos->version) { - pos->valid = 0; - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: position invalidated due to queue version change"); - return NULL; - } - - struct ll_queue* q = pos->queue; - struct ll_entry* entry = pos->current; - - // Удалить из двусвязного списка - if (entry->prev) { - entry->prev->next = entry->next; - } else { - q->head = entry->next; // Удаляем head - } - - if (entry->next) { - entry->next->prev = entry->prev; - } else { - q->tail = entry->prev; // Удаляем tail - } - - q->count--; - q->total_bytes -= entry->size; - - // Обновить позицию: перейти к следующему элементу - pos->current = entry->next; - pos->prev = entry->prev; - if (pos->current) { - // Остались элементы после удаленного - pos->index = pos->index; // Индекс остается тем же - } else { - // Удалили последний элемент - pos->index = q->count; // Указывает за конец - if (pos->index == 0) { - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - } - } - - entry->next = NULL; - entry->prev = NULL; - entry->ref_count--; // Уменьшить счетчик ссылок - - // Обновить версию очереди и позицию (вместо полной инвалидации) - q->version++; - if (pos->current) { - pos->version = q->version; - pos->valid = 1; // Сделать позицию снова валидной - // pos->index уже обновлен выше - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d", - entry, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return entry; -} - -int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters"); - return -1; - } - - // Проверить, что элемент действительно в этой очереди - struct ll_entry* curr = q->head; - while (curr) { - if (curr == entry) { - break; - } - curr = curr->next; - } - - if (!curr) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry); - return -1; - } - - // Удалить из двусвязного списка - if (entry->prev) { - entry->prev->next = entry->next; - } else { - q->head = entry->next; // Удаляем head - } - - if (entry->next) { - entry->next->prev = entry->prev; - } else { - q->tail = entry->prev; // Удаляем tail - } - - q->count--; - q->total_bytes -= entry->size; - - entry->next = NULL; - entry->prev = NULL; - entry->ref_count--; - - // Инвалидировать позиции - q->version++; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d", - entry, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} \ No newline at end of file diff --git a/lib/ll_queue.c3 b/lib/ll_queue.c3 deleted file mode 100755 index c39b5078..00000000 --- a/lib/ll_queue.c3 +++ /dev/null @@ -1,847 +0,0 @@ -// ll_queue.c -#include -#include -#include -#include -#include "ll_queue.h" -#include "u_async.h" -#include "debug_config.h" - -// Предварительное объявление для отложенного возобновления -static void queue_resume_timeout_cb(void* arg); - -// Проверить и запустить ожидающие коллбэки -static void check_waiters(struct ll_queue* q) { - if (!q || !q->waiters) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", - q->count, q->total_bytes); - - // Сначала собрать список коллбэков для вызова, чтобы избежать проблем с модификацией списка во время итерации - struct queue_waiter* to_call = NULL; - struct queue_waiter* to_call_tail = NULL; - - struct queue_waiter** pprev = &q->waiters; - struct queue_waiter* waiter = q->waiters; - - while (waiter) { - struct queue_waiter* next = waiter->next; - - // Проверить условие: не больше max_packets и не больше max_bytes - // max_bytes = 0 означает "не проверять байты" - if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)", - q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, - waiter->max_bytes == 0 ? "disabled" : "enabled"); - // Удалить waiter из списка - *pprev = next; - // Добавить в список для вызова - waiter->next = NULL; - if (to_call_tail) { - to_call_tail->next = waiter; - } else { - to_call = waiter; - } - to_call_tail = waiter; - } else { - // Условие не выполнено - оставить в списке - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)", - q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, - waiter->max_bytes == 0 ? "disabled" : "enabled"); - pprev = &waiter->next; - } - waiter = next; - } - - // Теперь вызвать коллбэки - waiter = to_call; - while (waiter) { - struct queue_waiter* next = waiter->next; - waiter->callback(q, waiter->callback_arg); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - waiter = next; - } -} - -// ==================== Унифицированная система версий позиций ==================== - -// Унифицированная проверка версии позиции с автоматическим пересканированием -static int queue_pos_ensure_valid(struct ll_queue_pos* pos) { - if (!pos || !pos->queue) return 0; - - // Если позиция уже валидна и версия совпадает, всё хорошо - if (pos->valid && pos->version == pos->queue->version) { - return 1; - } - - // Несовпадение версии или позиция невалидна - нужно пересканировать - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "Position version %lu != queue version %lu, rescanning...", - (unsigned long)pos->version, (unsigned long)pos->queue->version); - - // Сохранить целевой индекс (где мы хотим быть) - int target_index = pos->index; - - // Обновить версию позиции до текущей - pos->version = pos->queue->version; - - // Обработать краевой случай: индекс больше не существует из-за изменения размера очереди - if (target_index >= pos->queue->count) { - // Позиция выходит за пределы очереди - инвалидируем её - pos->valid = 0; - return 0; - } - - // Простой подход: сбросить и перейти к тому же индексу - // Это найдёт элемент, который теперь находится в целевом индексе - queue_pos_reset(pos); - return queue_pos_seek(pos, target_index); -} - -// ==================== Управление очередью ==================== - -struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) { - if (!ua) return NULL; - - struct ll_queue* q = calloc(1, sizeof(struct ll_queue)); - if (!q) return NULL; - - q->head = NULL; - q->tail = NULL; - q->count = 0; - q->total_bytes = 0; - q->size_limit = -1; // По умолчанию без ограничения - q->callback = NULL; - q->callback_arg = NULL; - q->callback_suspended = 0; // Коллбэки разрешены изначально - q->resume_timeout_id = NULL; - q->ua = ua; - q->waiters = NULL; - q->pool=pool; - q->version = 0; - - return q; -} - -void queue_free(struct ll_queue* q) { - if (!q) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d", - q, q->head, q->tail, q->count); - - // Освободить все элементы (уменьшить счетчик ссылок) - struct ll_entry* entry = q->head; - int entry_count = 0; - while (entry) { - struct ll_entry* next = entry->next; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d", - entry, entry_count++, entry->ref_count); - entry->ref_count--; // Дополнительно уменьшить за присутствие в очереди - queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0 - entry = next; - } - - // Освободить все ожидающие коллбэки - struct queue_waiter* waiter = q->waiters; - int waiter_count = 0; - while (waiter) { - struct queue_waiter* next = waiter->next; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - waiter = next; - } - - // Отменить отложенное возобновление если запланировано - if (q->resume_timeout_id) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id); - uasync_cancel_timeout(q->ua, q->resume_timeout_id); - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q); - free(q); -} - -// ==================== Конфигурация очереди ==================== - -void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) { - if (!q) return; - q->callback = cbk_fn; - q->callback_arg = arg; -} - -static void queue_resume_timeout_cb(void* arg) { - struct ll_queue* q = (struct ll_queue*)arg; - if (!q || !q->callback) return; - - // Очистить ID таймаута (таймаут сработал) - q->resume_timeout_id = NULL; - - // Разрешить коллбэки - q->callback_suspended = 0; - - // Если в очереди есть элементы, вызвать коллбэк с первым элементом - // Обработчик должен извлечь этот элемент вызовом queue_entry_get() - if (q->head) { - q->callback(q, q->head, q->callback_arg); - } -} - -void queue_resume_callback(struct ll_queue* q) { - if (!q || !q->callback) return; - - // Если уже есть отложенное возобновление, ничего не делать - if (q->resume_timeout_id) { - return; - } - - // Запланировать отложенное возобновление через uasync - q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb); -} - -void queue_set_size_limit(struct ll_queue* q, int lim) { - if (!q) return; - q->size_limit = lim; -} - -// ==================== Управление элементами ==================== - -struct ll_entry* queue_entry_new(size_t data_size) { - // Выделить память под структуру + область данных - struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size); - if (!entry) return NULL; - - entry->next = NULL; - entry->prev = NULL; // Инициализировать prev для двусвязного списка - entry->size = data_size; - entry->ref_count = 1; // Начальный счетчик ссылок - // Область данных оставить неинициализированной для производительности - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d", - entry, data_size, entry->ref_count); - - return entry; -} - -void queue_entry_free(struct ll_entry* entry) { - if (!entry) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d", - entry, entry->ref_count); - - if (entry->ref_count <= 0) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d", - entry, entry->ref_count); - return; // Предотвратить double-free - } - - entry->ref_count--; - if (entry->ref_count == 0) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry); - free(entry); - } else { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p", - entry->ref_count, entry); - } -} - -// ==================== Операции с очередью ==================== - -int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) return -1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d", - entry, entry->size, q->count); - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - // Увеличить счетчик ссылок при добавлении в очередь - entry->ref_count++; - - // Добавить в хвост (FIFO) - двусвязный список - entry->next = NULL; - entry->prev = q->tail; - - if (q->tail) { - q->tail->next = entry; - } else { - q->head = entry; // Очередь была пустой - } - q->tail = entry; - q->count++; - q->total_bytes += entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", - entry, q->count, q->total_bytes, entry->ref_count); - - // Инвалидировать позиции - q->version++; - - // Если коллбэки разрешены - вызвать коллбэк - // Это запускает автоматическую обработку очереди - if (!q->callback_suspended && q->callback) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry); - - // Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию - q->callback_suspended = 1; - q->callback(q, q->head, q->callback_arg); - // Не восстанавливать здесь - восстановление происходит через queue_resume_callback - } - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} - -int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) return -1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d", - entry, entry->size, q->count); - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - // Увеличить счетчик ссылок при добавлении в очередь - entry->ref_count++; - - // Добавить в голову (LIFO, высокий приоритет) - двусвязный список - entry->next = q->head; - entry->prev = NULL; - - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - q->count++; - q->total_bytes += entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", - entry, q->count, q->total_bytes, entry->ref_count); - - // Инвалидировать позиции - q->version++; - - // Если коллбэки разрешены - вызвать коллбэк - if (!q->callback_suspended && q->callback) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry); - q->callback_suspended = 1; - q->callback(q, q->head, q->callback_arg); - } - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} - -struct ll_entry* queue_entry_get(struct ll_queue* q) { - if (!q || !q->head) return NULL; - - struct ll_entry* entry = q->head; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d", - entry, entry->size, entry->ref_count); - - // Удалить из двусвязного списка - q->head = entry->next; - if (q->head) { - q->head->prev = NULL; - } else { - q->tail = NULL; // Очередь стала пустой - } - q->count--; - q->total_bytes -= entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu", - entry, q->count, q->total_bytes); - - entry->next = NULL; - entry->prev = NULL; // Полностью отсоединить от очереди - - // Уменьшить счетчик ссылок при извлечении из очереди - // entry->ref_count был увеличен при добавлении в очередь - // теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать - entry->ref_count--; - - // Инвалидировать позиции - q->version++; - - // При извлечении элемента приостанавливаем коллбэки - // Это предотвращает рекурсию если во время обработки добавляются новые элементы - q->callback_suspended = 1; - - // Проверить ожидающие коллбэки при извлечении элемента - // Это важно для waiters, которые ожидают уменьшения очереди - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count); - check_waiters(q); - - return entry; -} - -int queue_entry_count(struct ll_queue* q) { - if (!q) return 0; - return q->count; -} - -// ==================== Асинхронное ожидание ==================== - -struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, - queue_threshold_callback_fn callback, void* arg) { - if (!q || !callback) return NULL; - - // Создать новый waiter - struct queue_waiter* waiter; - if (q->pool) { - waiter = (struct queue_waiter*)memory_pool_alloc(q->pool); - } else { - waiter = malloc(sizeof(struct queue_waiter)); - } - if (!waiter) return NULL; - - waiter->max_packets = max_packets; - waiter->max_bytes = max_bytes; - waiter->callback = callback; - waiter->callback_arg = arg; - waiter->next = NULL; - - // Проверить условие немедленно - if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) { - // Условие уже выполнено - вызвать коллбэк и освободить waiter - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback", - q->count, max_packets, q->total_bytes, max_bytes); - callback(q, arg); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - return NULL; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu", - q->count, q->total_bytes, max_packets, max_bytes); - - // Добавить в список ожидающих - waiter->next = q->waiters; - q->waiters = waiter; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully"); - return waiter; -} - -void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) { - if (!q || !waiter) return; - - // Найти и удалить waiter из списка - struct queue_waiter** pprev = &q->waiters; - struct queue_waiter* w = q->waiters; - - while (w) { - if (w == waiter) { - *pprev = w->next; - if (q->pool) { - memory_pool_free(q->pool, w); - } else { - free(w); - } - return; - } - pprev = &w->next; - w = w->next; - } -} - -// ==================== Статистика и метрики ==================== - -void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) { - if (!q || !q->pool) { - if (waiter_allocations) *waiter_allocations = 0; - if (waiter_reuse) *waiter_reuse = 0; - return; - } - - memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse); -} - -// ==================== Управление позициями ==================== - -struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) { - if (!queue) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL"); - return NULL; - } - - struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos)); - if (!pos) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position"); - return NULL; - } - - pos->queue = queue; - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; // Перед head - pos->valid = 1; - pos->version = queue->version; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue); - return pos; -} - -void queue_pos_free(struct ll_queue_pos* pos) { - if (!pos) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos); - free(pos); -} - -void queue_pos_reset(struct ll_queue_pos* pos) { - if (!pos) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL"); - return; - } - - // Обновить версию позиции при необходимости (вместо инвалидации) - if (pos->queue->version != pos->version) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: updating position version from %lu to %lu", - (unsigned long)pos->version, (unsigned long)pos->queue->version); - pos->version = pos->queue->version; - } - - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - pos->valid = 1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset"); -} - -int queue_pos_next(struct ll_queue_pos* pos) { - if (!pos) return 0; - - // Убедиться, что позиция валидна (автоматическое обновление при необходимости) - if (!queue_pos_ensure_valid(pos)) { - return 0; - } - - if (!pos->current) { - // Перед head - переходим к head - if (!pos->queue->head) { - // Очередь пуста - некуда переходить - return 0; - } - pos->current = pos->queue->head; - pos->prev = NULL; - pos->index = 0; - } else if (pos->current->next) { - // Есть следующий элемент - pos->prev = pos->current; - pos->current = pos->current->next; - pos->index++; - } else { - // Достигнут конец очереди - return 0; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_prev(struct ll_queue_pos* pos) { - if (!pos) return 0; - - // Убедиться, что позиция валидна (автоматическое обновление при необходимости) - if (!queue_pos_ensure_valid(pos)) { - return 0; - } - - if (!pos->current) { - // Уже перед head - дальше некуда - return 0; - } else if (pos->current->prev) { - // Есть предыдущий элемент - pos->current = pos->current->prev; - pos->prev = pos->current->prev; - pos->index--; - } else { - // Достигнуто начало очереди - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_seek(struct ll_queue_pos* pos, int target_index) { - if (!pos) return 0; - - // Убедиться, что позиция валидна (автоматическое обновление при необходимости) - if (!queue_pos_ensure_valid(pos)) { - return 0; - } - - if (target_index < -1 || target_index >= pos->queue->count) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)", - target_index, pos->queue->count); - return 0; - } - - // Простая реализация: начинаем с начала и идем вперед - // Можно оптимизировать позже, если потребуется - queue_pos_reset(pos); - - for (int i = 0; i <= target_index; i++) { - if (!queue_pos_next(pos)) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index); - return 0; - } - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) { - if (!pos || !pos->valid || !entry) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters"); - return -1; - } - - if (pos->queue->version != pos->version) { - pos->valid = 0; - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: position invalidated due to queue version change"); - return -1; - } - - struct ll_queue* q = pos->queue; - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - entry->ref_count++; - - if (mode == 0) { - // Вставить перед current - if (!pos->current) { - // Вставить в head - entry->next = q->head; - entry->prev = NULL; - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - } else { - // Вставить перед current - entry->next = pos->current; - entry->prev = pos->current->prev; - - if (pos->current->prev) { - pos->current->prev->next = entry; - } else { - q->head = entry; // Вставляем в начало - } - pos->current->prev = entry; - } - } else { - // Вставить после current - if (!pos->current) { - // Вставить в head (current = NULL, значит перед head) - entry->next = q->head; - entry->prev = NULL; - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - } else { - // Вставить после current - entry->next = pos->current->next; - entry->prev = pos->current; - - if (pos->current->next) { - pos->current->next->prev = entry; - } else { - q->tail = entry; // Вставляем в конец - } - pos->current->next = entry; - } - } - - q->count++; - q->total_bytes += entry->size; - - // Обновить версию очереди и позиции (вместо полной инвалидации) - q->version++; - - // Обновить текущую позицию, если она активна - if (pos->current) { - pos->version = q->version; - pos->valid = 1; // Сделать позицию снова валидной - // Корректировка индекса в зависимости от режима вставки - if (mode == 0) { - // Вставка перед текущим - индекс остается тем же - // pos->index не меняется - } else { - // Вставка после текущего - текущий элемент смещается - pos->index++; - } - } - // Позиция остается валидной - убрали строку pos->valid = 0; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d", - entry, mode, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} - -struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) { - if (!pos || !pos->valid || !pos->current) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element"); - return NULL; - } - - // Убедиться, что позиция валидна (автоматическое обновление при необходимости) - if (!queue_pos_ensure_valid(pos)) { - return NULL; - } - - struct ll_queue* q = pos->queue; - struct ll_entry* entry = pos->current; - - // Удалить из двусвязного списка - if (entry->prev) { - entry->prev->next = entry->next; - } else { - q->head = entry->next; // Удаляем head - } - - if (entry->next) { - entry->next->prev = entry->prev; - } else { - q->tail = entry->prev; // Удаляем tail - } - - q->count--; - q->total_bytes -= entry->size; - - // Обновить позицию: перейти к следующему элементу - pos->current = entry->next; - pos->prev = entry->prev; - if (pos->current) { - // Остались элементы после удаленного - pos->index = pos->index; // Индекс остается тем же - } else { - // Удалили последний элемент - pos->index = q->count; // Указывает за конец - if (pos->index == 0) { - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - } - } - - entry->next = NULL; - entry->prev = NULL; - entry->ref_count--; // Уменьшить счетчик ссылок - - // Обновить версию очереди и позицию (вместо полной инвалидации) - q->version++; - if (pos->current) { - pos->version = q->version; - pos->valid = 1; // Сделать позицию снова валидной - // pos->index уже обновлен выше - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d", - entry, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return entry; -} - -int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters"); - return -1; - } - - // Проверить, что элемент действительно в этой очереди - struct ll_entry* curr = q->head; - while (curr) { - if (curr == entry) { - break; - } - curr = curr->next; - } - - if (!curr) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry); - return -1; - } - - // Удалить из двусвязного списка - if (entry->prev) { - entry->prev->next = entry->next; - } else { - q->head = entry->next; // Удаляем head - } - - if (entry->next) { - entry->next->prev = entry->prev; - } else { - q->tail = entry->prev; // Удаляем tail - } - - q->count--; - q->total_bytes -= entry->size; - - entry->next = NULL; - entry->prev = NULL; - entry->ref_count--; - - // Инвалидировать позиции - q->version++; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d", - entry, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} \ No newline at end of file diff --git a/lib/ll_queue.c_ b/lib/ll_queue.c_ deleted file mode 100755 index 67816967..00000000 --- a/lib/ll_queue.c_ +++ /dev/null @@ -1,744 +0,0 @@ -#include -#include -#include -#include -#include "ll_queue.h" -#include "u_async.h" -#include "debug_config.h" - -// Предварительное объявление для отложенного возобновления -static void queue_resume_timeout_cb(void* arg); - -// Вспомогательные функции для двусвязного списка -static void link_entry_after(struct ll_entry* existing, struct ll_entry* new_entry) { - if (!existing || !new_entry) return; - - new_entry->next = existing->next; - new_entry->prev = existing; - - if (existing->next) { - existing->next->prev = new_entry; - } - existing->next = new_entry; -} - -static void unlink_entry(struct ll_entry* entry) { - if (!entry) return; - - if (entry->prev) entry->prev->next = entry->next; - if (entry->next) entry->next->prev = entry->prev; - entry->next = NULL; - entry->prev = NULL; -} - -// Проверить и запустить ожидающие коллбэки -static void check_waiters(struct ll_queue* q) { - if (!q || !q->waiters) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", - q->count, q->total_bytes); - - struct queue_waiter** pprev = &q->waiters; - struct queue_waiter* waiter = q->waiters; - - while (waiter) { - struct queue_waiter* next = waiter->next; - - // Проверить условие: не больше max_packets и не больше max_bytes - // max_bytes = 0 означает "не проверять байты" - if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)", - q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, - waiter->max_bytes == 0 ? "disabled" : "enabled"); - waiter->callback(q, waiter->callback_arg); - // Удалить waiter из списка - *pprev = next; - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - // pprev уже указывает на правильный следующий элемент - } else { - // Условие не выполнено - оставить в списке - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition NOT met, count=%d>%d or bytes=%zu>%zu (max_bytes_check=%s)", - q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes, - waiter->max_bytes == 0 ? "disabled" : "enabled"); - pprev = &waiter->next; - } - waiter = next; - } -} - -// ==================== Управление очередью ==================== - -struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool) { - struct ll_queue* q = calloc(1, sizeof(struct ll_queue)); - if (!q) return NULL; - - q->head = NULL; - q->tail = NULL; - q->count = 0; - q->total_bytes = 0; - q->size_limit = -1; // По умолчанию без ограничения - q->callback = NULL; - q->callback_arg = NULL; - q->callback_suspended = 0; // Коллбэки разрешены изначально - q->resume_timeout_id = NULL; - q->ua = ua; - q->waiters = NULL; - q->pool=pool; - - return q; -} - -void queue_free(struct ll_queue* q) { - if (!q) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue %p, head=%p, tail=%p, count=%d", - q, q->head, q->tail, q->count); - - // Освободить все элементы (уменьшить счетчик ссылок) - struct ll_entry* entry = q->head; - int entry_count = 0; - while (entry) { - struct ll_entry* next = entry->next; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: releasing entry %p (entry %d), ref_count=%d", - entry, entry_count++, entry->ref_count); - queue_entry_free(entry); // Это уменьшит ref_count и освободит только если ref_count == 0 - entry = next; - } - - // Освободить все ожидающие коллбэки - struct queue_waiter* waiter = q->waiters; - int waiter_count = 0; - while (waiter) { - struct queue_waiter* next = waiter->next; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing waiter %p (waiter %d)", waiter, waiter_count++); - if (q->pool) { - memory_pool_free(q->pool, waiter); - } else { - free(waiter); - } - waiter = next; - } - - // Отменить отложенное возобновление если запланировано - if (q->resume_timeout_id) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: cancelling resume timeout %p", q->resume_timeout_id); - uasync_cancel_timeout(q->ua, q->resume_timeout_id); - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_free: freeing queue structure %p", q); - free(q); -} - -// ==================== Конфигурация очереди ==================== - -void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) { - if (!q) return; - q->callback = cbk_fn; - q->callback_arg = arg; -} - -static void queue_resume_timeout_cb(void* arg) { - struct ll_queue* q = (struct ll_queue*)arg; - if (!q || !q->callback) return; - - // Очистить ID таймаута (таймаут сработал) - q->resume_timeout_id = NULL; - - // Разрешить коллбэки - q->callback_suspended = 0; - - // Если в очереди есть элементы, вызвать коллбэк с первым элементом - // Обработчик должен извлечь этот элемент вызовом queue_entry_get() - if (q->head) { - q->callback(q, q->head, q->callback_arg); - } -} - -void queue_resume_callback(struct ll_queue* q) { - if (!q || !q->callback) return; - - // Если уже есть отложенное возобновление, ничего не делать - if (q->resume_timeout_id) { - return; - } - - // Запланировать отложенное возобновление через uasync - q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb); -} - -void queue_set_size_limit(struct ll_queue* q, int lim) { - if (!q) return; - q->size_limit = lim; -} - -// ==================== Управление элементами ==================== - -struct ll_entry* queue_entry_new(size_t data_size) { - // Выделить память под структуру + область данных - struct ll_entry* entry = malloc(sizeof(struct ll_entry) + data_size); - if (!entry) return NULL; - - entry->next = NULL; - entry->prev = NULL; // Инициализировать prev для двусвязного списка - entry->size = data_size; - entry->ref_count = 1; // Начальный счетчик ссылок - // Область данных оставить неинициализированной для производительности - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_new: created entry %p, size=%zu, ref_count=%d", - entry, data_size, entry->ref_count); - - return entry; -} - -void queue_entry_free(struct ll_entry* entry) { - if (!entry) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry=%p, ref_count=%d", - entry, entry->ref_count); - - if (entry->ref_count <= 0) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: entry %p has invalid ref_count=%d", - entry, entry->ref_count); - return; // Предотвратить double-free - } - - entry->ref_count--; - if (entry->ref_count == 0) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: actually freeing entry %p", entry); - free(entry); - } else { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_free: decremented ref_count to %d for entry %p", - entry->ref_count, entry); - } -} - -// ==================== Операции с очередью ==================== - -int queue_entry_put(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) return -1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: entry=%p, size=%zu, count=%d", - entry, entry->size, q->count); - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - // Увеличить счетчик ссылок при добавлении в очередь - entry->ref_count++; - - // Добавить в хвост (FIFO) - двусвязный список - entry->next = NULL; - entry->prev = q->tail; - - if (q->tail) { - q->tail->next = entry; - } else { - q->head = entry; // Очередь была пустой - } - q->tail = entry; - q->count++; - q->total_bytes += entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", - entry, q->count, q->total_bytes, entry->ref_count); - - // Если коллбэки разрешены - вызвать коллбэк - // Это запускает автоматическую обработку очереди - if (!q->callback_suspended && q->callback) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put: calling callback for entry %p", entry); - - // Приостановить коллбэки во время выполнения коллбэка, чтобы предотвратить рекурсию - q->callback_suspended = 1; - q->callback(q, entry, q->callback_arg); - // Не восстанавливать здесь - восстановление происходит через queue_resume_callback - } - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} - -int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) return -1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: entry=%p, size=%zu, count=%d", - entry, entry->size, q->count); - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - // Увеличить счетчик ссылок при добавлении в очередь - entry->ref_count++; - - // Добавить в голову (LIFO, высокий приоритет) - двусвязный список - entry->next = q->head; - entry->prev = NULL; - - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - q->count++; - q->total_bytes += entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: added entry %p, new count=%d, total_bytes=%zu, ref_count=%d", - entry, q->count, q->total_bytes, entry->ref_count); - - // Если коллбэки разрешены - вызвать коллбэк - if (!q->callback_suspended && q->callback) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_put_first: calling callback for entry %p", entry); - q->callback(q, entry, q->callback_arg); - } - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} - -struct ll_entry* queue_entry_get(struct ll_queue* q) { - if (!q || !q->head) return NULL; - - struct ll_entry* entry = q->head; - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: retrieving entry %p, size=%zu, ref_count=%d", - entry, entry->size, entry->ref_count); - - // Удалить из двусвязного списка - q->head = entry->next; - if (q->head) { - q->head->prev = NULL; - } else { - q->tail = NULL; // Очередь стала пустой - } - q->count--; - q->total_bytes -= entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: removed entry %p, new count=%d, total_bytes=%zu", - entry, q->count, q->total_bytes); - - entry->next = NULL; - entry->prev = NULL; // Полностью отсоединить от очереди - - // Уменьшить счетчик ссылок при извлечении из очереди - // entry->ref_count был увеличен при добавлении в очередь - // теперь уменьшаем, но не освобождаем, так как вызывающий код должен это сделать - entry->ref_count--; - - // При извлечении элемента приостанавливаем коллбэки - // Это предотвращает рекурсию если во время обработки добавляются новые элементы - q->callback_suspended = 1; - - // Проверить ожидающие коллбэки при извлечении элемента - // Это важно для waiters, которые ожидают уменьшения очереди - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_get: about to call check_waiters, count=%d", q->count); - check_waiters(q); - - return entry; -} - -int queue_entry_count(struct ll_queue* q) { - if (!q) return 0; - return q->count; -} - -// ==================== Асинхронное ожидание ==================== - -struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, - queue_threshold_callback_fn callback, void* arg) { - if (!q || !callback) return NULL; - - // Создать новый waiter - struct queue_waiter* waiter; - if (q->pool) { - waiter = (struct queue_waiter*)memory_pool_alloc(q->pool); - } else { - waiter = malloc(sizeof(struct queue_waiter)); - } - if (!waiter) return NULL; - - waiter->max_packets = max_packets; - waiter->max_bytes = max_bytes; - waiter->callback = callback; - waiter->callback_arg = arg; - waiter->next = NULL; - - // Проверить условие немедленно - if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) { - // Условие уже выполнено - вызвать коллбэк и освободить waiter - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: condition already met, count=%d<=%d, bytes=%zu<=%zu, calling callback", - q->count, max_packets, q->total_bytes, max_bytes); - callback(q, arg); - free(waiter); - return NULL; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: registering waiter, count=%d, bytes=%zu, max_packets=%d, max_bytes=%zu", - q->count, q->total_bytes, max_packets, max_bytes); - - // Добавить в список ожидающих - waiter->next = q->waiters; - q->waiters = waiter; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_wait_threshold: waiter registered successfully"); - return waiter; -} - -void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) { - if (!q || !waiter) return; - - // Найти и удалить waiter из списка - struct queue_waiter** pprev = &q->waiters; - struct queue_waiter* w = q->waiters; - - while (w) { - if (w == waiter) { - *pprev = w->next; - if (q->pool) { - memory_pool_free(q->pool, w); - } else { - free(w); - } - return; - } - pprev = &w->next; - w = w->next; - } -} - -// ==================== Статистика и метрики ==================== - -void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse) { - if (!q || !q->pool) { - if (waiter_allocations) *waiter_allocations = 0; - if (waiter_reuse) *waiter_reuse = 0; - return; - } - - memory_pool_get_stats(q->pool, waiter_allocations, waiter_reuse); -} - -// ==================== Управление позициями ==================== - -struct ll_queue_pos* queue_pos_new(struct ll_queue* queue) { - if (!queue) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: queue is NULL"); - return NULL; - } - - struct ll_queue_pos* pos = malloc(sizeof(struct ll_queue_pos)); - if (!pos) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: failed to allocate position"); - return NULL; - } - - pos->queue = queue; - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; // Перед head - pos->valid = 1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_new: created position %p for queue %p", pos, queue); - return pos; -} - -void queue_pos_free(struct ll_queue_pos* pos) { - if (!pos) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_free: freeing position %p", pos); - free(pos); -} - -void queue_pos_reset(struct ll_queue_pos* pos) { - if (!pos) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: pos is NULL"); - return; - } - - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - pos->valid = 1; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_reset: position reset"); -} - -int queue_pos_next(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: invalid position"); - return 0; - } - - if (!pos->current) { - // Перед head - переходим к head - if (!pos->queue->head) { - // Очередь пуста - некуда переходить - return 0; - } - pos->current = pos->queue->head; - pos->prev = NULL; - pos->index = 0; - } else if (pos->current->next) { - // Есть следующий элемент - pos->prev = pos->current; - pos->current = pos->current->next; - pos->index++; - } else { - // Достигнут конец очереди - return 0; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_next: moved to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_prev(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: invalid position"); - return 0; - } - - if (!pos->current) { - // Уже перед head - дальше некуда - return 0; - } else if (pos->current->prev) { - // Есть предыдущий элемент - pos->current = pos->current->prev; - pos->prev = pos->current->prev; - pos->index--; - } else { - // Достигнуто начало очереди - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_prev: moved to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_seek(struct ll_queue_pos* pos, int target_index) { - if (!pos || !pos->valid) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid position"); - return 0; - } - - if (target_index < -1 || target_index >= pos->queue->count) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: invalid target index %d (count=%d)", - target_index, pos->queue->count); - return 0; - } - - // Простая реализация: начинаем с начала и идем вперед - // Можно оптимизировать позже, если потребуется - queue_pos_reset(pos); - - for (int i = 0; i <= target_index; i++) { - if (!queue_pos_next(pos)) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: failed to reach index %d", target_index); - return 0; - } - } - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_seek: seeked to index %d, entry %p", - pos->index, pos->current); - return 1; -} - -int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode) { - if (!pos || !pos->valid || !entry) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: invalid parameters"); - return -1; - } - - struct ll_queue* q = pos->queue; - - // Проверить лимит размера - if (q->size_limit >= 0 && q->count >= q->size_limit) { - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: size limit exceeded, freeing entry %p", entry); - queue_entry_free(entry); - return -1; - } - - entry->ref_count++; - - if (mode == 0) { - // Вставить перед current - if (!pos->current) { - // Вставить в head - entry->next = q->head; - entry->prev = NULL; - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - } else { - // Вставить перед current - entry->next = pos->current; - entry->prev = pos->current->prev; - - if (pos->current->prev) { - pos->current->prev->next = entry; - } else { - q->head = entry; // Вставляем в начало - } - pos->current->prev = entry; - } - } else { - // Вставить после current - if (!pos->current) { - // Вставить в head (current = NULL, значит перед head) - entry->next = q->head; - entry->prev = NULL; - if (q->head) { - q->head->prev = entry; - } else { - q->tail = entry; // Очередь была пустой - } - q->head = entry; - } else { - // Вставить после current - entry->next = pos->current->next; - entry->prev = pos->current; - - if (pos->current->next) { - pos->current->next->prev = entry; - } else { - q->tail = entry; // Вставляем в конец - } - pos->current->next = entry; - } - } - - q->count++; - q->total_bytes += entry->size; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_insert: inserted entry %p, mode=%d, count=%d", - entry, mode, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} - -struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos) { - if (!pos || !pos->valid || !pos->current) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: invalid position or no current element"); - return NULL; - } - - struct ll_queue* q = pos->queue; - struct ll_entry* entry = pos->current; - - // Удалить из двусвязного списка - if (entry->prev) { - entry->prev->next = entry->next; - } else { - q->head = entry->next; // Удаляем head - } - - if (entry->next) { - entry->next->prev = entry->prev; - } else { - q->tail = entry->prev; // Удаляем tail - } - - q->count--; - q->total_bytes -= entry->size; - - // Обновить позицию: перейти к следующему элементу - pos->current = entry->next; - pos->prev = entry->prev; - if (pos->current) { - // Остались элементы после удаленного - pos->index = pos->index; // Индекс остается тем же - } else { - // Удалили последний элемент - pos->index = q->count; // Указывает за конец - if (pos->index == 0) { - pos->current = NULL; - pos->prev = NULL; - pos->index = -1; - } - } - - entry->next = NULL; - entry->prev = NULL; - entry->ref_count--; // Уменьшить счетчик ссылок - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_pos_remove: removed entry %p, new count=%d", - entry, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return entry; -} - -int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry) { - if (!q || !entry) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: invalid parameters"); - return -1; - } - - // Проверить, что элемент действительно в этой очереди - struct ll_entry* curr = q->head; - while (curr) { - if (curr == entry) { - break; - } - curr = curr->next; - } - - if (!curr) { - DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: entry %p not found in queue", entry); - return -1; - } - - // Удалить из двусвязного списка - if (entry->prev) { - entry->prev->next = entry->next; - } else { - q->head = entry->next; // Удаляем head - } - - if (entry->next) { - entry->next->prev = entry->prev; - } else { - q->tail = entry->prev; // Удаляем tail - } - - q->count--; - q->total_bytes -= entry->size; - - entry->next = NULL; - entry->prev = NULL; - entry->ref_count--; - - DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "queue_entry_remove: removed entry %p, new count=%d", - entry, q->count); - - // Проверить ожидающие коллбэки - check_waiters(q); - - return 0; -} diff --git a/lib/ll_queue.h b/lib/ll_queue.h index 408e2c59..0b9bec7b 100644 --- a/lib/ll_queue.h +++ b/lib/ll_queue.h @@ -1,4 +1,4 @@ -// ll_queue.h +// ll_queue.h - Упрощенная архитектура: разделение создания элементов и работы с очередью #ifndef LL_QUEUE_H #define LL_QUEUE_H @@ -11,27 +11,31 @@ struct ll_queue; struct ll_entry; struct queue_waiter; struct memory_pool; -struct ll_queue_pos; // Управляющая структура позиции +struct UASYNC; -// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки -// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент -typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg); +// Автозабор элемента из очереди. вызывается когда в очереди что-то есть и коллбэк не занят обработкой. +// Параметры: указатель на очередь, указатель на пользовательские данные, пользовательский аргумент +// Когда коллбэк закончит обрабатывать элемент он должен вызвать queue_resume_callback - сообщить о готовности получить следующий элемент (приём очередным вызовом коллбэка в следующем цикле mainloop). +typedef void (*queue_callback_fn)(struct ll_queue* q, void* data, void* arg); // Структура элемента - переменный размер, данные расположены сразу после структуры struct ll_entry { struct ll_entry* next; // Указатель на следующий элемент в очереди struct ll_entry* prev; // Указатель на предыдущий элемент в очереди size_t size; // Размер данных элемента (байт) - int ref_count; // Счетчик ссылок для предотвращения double-free + int ref_count; // Счетчик ссылок (1 при создании, не изменяется при помещении/изъятии из очереди) + uint32_t id; // Идентификатор для хеш-поиска + struct ll_entry* hash_next; // Следующий в хеш-цепочке + struct memory_pool* pool; // Пул, из которого выделен этот элемент (NULL, если выделен через malloc) + uint8_t payload[0]; }; -// Структура условия ожидания (waiter) +// Ожидающий добавления элементов в очередь (пока очередь не освободться до нужного размера чтобы не забивать) struct queue_waiter { int max_packets; // Максимальное количество пакетов size_t max_bytes; // Максимальное количество байт (0 = не проверять байты) void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова void* callback_arg; // Аргумент коллбэка - struct queue_waiter* next; // Следующий ожидающий в списке }; typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg); @@ -46,29 +50,29 @@ struct ll_queue { queue_callback_fn callback; // Функция коллбэка void* callback_arg; // Пользовательский аргумент для коллбэка - int callback_suspended; // 1 если коллбэки приостановлены (во время обработки) + int callback_suspended; void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления struct UASYNC* ua; // Экземпляр uasync для таймеров - struct queue_waiter* waiters; // Список ожидающих коллбэков + struct queue_waiter waiter; // Встроенный одиночный waiter - // Пулы памяти для оптимизации аллокаций - struct memory_pool* pool; // Пул для структур struct queue_waiter - - uint64_t version; // Версия для инвалидации позиций + // Хеш-таблица для быстрого поиска по ID + struct ll_entry** hash_table; + size_t hash_size; }; // ==================== Управление очередью ==================== // Создать новую пустую очередь // ua - экземпляр uasync для таймеров (обязательный параметр) -// pool - если не null то использовать этот пул памяти -// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h) -struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool); +// hash_size - размер хеш-таблицы для быстрого поиска (0 = без хеш-таблицы) +// Возвращает: указатель на очередь или NULL при ошибке выделения памяти +struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size); // Освободить очередь и все её элементы -// Также отменяет отложенное возобновление если оно запланировано +// ВАЖНО: освобождает только структуру очереди, элементы в очереди НЕ освобождаются! +// Элементы должны быть предварительно извлечены через queue_data_get() и освобождены через queue_data_free() void queue_free(struct ll_queue* q); // ==================== Конфигурация очереди ==================== @@ -92,53 +96,49 @@ void queue_set_size_limit(struct ll_queue* q, int lim); // Создать новый элемент с областью данных указанного размера // Память выделяется одним блоком: [struct ll_entry][область данных data_size байт] -// Возвращает: указатель на элемент или NULL при ошибке выделения памяти -struct ll_entry* queue_entry_new(size_t data_size); +// Возвращает: указатель на пользовательские данные или NULL при ошибке выделения памяти +// ПРИМЕЧАНИЕ: ref_count = 1, элемент не в очереди +void* queue_data_new(size_t data_size); + +// Создать новый элемент из пула (размер был определен при создании пула) +// Возвращает: указатель на пользовательские данные или NULL при ошибке выделения памяти +// ПРИМЕЧАНИЕ: ref_count = 1, элемент не в очереди +void* queue_data_new_from_pool(struct memory_pool* pool); -// Освободить элемент (не влияет на связи в очереди) -void queue_entry_free(struct ll_entry* entry); +// Освободить элемент (не влияет на очереди - должен быть предварительно взят из всех очередей) +// Уменьшает ref_count, если становится 0 - освобождает память +void queue_data_free(void* data); // ==================== Операции с очередью ==================== // Добавить элемент в конец очереди (FIFO) // Если очередь была пустой и коллбэки разрешены - вызывает коллбэк // Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) -int queue_entry_put(struct ll_queue* q, struct ll_entry* entry); +// ПРИМЕЧАНИЕ: НЕ изменяет ref_count элемента +int queue_data_put(struct ll_queue* q, void* data, uint32_t id); // Добавить элемент в начало очереди (LIFO, высокий приоритет) // Если очередь была пустой и коллбэки разрешены - вызывает коллбэк // Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) -int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry); +// ПРИМЕЧАНИЕ: НЕ изменяет ref_count элемента +int queue_data_put_first(struct ll_queue* q, void* data, uint32_t id); // Извлечь элемент из начала очереди // При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию -// Возвращает: указатель на элемент или NULL если очередь пуста -struct ll_entry* queue_entry_get(struct ll_queue* q); +// Возвращает: указатель на пользовательские данные или NULL если очередь пуста +// ПРИМЕЧАНИЕ: НЕ изменяет ref_count элемента, просто удаляет из очереди +void* queue_data_get(struct ll_queue* q); // Получить текущее количество элементов в очереди int queue_entry_count(struct ll_queue* q); // ==================== Вспомогательные функции ==================== -// Получить указатель на область данных элемента -// Данные расположены сразу после структуры struct ll_entry -static inline void* ll_entry_data(struct ll_entry* entry) { - if (!entry) return NULL; - return (void*)(entry + 1); -} - -// Получить размер данных элемента -static inline size_t ll_entry_size(struct ll_entry* entry) { - if (!entry) return 0; - return entry->size; -} - // ==================== Асинхронное ожидание ==================== // Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь // не более max_packets пакетов и не более max_bytes байт (если max_bytes != 0). // Если условие уже выполнено, коллбэк вызывается немедленно. -// Можно зарегистрировать несколько ожиданий на одной очереди. // Возвращает указатель на waiter для возможной отмены через queue_cancel_wait struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, queue_threshold_callback_fn callback, void* arg); @@ -152,73 +152,15 @@ static inline size_t queue_total_bytes(struct ll_queue* q) { return q->total_bytes; } -// Получить статистику использования пулов памяти -void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse); - -// ==================== Управление позициями ==================== - -// Структура управления позицией для навигации по очереди -struct ll_queue_pos { - struct ll_queue* queue; // Ссылка на очередь - struct ll_entry* current; // Текущий элемент (NULL = перед head) - struct ll_entry* prev; // Предыдущий элемент (для навигации) - int index; // Текущий индекс (-1 = перед head) - int valid; // Флаг валидности позиции - uint64_t version; // Версия очереди на момент создания/обновления позиции -}; - -// Создать новую позицию для очереди -struct ll_queue_pos* queue_pos_new(struct ll_queue* queue); - -// Освободить позицию (не удаляет элементы очереди) -void queue_pos_free(struct ll_queue_pos* pos); - -// Сбросить позицию в начальное состояние (перед head) -void queue_pos_reset(struct ll_queue_pos* pos); - -// Перейти к следующему элементу -// Возвращает: 1 при успехе, 0 если достигнут конец -int queue_pos_next(struct ll_queue_pos* pos); - -// Перейти к предыдущему элементу -// Возвращает: 1 при успехе, 0 если достигнуто начало -int queue_pos_prev(struct ll_queue_pos* pos); - -// Перейти к конкретному индексу (0 = head) -// Возвращает: 1 при успехе, 0 если индекс неверен -int queue_pos_seek(struct ll_queue_pos* pos, int index); - -// Получить текущий элемент без удаления -static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) return NULL; - return pos->current; -} - -// Получить текущий индекс -static inline int queue_pos_index(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) return -1; - return pos->index; -} - -// Обновить позицию после изменения очереди (сбросить версию) -static inline void queue_pos_refresh(struct ll_queue_pos* pos) { - if (!pos || !pos->queue) return; - pos->version = pos->queue->version; - pos->valid = 1; -} - -// Вставить элемент относительно текущей позиции -// mode: 0 = перед current, 1 = после current -// Возвращает: 0 при успехе, -1 при ошибке -int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode); +// ==================== Поиск и удаление по ID ==================== -// Удалить элемент из текущей позиции и вернуть его -// После удаления current указывает на следующий элемент (или NULL) -// Возвращает: указатель на удаленный элемент или NULL -struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos); +// Найти элемент по ID +// Возвращает: указатель на пользовательские данные или NULL если не найден +void* queue_find_data_by_id(struct ll_queue* q, uint32_t id); -// Удалить конкретный элемент из очереди по указателю +// Удалить элемент из очереди по указателю на данные // Возвращает: 0 при успехе, -1 если элемент не найден -int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry); +// ПРИМЕЧАНИЕ: НЕ изменяет ref_count элемента, просто удаляет из очереди +int queue_remove_data(struct ll_queue* q, void* data); #endif // LL_QUEUE_H \ No newline at end of file diff --git a/lib/ll_queue.h2 b/lib/ll_queue.h2 deleted file mode 100755 index 408e2c59..00000000 --- a/lib/ll_queue.h2 +++ /dev/null @@ -1,224 +0,0 @@ -// ll_queue.h -#ifndef LL_QUEUE_H -#define LL_QUEUE_H - -#include // для size_t -#include // для uint64_t -#include "memory_pool.h" // для struct memory_pool - -// Предварительные объявления -struct ll_queue; -struct ll_entry; -struct queue_waiter; -struct memory_pool; -struct ll_queue_pos; // Управляющая структура позиции - -// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки -// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент -typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg); - -// Структура элемента - переменный размер, данные расположены сразу после структуры -struct ll_entry { - struct ll_entry* next; // Указатель на следующий элемент в очереди - struct ll_entry* prev; // Указатель на предыдущий элемент в очереди - size_t size; // Размер данных элемента (байт) - int ref_count; // Счетчик ссылок для предотвращения double-free -}; - -// Структура условия ожидания (waiter) -struct queue_waiter { - int max_packets; // Максимальное количество пакетов - size_t max_bytes; // Максимальное количество байт (0 = не проверять байты) - void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова - void* callback_arg; // Аргумент коллбэка - struct queue_waiter* next; // Следующий ожидающий в списке -}; - -typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg); - -// Структура очереди -struct ll_queue { - struct ll_entry* head; // Первый элемент (извлекается отсюда) - struct ll_entry* tail; // Последний элемент (добавляется сюда) - int count; // Текущее количество элементов - size_t total_bytes; // Общий размер данных всех элементов (байт) - int size_limit; // Максимальное количество (-1 = без ограничения) - - queue_callback_fn callback; // Функция коллбэка - void* callback_arg; // Пользовательский аргумент для коллбэка - int callback_suspended; // 1 если коллбэки приостановлены (во время обработки) - - void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления - struct UASYNC* ua; // Экземпляр uasync для таймеров - - struct queue_waiter* waiters; // Список ожидающих коллбэков - - // Пулы памяти для оптимизации аллокаций - struct memory_pool* pool; // Пул для структур struct queue_waiter - - uint64_t version; // Версия для инвалидации позиций -}; - -// ==================== Управление очередью ==================== - -// Создать новую пустую очередь -// ua - экземпляр uasync для таймеров (обязательный параметр) -// pool - если не null то использовать этот пул памяти -// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h) -struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool); - -// Освободить очередь и все её элементы -// Также отменяет отложенное возобновление если оно запланировано -void queue_free(struct ll_queue* q); - -// ==================== Конфигурация очереди ==================== - -// Установить функцию и аргумент коллбэка для автозабора из очереди -// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк -// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету. -void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg); - -// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя). -// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет. -// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0) -// Это предотвращает накопление рекурсии в стеке вызовов -void queue_resume_callback(struct ll_queue* q); - -// Установить максимальное количество элементов в очереди -// При превышении лимита новый элемент автоматически освобождается -void queue_set_size_limit(struct ll_queue* q, int lim); - -// ==================== Управление элементами ==================== - -// Создать новый элемент с областью данных указанного размера -// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт] -// Возвращает: указатель на элемент или NULL при ошибке выделения памяти -struct ll_entry* queue_entry_new(size_t data_size); - -// Освободить элемент (не влияет на связи в очереди) -void queue_entry_free(struct ll_entry* entry); - -// ==================== Операции с очередью ==================== - -// Добавить элемент в конец очереди (FIFO) -// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк -// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) -int queue_entry_put(struct ll_queue* q, struct ll_entry* entry); - -// Добавить элемент в начало очереди (LIFO, высокий приоритет) -// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк -// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) -int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry); - -// Извлечь элемент из начала очереди -// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию -// Возвращает: указатель на элемент или NULL если очередь пуста -struct ll_entry* queue_entry_get(struct ll_queue* q); - -// Получить текущее количество элементов в очереди -int queue_entry_count(struct ll_queue* q); - -// ==================== Вспомогательные функции ==================== - -// Получить указатель на область данных элемента -// Данные расположены сразу после структуры struct ll_entry -static inline void* ll_entry_data(struct ll_entry* entry) { - if (!entry) return NULL; - return (void*)(entry + 1); -} - -// Получить размер данных элемента -static inline size_t ll_entry_size(struct ll_entry* entry) { - if (!entry) return 0; - return entry->size; -} - -// ==================== Асинхронное ожидание ==================== - -// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь -// не более max_packets пакетов и не более max_bytes байт (если max_bytes != 0). -// Если условие уже выполнено, коллбэк вызывается немедленно. -// Можно зарегистрировать несколько ожиданий на одной очереди. -// Возвращает указатель на waiter для возможной отмены через queue_cancel_wait -struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, - queue_threshold_callback_fn callback, void* arg); - -// Отменить ожидание (удалить waiter из списка) -void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter); - -// Получить общий размер данных в очереди (байт) -static inline size_t queue_total_bytes(struct ll_queue* q) { - if (!q) return 0; - return q->total_bytes; -} - -// Получить статистику использования пулов памяти -void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse); - -// ==================== Управление позициями ==================== - -// Структура управления позицией для навигации по очереди -struct ll_queue_pos { - struct ll_queue* queue; // Ссылка на очередь - struct ll_entry* current; // Текущий элемент (NULL = перед head) - struct ll_entry* prev; // Предыдущий элемент (для навигации) - int index; // Текущий индекс (-1 = перед head) - int valid; // Флаг валидности позиции - uint64_t version; // Версия очереди на момент создания/обновления позиции -}; - -// Создать новую позицию для очереди -struct ll_queue_pos* queue_pos_new(struct ll_queue* queue); - -// Освободить позицию (не удаляет элементы очереди) -void queue_pos_free(struct ll_queue_pos* pos); - -// Сбросить позицию в начальное состояние (перед head) -void queue_pos_reset(struct ll_queue_pos* pos); - -// Перейти к следующему элементу -// Возвращает: 1 при успехе, 0 если достигнут конец -int queue_pos_next(struct ll_queue_pos* pos); - -// Перейти к предыдущему элементу -// Возвращает: 1 при успехе, 0 если достигнуто начало -int queue_pos_prev(struct ll_queue_pos* pos); - -// Перейти к конкретному индексу (0 = head) -// Возвращает: 1 при успехе, 0 если индекс неверен -int queue_pos_seek(struct ll_queue_pos* pos, int index); - -// Получить текущий элемент без удаления -static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) return NULL; - return pos->current; -} - -// Получить текущий индекс -static inline int queue_pos_index(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) return -1; - return pos->index; -} - -// Обновить позицию после изменения очереди (сбросить версию) -static inline void queue_pos_refresh(struct ll_queue_pos* pos) { - if (!pos || !pos->queue) return; - pos->version = pos->queue->version; - pos->valid = 1; -} - -// Вставить элемент относительно текущей позиции -// mode: 0 = перед current, 1 = после current -// Возвращает: 0 при успехе, -1 при ошибке -int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode); - -// Удалить элемент из текущей позиции и вернуть его -// После удаления current указывает на следующий элемент (или NULL) -// Возвращает: указатель на удаленный элемент или NULL -struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos); - -// Удалить конкретный элемент из очереди по указателю -// Возвращает: 0 при успехе, -1 если элемент не найден -int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry); - -#endif // LL_QUEUE_H \ No newline at end of file diff --git a/lib/ll_queue.h3 b/lib/ll_queue.h3 deleted file mode 100755 index 408e2c59..00000000 --- a/lib/ll_queue.h3 +++ /dev/null @@ -1,224 +0,0 @@ -// ll_queue.h -#ifndef LL_QUEUE_H -#define LL_QUEUE_H - -#include // для size_t -#include // для uint64_t -#include "memory_pool.h" // для struct memory_pool - -// Предварительные объявления -struct ll_queue; -struct ll_entry; -struct queue_waiter; -struct memory_pool; -struct ll_queue_pos; // Управляющая структура позиции - -// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки -// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент -typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg); - -// Структура элемента - переменный размер, данные расположены сразу после структуры -struct ll_entry { - struct ll_entry* next; // Указатель на следующий элемент в очереди - struct ll_entry* prev; // Указатель на предыдущий элемент в очереди - size_t size; // Размер данных элемента (байт) - int ref_count; // Счетчик ссылок для предотвращения double-free -}; - -// Структура условия ожидания (waiter) -struct queue_waiter { - int max_packets; // Максимальное количество пакетов - size_t max_bytes; // Максимальное количество байт (0 = не проверять байты) - void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова - void* callback_arg; // Аргумент коллбэка - struct queue_waiter* next; // Следующий ожидающий в списке -}; - -typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg); - -// Структура очереди -struct ll_queue { - struct ll_entry* head; // Первый элемент (извлекается отсюда) - struct ll_entry* tail; // Последний элемент (добавляется сюда) - int count; // Текущее количество элементов - size_t total_bytes; // Общий размер данных всех элементов (байт) - int size_limit; // Максимальное количество (-1 = без ограничения) - - queue_callback_fn callback; // Функция коллбэка - void* callback_arg; // Пользовательский аргумент для коллбэка - int callback_suspended; // 1 если коллбэки приостановлены (во время обработки) - - void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления - struct UASYNC* ua; // Экземпляр uasync для таймеров - - struct queue_waiter* waiters; // Список ожидающих коллбэков - - // Пулы памяти для оптимизации аллокаций - struct memory_pool* pool; // Пул для структур struct queue_waiter - - uint64_t version; // Версия для инвалидации позиций -}; - -// ==================== Управление очередью ==================== - -// Создать новую пустую очередь -// ua - экземпляр uasync для таймеров (обязательный параметр) -// pool - если не null то использовать этот пул памяти -// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h) -struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool); - -// Освободить очередь и все её элементы -// Также отменяет отложенное возобновление если оно запланировано -void queue_free(struct ll_queue* q); - -// ==================== Конфигурация очереди ==================== - -// Установить функцию и аргумент коллбэка для автозабора из очереди -// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк -// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету. -void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg); - -// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя). -// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет. -// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0) -// Это предотвращает накопление рекурсии в стеке вызовов -void queue_resume_callback(struct ll_queue* q); - -// Установить максимальное количество элементов в очереди -// При превышении лимита новый элемент автоматически освобождается -void queue_set_size_limit(struct ll_queue* q, int lim); - -// ==================== Управление элементами ==================== - -// Создать новый элемент с областью данных указанного размера -// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт] -// Возвращает: указатель на элемент или NULL при ошибке выделения памяти -struct ll_entry* queue_entry_new(size_t data_size); - -// Освободить элемент (не влияет на связи в очереди) -void queue_entry_free(struct ll_entry* entry); - -// ==================== Операции с очередью ==================== - -// Добавить элемент в конец очереди (FIFO) -// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк -// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) -int queue_entry_put(struct ll_queue* q, struct ll_entry* entry); - -// Добавить элемент в начало очереди (LIFO, высокий приоритет) -// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк -// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) -int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry); - -// Извлечь элемент из начала очереди -// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию -// Возвращает: указатель на элемент или NULL если очередь пуста -struct ll_entry* queue_entry_get(struct ll_queue* q); - -// Получить текущее количество элементов в очереди -int queue_entry_count(struct ll_queue* q); - -// ==================== Вспомогательные функции ==================== - -// Получить указатель на область данных элемента -// Данные расположены сразу после структуры struct ll_entry -static inline void* ll_entry_data(struct ll_entry* entry) { - if (!entry) return NULL; - return (void*)(entry + 1); -} - -// Получить размер данных элемента -static inline size_t ll_entry_size(struct ll_entry* entry) { - if (!entry) return 0; - return entry->size; -} - -// ==================== Асинхронное ожидание ==================== - -// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь -// не более max_packets пакетов и не более max_bytes байт (если max_bytes != 0). -// Если условие уже выполнено, коллбэк вызывается немедленно. -// Можно зарегистрировать несколько ожиданий на одной очереди. -// Возвращает указатель на waiter для возможной отмены через queue_cancel_wait -struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, - queue_threshold_callback_fn callback, void* arg); - -// Отменить ожидание (удалить waiter из списка) -void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter); - -// Получить общий размер данных в очереди (байт) -static inline size_t queue_total_bytes(struct ll_queue* q) { - if (!q) return 0; - return q->total_bytes; -} - -// Получить статистику использования пулов памяти -void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse); - -// ==================== Управление позициями ==================== - -// Структура управления позицией для навигации по очереди -struct ll_queue_pos { - struct ll_queue* queue; // Ссылка на очередь - struct ll_entry* current; // Текущий элемент (NULL = перед head) - struct ll_entry* prev; // Предыдущий элемент (для навигации) - int index; // Текущий индекс (-1 = перед head) - int valid; // Флаг валидности позиции - uint64_t version; // Версия очереди на момент создания/обновления позиции -}; - -// Создать новую позицию для очереди -struct ll_queue_pos* queue_pos_new(struct ll_queue* queue); - -// Освободить позицию (не удаляет элементы очереди) -void queue_pos_free(struct ll_queue_pos* pos); - -// Сбросить позицию в начальное состояние (перед head) -void queue_pos_reset(struct ll_queue_pos* pos); - -// Перейти к следующему элементу -// Возвращает: 1 при успехе, 0 если достигнут конец -int queue_pos_next(struct ll_queue_pos* pos); - -// Перейти к предыдущему элементу -// Возвращает: 1 при успехе, 0 если достигнуто начало -int queue_pos_prev(struct ll_queue_pos* pos); - -// Перейти к конкретному индексу (0 = head) -// Возвращает: 1 при успехе, 0 если индекс неверен -int queue_pos_seek(struct ll_queue_pos* pos, int index); - -// Получить текущий элемент без удаления -static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) return NULL; - return pos->current; -} - -// Получить текущий индекс -static inline int queue_pos_index(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) return -1; - return pos->index; -} - -// Обновить позицию после изменения очереди (сбросить версию) -static inline void queue_pos_refresh(struct ll_queue_pos* pos) { - if (!pos || !pos->queue) return; - pos->version = pos->queue->version; - pos->valid = 1; -} - -// Вставить элемент относительно текущей позиции -// mode: 0 = перед current, 1 = после current -// Возвращает: 0 при успехе, -1 при ошибке -int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode); - -// Удалить элемент из текущей позиции и вернуть его -// После удаления current указывает на следующий элемент (или NULL) -// Возвращает: указатель на удаленный элемент или NULL -struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos); - -// Удалить конкретный элемент из очереди по указателю -// Возвращает: 0 при успехе, -1 если элемент не найден -int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry); - -#endif // LL_QUEUE_H \ No newline at end of file diff --git a/lib/ll_queue.h_ b/lib/ll_queue.h_ deleted file mode 100755 index 33e8266f..00000000 --- a/lib/ll_queue.h_ +++ /dev/null @@ -1,210 +0,0 @@ -#ifndef LL_QUEUE_H -#define LL_QUEUE_H - -#include // для size_t -#include "memory_pool.h" // для struct memory_pool - -// Предварительные объявления -struct ll_queue; -struct ll_entry; -struct queue_waiter; -struct memory_pool; -struct ll_queue_pos; // Управляющая структура позиции - -// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки -// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент -typedef void (*queue_callback_fn)(struct ll_queue* q, struct ll_entry* entry, void* arg); - -// Структура элемента - переменный размер, данные расположены сразу после структуры -struct ll_entry { - struct ll_entry* next; // Указатель на следующий элемент в очереди - struct ll_entry* prev; // Указатель на предыдущий элемент в очереди - size_t size; // Размер данных элемента (байт) - int ref_count; // Счетчик ссылок для предотвращения double-free -}; - -// Структура условия ожидания (waiter) -struct queue_waiter { - int max_packets; // Максимальное количество пакетов - size_t max_bytes; // Максимальное количество байт - void (*callback)(struct ll_queue* q, void* arg); // Коллбэк для вызова - void* callback_arg; // Аргумент коллбэка - struct queue_waiter* next; // Следующий ожидающий в списке -}; - -typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg); - -// Структура очереди -struct ll_queue { - struct ll_entry* head; // Первый элемент (извлекается отсюда) - struct ll_entry* tail; // Последний элемент (добавляется сюда) - int count; // Текущее количество элементов - size_t total_bytes; // Общий размер данных всех элементов (байт) - int size_limit; // Максимальное количество (-1 = без ограничения) - - queue_callback_fn callback; // Функция коллбэка - void* callback_arg; // Пользовательский аргумент для коллбэка - int callback_suspended; // 1 если коллбэки приостановлены (во время обработки) - - void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления - struct UASYNC* ua; // Экземпляр uasync для таймеров - - struct queue_waiter* waiters; // Список ожидающих коллбэков - - // Пулы памяти для оптимизации аллокаций - struct memory_pool* pool; // Пул для структур struct queue_waiter -}; - -// ==================== Управление очередью ==================== - -// Создать новую пустую очередь -// ua - экземпляр uasync для таймеров (обязательный параметр) -// pool - если не null то использовать этот пул памяти -// Возвращает: указатель на очередь или NULL при ошибке выделения памяти (see memory_pool.c/h) -struct ll_queue* queue_new(struct UASYNC* ua, struct memory_pool* pool); - -// Освободить очередь и все её элементы -// Также отменяет отложенное возобновление если оно запланировано -void queue_free(struct ll_queue* q); - -// ==================== Конфигурация очереди ==================== - -// Установить функцию и аргумент коллбэка для автозабора из очереди -// Коллбэк вызывается когда в очереди есть элемент и разрешен коллбэк -// обработчик должен обработать этот пакет (может использовать асинхронное ожидание). Когда будет готов к приёму следующего - должен вызвать resume_callback. обработка строго по одному пакету. -void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg); - -// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя). -// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет. -// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0) -// Это предотвращает накопление рекурсии в стеке вызовов -void queue_resume_callback(struct ll_queue* q); - -// Установить максимальное количество элементов в очереди -// При превышении лимита новый элемент автоматически освобождается -void queue_set_size_limit(struct ll_queue* q, int lim); - -// ==================== Управление элементами ==================== - -// Создать новый элемент с областью данных указанного размера -// Память выделяется одним блоком: [struct ll_entry][область данных data_size байт] -// Возвращает: указатель на элемент или NULL при ошибке выделения памяти -struct ll_entry* queue_entry_new(size_t data_size); - -// Освободить элемент (не влияет на связи в очереди) -void queue_entry_free(struct ll_entry* entry); - -// ==================== Операции с очередью ==================== - -// Добавить элемент в конец очереди (FIFO) -// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк -// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) -int queue_entry_put(struct ll_queue* q, struct ll_entry* entry); - -// Добавить элемент в начало очереди (LIFO, высокий приоритет) -// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк -// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден) -int queue_entry_put_first(struct ll_queue* q, struct ll_entry* entry); - -// Извлечь элемент из начала очереди -// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию -// Возвращает: указатель на элемент или NULL если очередь пуста -struct ll_entry* queue_entry_get(struct ll_queue* q); - -// Получить текущее количество элементов в очереди -int queue_entry_count(struct ll_queue* q); - -// ==================== Вспомогательные функции ==================== - -// Получить указатель на область данных элемента -// Данные расположены сразу после структуры struct ll_entry -static inline void* ll_entry_data(struct ll_entry* entry) { - return (void*)(entry + 1); -} - -// Получить размер данных элемента -static inline size_t ll_entry_size(struct ll_entry* entry) { - return entry->size; -} - -// ==================== Асинхронное ожидание ==================== - -// Зарегистрировать одноразовый коллбэк, который будет вызван когда очередь будет иметь -// не более max_packets пакетов и не более max_bytes байт. -// Если условие уже выполнено, коллбэк вызывается немедленно. -// Можно зарегистрировать несколько ожиданий на одной очереди. -// Возвращает указатель на waiter для возможной отмены через queue_cancel_wait -struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes, - queue_threshold_callback_fn callback, void* arg); - -// Отменить ожидание (удалить waiter из списка) -void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter); - -// Получить общий размер данных в очереди (байт) -static inline size_t queue_total_bytes(struct ll_queue* q) { - if (!q) return 0; - return q->total_bytes; -} - -// Получить статистику использования пулов памяти -void queue_get_pool_stats(struct ll_queue* q, size_t* waiter_allocations, size_t* waiter_reuse); - -// ==================== Управление позициями ==================== - -// Структура управления позицией для навигации по очереди -struct ll_queue_pos { - struct ll_queue* queue; // Ссылка на очередь - struct ll_entry* current; // Текущий элемент (NULL = перед head) - struct ll_entry* prev; // Предыдущий элемент (для навигации) - int index; // Текущий индекс (-1 = перед head) - int valid; // Флаг валидности позиции -}; - -// Создать новую позицию для очереди -struct ll_queue_pos* queue_pos_new(struct ll_queue* queue); - -// Освободить позицию (не удаляет элементы очереди) -void queue_pos_free(struct ll_queue_pos* pos); - -// Сбросить позицию в начальное состояние (перед head) -void queue_pos_reset(struct ll_queue_pos* pos); - -// Перейти к следующему элементу -// Возвращает: 1 при успехе, 0 если достигнут конец -int queue_pos_next(struct ll_queue_pos* pos); - -// Перейти к предыдущему элементу -// Возвращает: 1 при успехе, 0 если достигнуто начало -int queue_pos_prev(struct ll_queue_pos* pos); - -// Перейти к конкретному индексу (0 = head) -// Возвращает: 1 при успехе, 0 если индекс неверен -int queue_pos_seek(struct ll_queue_pos* pos, int index); - -// Получить текущий элемент без удаления -static inline struct ll_entry* queue_pos_current(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) return NULL; - return pos->current; -} - -// Получить текущий индекс -static inline int queue_pos_index(struct ll_queue_pos* pos) { - if (!pos || !pos->valid) return -1; - return pos->index; -} - -// Вставить элемент относительно текущей позиции -// mode: 0 = перед current, 1 = после current -// Возвращает: 0 при успехе, -1 при ошибке -int queue_pos_insert(struct ll_queue_pos* pos, struct ll_entry* entry, int mode); - -// Удалить элемент из текущей позиции и вернуть его -// После удаления current указывает на следующий элемент (или NULL) -// Возвращает: указатель на удаленный элемент или NULL -struct ll_entry* queue_pos_remove(struct ll_queue_pos* pos); - -// Удалить конкретный элемент из очереди по указателю -// Возвращает: 0 при успехе, -1 если элемент не найден -int queue_entry_remove(struct ll_queue* q, struct ll_entry* entry); - -#endif // LL_QUEUE_H diff --git a/lib/memory_pool.h b/lib/memory_pool.h index fa0bb50d..ba880c24 100644 --- a/lib/memory_pool.h +++ b/lib/memory_pool.h @@ -14,10 +14,14 @@ struct memory_pool { size_t reuse_count; // Количество повторных использований из пула }; +// сам пул: +// если используем с элементами ll_queue то не забываем что к object_size надо прибавить sizeof(struct ll_entry) struct memory_pool* memory_pool_init(size_t object_size); +void memory_pool_destroy(struct memory_pool* pool); + +// элементы пула: void* memory_pool_alloc(struct memory_pool* pool); void memory_pool_free(struct memory_pool* pool, void* obj); void memory_pool_get_stats(struct memory_pool* pool, size_t* allocations, size_t* reuse_count); -void memory_pool_destroy(struct memory_pool* pool); #endif // MEMORY_POOL_H diff --git a/lib/timeout_heap.c b/lib/timeout_heap.c index d219736a..a0678558 100644 --- a/lib/timeout_heap.c +++ b/lib/timeout_heap.c @@ -1,6 +1,7 @@ // timeout_heap.c #include "timeout_heap.h" +#include "debug_config.h" #include #include // For potential error printing, optional @@ -122,6 +123,7 @@ int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) { if (h->free_callback) { h->free_callback(h->user_data, data_to_free); } else { + DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "timeout_heap_pop: freeing data %p", data_to_free); free(data_to_free); } h->freed_count++; diff --git a/lib/u_async.c b/lib/u_async.c index 3ee931d9..4957f5cf 100644 --- a/lib/u_async.c +++ b/lib/u_async.c @@ -1,734 +1,699 @@ -// uasync.c - -#include "u_async.h" -#include "debug_config.h" -#include -#include -#include -#include -#include -#include -#include -#include - - - -// Timeout node with safe cancellation -struct timeout_node { - void* arg; - timeout_callback_t callback; - uint64_t expiration_ms; // absolute expiration time in milliseconds - struct UASYNC* ua; // Pointer back to uasync instance for counter updates - int cancelled; // Cancellation flag -}; - -// Socket node with array-based storage -struct socket_node { - int fd; - socket_callback_t read_cbk; - socket_callback_t write_cbk; - socket_callback_t except_cbk; - void* user_data; - int active; // 1 if socket is active, 0 if freed (for reuse) -}; - -// Array-based socket management for O(1) operations -struct socket_array { - struct socket_node* sockets; // Dynamic array of socket nodes - int* fd_to_index; // FD to array index mapping - int* index_to_fd; // Array index to FD mapping - int capacity; // Total allocated capacity - int count; // Number of active sockets - int max_fd; // Maximum FD for bounds checking -}; - -static struct socket_array* socket_array_create(int initial_capacity); -static void socket_array_destroy(struct socket_array* sa); -static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data); -static int socket_array_remove(struct socket_array* sa, int fd); -static struct socket_node* socket_array_get(struct socket_array* sa, int fd); -static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds); - -// No global instance - each module must use its own struct UASYNC instance - -// Array-based socket management implementation -static struct socket_array* socket_array_create(int initial_capacity) { - if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity - - struct socket_array* sa = malloc(sizeof(struct socket_array)); - if (!sa) return NULL; - - sa->sockets = calloc(initial_capacity, sizeof(struct socket_node)); - sa->fd_to_index = calloc(initial_capacity, sizeof(int)); - sa->index_to_fd = calloc(initial_capacity, sizeof(int)); - - if (!sa->sockets || !sa->fd_to_index || !sa->index_to_fd) { - free(sa->sockets); - free(sa->fd_to_index); - free(sa->index_to_fd); - free(sa); - return NULL; - } - - // Initialize mapping arrays to -1 (invalid) - for (int i = 0; i < initial_capacity; i++) { - sa->fd_to_index[i] = -1; - sa->index_to_fd[i] = -1; - sa->sockets[i].fd = -1; - sa->sockets[i].active = 0; - } - - sa->capacity = initial_capacity; - sa->count = 0; - sa->max_fd = -1; - - return sa; -} - -static void socket_array_destroy(struct socket_array* sa) { - if (!sa) return; - - free(sa->sockets); - free(sa->fd_to_index); - free(sa->index_to_fd); - free(sa); -} - -static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { - if (!sa || fd < 0 || fd >= FD_SETSIZE) return -1; - if (fd >= sa->capacity) { - // Need to resize - double the capacity - int new_capacity = sa->capacity * 2; - if (fd >= new_capacity) new_capacity = fd + 16; // Ensure enough space - - struct socket_node* new_sockets = realloc(sa->sockets, new_capacity * sizeof(struct socket_node)); - int* new_fd_to_index = realloc(sa->fd_to_index, new_capacity * sizeof(int)); - int* new_index_to_fd = realloc(sa->index_to_fd, new_capacity * sizeof(int)); - - if (!new_sockets || !new_fd_to_index || !new_index_to_fd) { - // Allocation failed - free(new_sockets); - free(new_fd_to_index); - free(new_index_to_fd); - return -1; - } - - // Initialize new elements - for (int i = sa->capacity; i < new_capacity; i++) { - new_fd_to_index[i] = -1; - new_index_to_fd[i] = -1; - new_sockets[i].fd = -1; - new_sockets[i].active = 0; - } - - sa->sockets = new_sockets; - sa->fd_to_index = new_fd_to_index; - sa->index_to_fd = new_index_to_fd; - sa->capacity = new_capacity; - } - - // Check if FD already exists - if (sa->fd_to_index[fd] != -1) return -1; // FD already exists - - // Find first free slot - int index = -1; - for (int i = 0; i < sa->capacity; i++) { - if (!sa->sockets[i].active) { - index = i; - break; - } - } - - if (index == -1) return -1; // No free slots (shouldn't happen) - - // Add the socket - sa->sockets[index].fd = fd; - sa->sockets[index].read_cbk = read_cbk; - sa->sockets[index].write_cbk = write_cbk; - sa->sockets[index].except_cbk = except_cbk; - sa->sockets[index].user_data = user_data; - sa->sockets[index].active = 1; - - sa->fd_to_index[fd] = index; - sa->index_to_fd[index] = fd; - sa->count++; - - if (fd > sa->max_fd) sa->max_fd = fd; - - return index; -} - -static int socket_array_remove(struct socket_array* sa, int fd) { - if (!sa || fd < 0 || fd >= sa->capacity) return -1; - - int index = sa->fd_to_index[fd]; - if (index == -1 || !sa->sockets[index].active) return -1; // FD not found - - // Mark as inactive - sa->sockets[index].active = 0; - sa->sockets[index].fd = -1; - sa->fd_to_index[fd] = -1; - sa->index_to_fd[index] = -1; - sa->count--; - - return 0; -} - -static struct socket_node* socket_array_get(struct socket_array* sa, int fd) { - if (!sa || fd < 0 || fd >= sa->capacity) return NULL; - - int index = sa->fd_to_index[fd]; - if (index == -1 || !sa->sockets[index].active) return NULL; - - return &sa->sockets[index]; -} - -static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds) { - if (!sa || !fds || max_fds <= 0) return 0; - - int count = 0; - for (int i = 0; i < sa->capacity && count < max_fds; i++) { - if (sa->sockets[i].active) { - fds[count].fd = sa->sockets[i].fd; - fds[count].events = 0; - if (sa->sockets[i].read_cbk) fds[count].events |= POLLIN; - if (sa->sockets[i].write_cbk) fds[count].events |= POLLOUT; - if (sa->sockets[i].except_cbk) fds[count].events |= POLLERR; - fds[count].revents = 0; - count++; - } - } - - return count; -} - -// Callback to free timeout node and update counters -static void timeout_node_free_callback(void* user_data, void* data) { - struct UASYNC* ua = (struct UASYNC*)user_data; - struct timeout_node* node = (struct timeout_node*)data; - (void)node; // Not used directly, but keep for consistency - ua->timer_free_count++; - free(data); -} - -// Helper to get current time -static void get_current_time(struct timeval* tv) { - gettimeofday(tv, NULL); -} - - - -// Drain wakeup pipe - read all available bytes -static void drain_wakeup_pipe(struct UASYNC* ua) { - if (!ua || !ua->wakeup_initialized) return; - - char buf[64]; - while (1) { - ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf)); - if (n <= 0) break; - } -} - -// Helper to add timeval: tv += dt (timebase units) -static void timeval_add_tb(struct timeval* tv, int dt) { - tv->tv_usec += (dt % 10000) * 100; - tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000; - tv->tv_usec %= 1000000; -} - -// Convert timeval to milliseconds (uint64_t) -static uint64_t timeval_to_ms(const struct timeval* tv) { - return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL; -} - - - -// Simplified timeout handling without reference counting - -// Process expired timeouts with safe cancellation -static void process_timeouts(struct UASYNC* ua) { - if (!ua || !ua->timeout_heap) return; - - struct timeval now_tv; - get_current_time(&now_tv); - uint64_t now_ms = timeval_to_ms(&now_tv); - - while (1) { - TimeoutEntry entry; - if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break; - if (entry.expiration > now_ms) break; - - // Pop the expired timeout - timeout_heap_pop(ua->timeout_heap, &entry); - struct timeout_node* node = (struct timeout_node*)entry.data; - - if (node && node->callback && !node->cancelled) { - // Execute callback only if not cancelled - node->callback(node->arg); - } - - // Always free the node after processing - if (node && node->ua) { - node->ua->timer_free_count++; - } - free(node); - } -} - -// Compute time to next timeout -static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) { - if (!ua || !ua->timeout_heap) { - tv->tv_sec = 0; - tv->tv_usec = 0; - return; - } - - TimeoutEntry entry; - if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) { - tv->tv_sec = 0; - tv->tv_usec = 0; - return; - } - - struct timeval now_tv; - get_current_time(&now_tv); - uint64_t now_ms = timeval_to_ms(&now_tv); - - if (entry.expiration <= now_ms) { - tv->tv_sec = 0; - tv->tv_usec = 0; - return; - } - - uint64_t delta_ms = entry.expiration - now_ms; - if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow - delta_ms = 86400000; - } - tv->tv_sec = delta_ms / 1000; - tv->tv_usec = (delta_ms % 1000) * 1000; -} - - - -// Instance version -void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) { - if (!ua || timeout_tb < 0 || !callback) return NULL; - if (!ua->timeout_heap) return NULL; - - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p", - ua, timeout_tb, arg, callback); - - struct timeout_node* node = malloc(sizeof(struct timeout_node)); - if (!node) { - DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node"); - return NULL; - } - ua->timer_alloc_count++; - - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)", - node, ua->timer_alloc_count); - - node->arg = arg; - node->callback = callback; - node->ua = ua; - node->cancelled = 0; - - // Calculate expiration time in milliseconds - struct timeval now; - get_current_time(&now); - timeval_add_tb(&now, timeout_tb); - node->expiration_ms = timeval_to_ms(&now); - - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms", - node, (unsigned long long)node->expiration_ms); - - // Add to heap - if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap"); - free(node); - ua->timer_free_count++; // Balance the alloc counter - return NULL; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node); - return node; -} - - - -// Instance version -err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) { - if (!ua || !t_id || !ua->timeout_heap) { - DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p", - ua, t_id, ua ? ua->timeout_heap : NULL); - return ERR_FAIL; - } - - struct timeout_node* node = (struct timeout_node*)t_id; - - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms", - ua, t_id, node, (unsigned long long)node->expiration_ms); - - // Try to cancel from heap first - if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) { - // Successfully removed from heap - mark as cancelled and update counter - node->cancelled = 1; - node->callback = NULL; - ua->timer_free_count++; // Update counter for cancelled timer - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap, free_count=%zu", node, ua->timer_free_count); - free(node); // Free the cancelled timer node - return ERR_OK; - } - node->cancelled = 1; - node->callback = NULL; - return ERR_OK; -} // Successfully cancelled (marked) - - -// Instance version -void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { - if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Bounds check - - int index = socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data); - if (index < 0) return NULL; - - ua->socket_alloc_count++; - - // Return pointer to the socket node (same as before for API compatibility) - return &ua->sockets->sockets[index]; -} - - - -// Instance version -err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) { - if (!ua || !s_id) return ERR_FAIL; - - struct socket_node* node = (struct socket_node*)s_id; - if (node->fd < 0) return ERR_FAIL; // Invalid node - - int result = socket_array_remove(ua->sockets, node->fd); - if (result != 0) return ERR_FAIL; - - ua->socket_free_count++; - return ERR_OK; -} - - - -void uasync_mainloop(struct UASYNC* ua) { - while (1) { - uasync_poll(ua, -1); /* infinite timeout */ - } -} - -// Instance version -void uasync_poll(struct UASYNC* ua, int timeout_tb) { - if (!ua) return; - - /* Process expired timeouts */ - process_timeouts(ua); - - /* Compute timeout for poll in milliseconds */ - int timeout_ms = -1; // infinite by default - - // Get next timeout from heap - struct timeval tv; - get_next_timeout(ua, &tv); - - if (tv.tv_sec > 0 || tv.tv_usec > 0 || (ua->timeout_heap && ua->timeout_heap->size > 0)) { - // Convert timeval to milliseconds, cap at INT_MAX - uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL; - if (ms > INT_MAX) ms = INT_MAX; - timeout_ms = (int)ms; - } - - /* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */ - if (timeout_tb >= 0) { - // Convert timebase (0.1 ms) to milliseconds - int user_timeout_ms = timeout_tb / 10; - if (timeout_tb % 10 != 0) user_timeout_ms++; // round up - - if (timeout_ms < 0 || user_timeout_ms < timeout_ms) { - timeout_ms = user_timeout_ms; - } - } - - /* Build pollfd array from socket array - O(1) per socket */ - int socket_count = ua->sockets ? ua->sockets->count : 0; - int wakeup_fd_present = ua->wakeup_initialized ? 1 : 0; - int total_fds = socket_count + wakeup_fd_present; - - if (total_fds == 0) { - /* No sockets and no wakeup fd, just wait for timeout */ - if (timeout_ms >= 0) { - /* usleep would be better but we just call poll with empty set */ - struct pollfd dummy; - poll(&dummy, 0, timeout_ms); - } else { - /* Infinite timeout with no sockets - should not happen in practice */ - return; - } - /* Check timeouts again after sleep */ - process_timeouts(ua); - return; - } - - struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd)); - struct socket_node** nodes = NULL; - if (socket_count > 0) { - nodes = malloc(socket_count * sizeof(struct socket_node*)); - } - if (!fds || (socket_count > 0 && !nodes)) { - free(fds); - free(nodes); - return; /* out of memory */ - } - - /* Fill arrays */ - int idx = 0; - - /* Add wakeup fd first if present */ - if (wakeup_fd_present) { - fds[idx].fd = ua->wakeup_pipe[0]; - fds[idx].events = POLLIN; - fds[idx].revents = 0; - idx++; - } - - /* Add socket fds using efficient array traversal */ - int node_idx = 0; - for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) { - if (ua->sockets->sockets[i].active) { - struct socket_node* cur = &ua->sockets->sockets[i]; - fds[idx].fd = cur->fd; - fds[idx].events = 0; - fds[idx].revents = 0; - - if (cur->read_cbk) fds[idx].events |= POLLIN; - if (cur->write_cbk) fds[idx].events |= POLLOUT; - if (cur->except_cbk) fds[idx].events |= POLLPRI; - - if (nodes) { - nodes[node_idx] = cur; - } - idx++; - node_idx++; - } - } - - /* Call poll */ - int ret = poll(fds, total_fds, timeout_ms); - if (ret < 0) { - if (errno == EINTR) { - free(fds); - free(nodes); - return; - } - perror("poll"); - free(fds); - free(nodes); - return; - } - - /* Process timeouts that may have expired during poll */ - process_timeouts(ua); - - /* Process socket events */ - if (ret > 0) { - for (int i = 0; i < total_fds; i++) { - if (fds[i].revents == 0) continue; - - /* Handle wakeup fd separately */ - if (wakeup_fd_present && i == 0) { - if (fds[i].revents & POLLIN) { - drain_wakeup_pipe(ua); - } - continue; - } - - /* Socket event */ - int socket_idx = i - wakeup_fd_present; - struct socket_node* node = nodes[socket_idx]; - - /* Check for error conditions first */ - if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { - /* Treat as exceptional condition */ - if (node->except_cbk) { - node->except_cbk(node->fd, node->user_data); - } - } - - /* Exceptional data (out-of-band) */ - if (fds[i].revents & POLLPRI) { - if (node->except_cbk) { - node->except_cbk(node->fd, node->user_data); - } - } - - /* Read readiness */ - if (fds[i].revents & POLLIN) { - if (node->read_cbk) { - node->read_cbk(node->fd, node->user_data); - } - } - - /* Write readiness */ - if (fds[i].revents & POLLOUT) { - if (node->write_cbk) { - node->write_cbk(node->fd, node->user_data); - } - } - } - } - - free(fds); - free(nodes); -} - - - -// ========== Instance management functions ========== - -struct UASYNC* uasync_create(void) { - // Initialize debug system on first use - static int debug_initialized = 0; - if (!debug_initialized) { - debug_config_init(); - debug_initialized = 1; - } - - struct UASYNC* ua = malloc(sizeof(struct UASYNC)); - if (!ua) return NULL; - - memset(ua, 0, sizeof(struct UASYNC)); - ua->wakeup_pipe[0] = -1; - ua->wakeup_pipe[1] = -1; - ua->wakeup_initialized = 0; - - // Create wakeup pipe - if (pipe(ua->wakeup_pipe) < 0) { - DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno)); - // Continue without wakeup mechanism - ua->wakeup_pipe[0] = -1; - ua->wakeup_pipe[1] = -1; - } else { - ua->wakeup_initialized = 1; - // Set non-blocking on read end to avoid blocking if pipe is full - int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0); - if (flags >= 0) { - fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK); - } - } - - ua->sockets = socket_array_create(16); - if (!ua->sockets) { - if (ua->wakeup_initialized) { - close(ua->wakeup_pipe[0]); - close(ua->wakeup_pipe[1]); - } - free(ua); - return NULL; - } - - ua->timeout_heap = timeout_heap_create(16); - if (!ua->timeout_heap) { - socket_array_destroy(ua->sockets); - if (ua->wakeup_initialized) { - close(ua->wakeup_pipe[0]); - close(ua->wakeup_pipe[1]); - } - free(ua); - return NULL; - } - - // Set callback to free timeout nodes and update counters - timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); - - return ua; -} - -// Print all resources for debugging -void uasync_print_resources(struct UASYNC* ua, const char* prefix) { - if (!ua) { - printf("%s: NULL uasync instance\n", prefix); - return; - } - - printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua); - printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n", - ua->timer_alloc_count, ua->timer_free_count, - (ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); - printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n", - ua->socket_alloc_count, ua->socket_free_count, - (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); - +// uasync.c + +#include "u_async.h" +#include "debug_config.h" +#include +#include +#include +#include +#include +#include +#include +#include + + + +// Timeout node with safe cancellation +struct timeout_node { + void* arg; + timeout_callback_t callback; + uint64_t expiration_ms; // absolute expiration time in milliseconds + struct UASYNC* ua; // Pointer back to uasync instance for counter updates + int cancelled; // Cancellation flag +}; + +// Socket node with array-based storage +struct socket_node { + int fd; + socket_callback_t read_cbk; + socket_callback_t write_cbk; + socket_callback_t except_cbk; + void* user_data; + int active; // 1 if socket is active, 0 if freed (for reuse) +}; + +// Array-based socket management for O(1) operations +struct socket_array { + struct socket_node* sockets; // Dynamic array of socket nodes + int* fd_to_index; // FD to array index mapping + int* index_to_fd; // Array index to FD mapping + int capacity; // Total allocated capacity + int count; // Number of active sockets + int max_fd; // Maximum FD for bounds checking +}; + +static struct socket_array* socket_array_create(int initial_capacity); +static void socket_array_destroy(struct socket_array* sa); +static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data); +static int socket_array_remove(struct socket_array* sa, int fd); +static struct socket_node* socket_array_get(struct socket_array* sa, int fd); + +// No global instance - each module must use its own struct UASYNC instance + +// Array-based socket management implementation +static struct socket_array* socket_array_create(int initial_capacity) { + if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity + + struct socket_array* sa = malloc(sizeof(struct socket_array)); + if (!sa) return NULL; + + sa->sockets = calloc(initial_capacity, sizeof(struct socket_node)); + sa->fd_to_index = calloc(initial_capacity, sizeof(int)); + sa->index_to_fd = calloc(initial_capacity, sizeof(int)); + + if (!sa->sockets || !sa->fd_to_index || !sa->index_to_fd) { + free(sa->sockets); + free(sa->fd_to_index); + free(sa->index_to_fd); + free(sa); + return NULL; + } + + // Initialize mapping arrays to -1 (invalid) + for (int i = 0; i < initial_capacity; i++) { + sa->fd_to_index[i] = -1; + sa->index_to_fd[i] = -1; + sa->sockets[i].fd = -1; + sa->sockets[i].active = 0; + } + + sa->capacity = initial_capacity; + sa->count = 0; + sa->max_fd = -1; + + return sa; +} + +static void socket_array_destroy(struct socket_array* sa) { + if (!sa) return; + + free(sa->sockets); + free(sa->fd_to_index); + free(sa->index_to_fd); + free(sa); +} + +static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { + if (!sa || fd < 0 || fd >= FD_SETSIZE) return -1; + if (fd >= sa->capacity) { + // Need to resize - double the capacity + int new_capacity = sa->capacity * 2; + if (fd >= new_capacity) new_capacity = fd + 16; // Ensure enough space + + struct socket_node* new_sockets = realloc(sa->sockets, new_capacity * sizeof(struct socket_node)); + int* new_fd_to_index = realloc(sa->fd_to_index, new_capacity * sizeof(int)); + int* new_index_to_fd = realloc(sa->index_to_fd, new_capacity * sizeof(int)); + + if (!new_sockets || !new_fd_to_index || !new_index_to_fd) { + // Allocation failed + free(new_sockets); + free(new_fd_to_index); + free(new_index_to_fd); + return -1; + } + + // Initialize new elements + for (int i = sa->capacity; i < new_capacity; i++) { + new_fd_to_index[i] = -1; + new_index_to_fd[i] = -1; + new_sockets[i].fd = -1; + new_sockets[i].active = 0; + } + + sa->sockets = new_sockets; + sa->fd_to_index = new_fd_to_index; + sa->index_to_fd = new_index_to_fd; + sa->capacity = new_capacity; + } + + // Check if FD already exists + if (sa->fd_to_index[fd] != -1) return -1; // FD already exists + + // Find first free slot + int index = -1; + for (int i = 0; i < sa->capacity; i++) { + if (!sa->sockets[i].active) { + index = i; + break; + } + } + + if (index == -1) return -1; // No free slots (shouldn't happen) + + // Add the socket + sa->sockets[index].fd = fd; + sa->sockets[index].read_cbk = read_cbk; + sa->sockets[index].write_cbk = write_cbk; + sa->sockets[index].except_cbk = except_cbk; + sa->sockets[index].user_data = user_data; + sa->sockets[index].active = 1; + + sa->fd_to_index[fd] = index; + sa->index_to_fd[index] = fd; + sa->count++; + + if (fd > sa->max_fd) sa->max_fd = fd; + + return index; +} + +static int socket_array_remove(struct socket_array* sa, int fd) { + if (!sa || fd < 0 || fd >= sa->capacity) return -1; + + int index = sa->fd_to_index[fd]; + if (index == -1 || !sa->sockets[index].active) return -1; // FD not found + + // Mark as inactive + sa->sockets[index].active = 0; + sa->sockets[index].fd = -1; + sa->fd_to_index[fd] = -1; + sa->index_to_fd[index] = -1; + sa->count--; + + return 0; +} + +static struct socket_node* socket_array_get(struct socket_array* sa, int fd) { + if (!sa || fd < 0 || fd >= sa->capacity) return NULL; + + int index = sa->fd_to_index[fd]; + if (index == -1 || !sa->sockets[index].active) return NULL; + + return &sa->sockets[index]; +} + +// Callback to free timeout node and update counters +static void timeout_node_free_callback(void* user_data, void* data) { + struct UASYNC* ua = (struct UASYNC*)user_data; + struct timeout_node* node = (struct timeout_node*)data; + (void)node; // Not used directly, but keep for consistency + ua->timer_free_count++; + free(data); +} + +// Helper to get current time +static void get_current_time(struct timeval* tv) { + gettimeofday(tv, NULL); +} + + + +// Drain wakeup pipe - read all available bytes +static void drain_wakeup_pipe(struct UASYNC* ua) { + if (!ua || !ua->wakeup_initialized) return; + + char buf[64]; + while (1) { + ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf)); + if (n <= 0) break; + } +} + +// Helper to add timeval: tv += dt (timebase units) +static void timeval_add_tb(struct timeval* tv, int dt) { + tv->tv_usec += (dt % 10000) * 100; + tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000; + tv->tv_usec %= 1000000; +} + +// Convert timeval to milliseconds (uint64_t) +static uint64_t timeval_to_ms(const struct timeval* tv) { + return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL; +} + + + +// Simplified timeout handling without reference counting + +// Process expired timeouts with safe cancellation +static void process_timeouts(struct UASYNC* ua) { + if (!ua || !ua->timeout_heap) return; + + struct timeval now_tv; + get_current_time(&now_tv); + uint64_t now_ms = timeval_to_ms(&now_tv); + + while (1) { + TimeoutEntry entry; + if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break; + if (entry.expiration > now_ms) break; + + // Pop the expired timeout + timeout_heap_pop(ua->timeout_heap, &entry); + struct timeout_node* node = (struct timeout_node*)entry.data; + + if (node && node->callback && !node->cancelled) { + // Execute callback only if not cancelled + node->callback(node->arg); + } + + // Always free the node after processing + if (node && node->ua) { + node->ua->timer_free_count++; + } + free(node); + } +} + +// Compute time to next timeout +static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) { + if (!ua || !ua->timeout_heap) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + TimeoutEntry entry; + if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + struct timeval now_tv; + get_current_time(&now_tv); + uint64_t now_ms = timeval_to_ms(&now_tv); + + if (entry.expiration <= now_ms) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + uint64_t delta_ms = entry.expiration - now_ms; + tv->tv_sec = delta_ms / 1000; + tv->tv_usec = (delta_ms % 1000) * 1000; +} + + + +// Instance version +void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) { + if (!ua || timeout_tb < 0 || !callback) return NULL; + if (!ua->timeout_heap) return NULL; + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p", + ua, timeout_tb, arg, callback); + + struct timeout_node* node = malloc(sizeof(struct timeout_node)); + if (!node) { + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node"); + return NULL; + } + ua->timer_alloc_count++; + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)", + node, ua->timer_alloc_count); + + node->arg = arg; + node->callback = callback; + node->ua = ua; + node->cancelled = 0; + + // Calculate expiration time in milliseconds + struct timeval now; + get_current_time(&now); + timeval_add_tb(&now, timeout_tb); + node->expiration_ms = timeval_to_ms(&now); + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms", + node, (unsigned long long)node->expiration_ms); + + // Add to heap + if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap"); + free(node); + ua->timer_free_count++; // Balance the alloc counter + return NULL; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node); + return node; +} + + + +// Instance version +err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) { + if (!ua || !t_id || !ua->timeout_heap) { + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p", + ua, t_id, ua ? ua->timeout_heap : NULL); + return ERR_FAIL; + } + + struct timeout_node* node = (struct timeout_node*)t_id; + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms", + ua, t_id, node, (unsigned long long)node->expiration_ms); + + // Try to cancel from heap first + if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) { + // Successfully removed from heap - mark as cancelled and update counter + node->cancelled = 1; + node->callback = NULL; + ua->timer_free_count++; // Update counter for cancelled timer + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap, free_count=%zu", node, ua->timer_free_count); + free(node); // Free the cancelled timer node + return ERR_OK; + } + + // If not found in heap, it may have already expired or been invalid + return ERR_FAIL; +} + + +// Instance version +void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { + if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Bounds check + + int index = socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data); + if (index < 0) return NULL; + + ua->socket_alloc_count++; + + // Return pointer to the socket node (same as before for API compatibility) + return &ua->sockets->sockets[index]; +} + + + +// Instance version +err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) { + if (!ua || !s_id) return ERR_FAIL; + + struct socket_node* node = (struct socket_node*)s_id; + if (node->fd < 0) return ERR_FAIL; // Invalid node + + int result = socket_array_remove(ua->sockets, node->fd); + if (result != 0) return ERR_FAIL; + + ua->socket_free_count++; + return ERR_OK; +} + + + +void uasync_mainloop(struct UASYNC* ua) { + while (1) { + uasync_poll(ua, -1); /* infinite timeout */ + } +} + +// Instance version +void uasync_poll(struct UASYNC* ua, int timeout_tb) { + if (!ua) return; + + /* Process expired timeouts */ + process_timeouts(ua); + + /* Compute timeout for poll in milliseconds */ + int timeout_ms = -1; // infinite by default + + // Get next timeout from heap + struct timeval tv; + get_next_timeout(ua, &tv); + + if (tv.tv_sec > 0 || tv.tv_usec > 0 || (ua->timeout_heap && ua->timeout_heap->size > 0)) { + // Convert timeval to milliseconds, cap at INT_MAX + uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL; + if (ms > INT_MAX) ms = INT_MAX; + timeout_ms = (int)ms; + } + + /* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */ + if (timeout_tb >= 0) { + // Convert timebase (0.1 ms) to milliseconds + int user_timeout_ms = timeout_tb / 10; + if (timeout_tb % 10 != 0) user_timeout_ms++; // round up + + if (timeout_ms < 0 || user_timeout_ms < timeout_ms) { + timeout_ms = user_timeout_ms; + } + } + + /* Build pollfd array from socket array - O(1) per socket */ + int socket_count = ua->sockets ? ua->sockets->count : 0; + int wakeup_fd_present = ua->wakeup_initialized ? 1 : 0; + int total_fds = socket_count + wakeup_fd_present; + + if (total_fds == 0) { + /* No sockets and no wakeup fd, just wait for timeout */ + if (timeout_ms >= 0) { + /* usleep would be better but we just call poll with empty set */ + poll(NULL, 0, timeout_ms); + } else { + /* Infinite timeout with no sockets - should not happen in practice */ + return; + } + /* Check timeouts again after sleep */ + process_timeouts(ua); + return; + } + + struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd)); + struct socket_node** nodes = NULL; + if (socket_count > 0) { + nodes = malloc(socket_count * sizeof(struct socket_node*)); + } + if (!fds || (socket_count > 0 && !nodes)) { + free(fds); + free(nodes); + return; /* out of memory */ + } + + /* Fill arrays */ + int idx = 0; + + /* Add wakeup fd first if present */ + if (wakeup_fd_present) { + fds[idx].fd = ua->wakeup_pipe[0]; + fds[idx].events = POLLIN; + fds[idx].revents = 0; + idx++; + } + + /* Add socket fds using efficient array traversal */ + int node_idx = 0; + for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) { + if (ua->sockets->sockets[i].active) { + struct socket_node* cur = &ua->sockets->sockets[i]; + fds[idx].fd = cur->fd; + fds[idx].events = 0; + fds[idx].revents = 0; + + if (cur->read_cbk) fds[idx].events |= POLLIN; + if (cur->write_cbk) fds[idx].events |= POLLOUT; + if (cur->except_cbk) fds[idx].events |= POLLPRI; + + if (nodes) { + nodes[node_idx] = cur; + } + idx++; + node_idx++; + } + } + + /* Call poll */ + int ret = poll(fds, total_fds, timeout_ms); + if (ret < 0) { + if (errno == EINTR) { + free(fds); + free(nodes); + return; + } + perror("poll"); + free(fds); + free(nodes); + return; + } + + /* Process timeouts that may have expired during poll */ + process_timeouts(ua); + + /* Process socket events */ + if (ret > 0) { + for (int i = 0; i < total_fds; i++) { + if (fds[i].revents == 0) continue; + + /* Handle wakeup fd separately */ + if (wakeup_fd_present && i == 0) { + if (fds[i].revents & POLLIN) { + drain_wakeup_pipe(ua); + } + continue; + } + + /* Socket event */ + int socket_idx = i - wakeup_fd_present; + struct socket_node* node = nodes[socket_idx]; + + /* Check for error conditions first */ + if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { + /* Treat as exceptional condition */ + if (node->except_cbk) { + node->except_cbk(node->fd, node->user_data); + } + } + + /* Exceptional data (out-of-band) */ + if (fds[i].revents & POLLPRI) { + if (node->except_cbk) { + node->except_cbk(node->fd, node->user_data); + } + } + + /* Read readiness */ + if (fds[i].revents & POLLIN) { + if (node->read_cbk) { + node->read_cbk(node->fd, node->user_data); + } + } + + /* Write readiness */ + if (fds[i].revents & POLLOUT) { + if (node->write_cbk) { + node->write_cbk(node->fd, node->user_data); + } + } + } + } + + free(fds); + free(nodes); +} + + + +// ========== Instance management functions ========== + +struct UASYNC* uasync_create(void) { + // Initialize debug system on first use + static int debug_initialized = 0; + if (!debug_initialized) { + debug_config_init(); + debug_initialized = 1; + } + + struct UASYNC* ua = malloc(sizeof(struct UASYNC)); + if (!ua) return NULL; + + memset(ua, 0, sizeof(struct UASYNC)); + ua->wakeup_pipe[0] = -1; + ua->wakeup_pipe[1] = -1; + ua->wakeup_initialized = 0; + + // Create wakeup pipe + if (pipe(ua->wakeup_pipe) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno)); + // Continue without wakeup mechanism + ua->wakeup_pipe[0] = -1; + ua->wakeup_pipe[1] = -1; + } else { + ua->wakeup_initialized = 1; + // Set non-blocking on read end to avoid blocking if pipe is full + int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0); + if (flags >= 0) { + fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK); + } + } + + ua->sockets = socket_array_create(16); + if (!ua->sockets) { + if (ua->wakeup_initialized) { + close(ua->wakeup_pipe[0]); + close(ua->wakeup_pipe[1]); + } + free(ua); + return NULL; + } + + ua->timeout_heap = timeout_heap_create(16); + if (!ua->timeout_heap) { + socket_array_destroy(ua->sockets); + if (ua->wakeup_initialized) { + close(ua->wakeup_pipe[0]); + close(ua->wakeup_pipe[1]); + } + free(ua); + return NULL; + } + + // Set callback to free timeout nodes and update counters + timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); + + return ua; +} + +// Print all resources for debugging +void uasync_print_resources(struct UASYNC* ua, const char* prefix) { + if (!ua) { + printf("%s: NULL uasync instance\n", prefix); + return; + } + + printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua); + printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n", + ua->timer_alloc_count, ua->timer_free_count, + (ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); + printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n", + ua->socket_alloc_count, ua->socket_free_count, + (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); + // Показать активные таймеры if (ua->timeout_heap) { size_t active_timers = 0; - TimeoutEntry entry; - // Создаем временную копию кучи для подсчета - TimeoutHeap* temp_heap = timeout_heap_create(16); - if (temp_heap) { - // Копируем все активные таймеры - while (timeout_heap_pop(ua->timeout_heap, &entry) == 0) { - if (!entry.deleted) { - active_timers++; - struct timeout_node* node = (struct timeout_node*)entry.data; - printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n", - node, (unsigned long long)entry.expiration, node->cancelled); - } - timeout_heap_push(temp_heap, entry.expiration, entry.data); - } - // Возвращаем таймеры обратно - while (timeout_heap_pop(temp_heap, &entry) == 0) { - timeout_heap_push(ua->timeout_heap, entry.expiration, entry.data); + // Безопасное чтение без извлечения - просто итерируем по массиву + for (size_t i = 0; i < ua->timeout_heap->size; i++) { + if (!ua->timeout_heap->heap[i].deleted) { + active_timers++; + struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data; + printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n", + node, (unsigned long long)ua->timeout_heap->heap[i].expiration, node->cancelled); } - timeout_heap_destroy(temp_heap); } printf(" Active timers in heap: %zu\n", active_timers); } - - // Показать активные сокеты - if (ua->sockets) { - int active_sockets = 0; - printf(" Socket array capacity: %d, active: %d\n", - ua->sockets->capacity, ua->sockets->count); - for (int i = 0; i < ua->sockets->capacity; i++) { - if (ua->sockets->sockets[i].active) { - active_sockets++; - printf(" Socket: fd=%d, active=%d\n", - ua->sockets->sockets[i].fd, - ua->sockets->sockets[i].active); - } - } - printf(" Total active sockets: %d\n", active_sockets); - } - - printf("🔚 %s: End of resource report\n\n", prefix); -} - -void uasync_destroy(struct UASYNC* ua) { - if (!ua) return; - - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua); - + + // Показать активные сокеты + if (ua->sockets) { + int active_sockets = 0; + printf(" Socket array capacity: %d, active: %d\n", + ua->sockets->capacity, ua->sockets->count); + for (int i = 0; i < ua->sockets->capacity; i++) { + if (ua->sockets->sockets[i].active) { + active_sockets++; + printf(" Socket: fd=%d, active=%d\n", + ua->sockets->sockets[i].fd, + ua->sockets->sockets[i].active); + } + } + printf(" Total active sockets: %d\n", active_sockets); + } + + printf("🔚 %s: End of resource report\n\n", prefix); +} + +void uasync_destroy(struct UASYNC* ua, int close_fds) { + if (!ua) return; + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua); + // Диагностика ресурсов перед очисткой uasync_print_resources(ua, "BEFORE_DESTROY"); - - // Check for potential memory leaks - if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { - DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu", - ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); - DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd", - ua->timer_alloc_count, ua->timer_free_count, - (ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); - DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd", - ua->socket_alloc_count, ua->socket_free_count, - (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); - // Continue cleanup, will abort after if leaks remain - } - + + // Check for potential memory leaks + if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu", + ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd", + ua->timer_alloc_count, ua->timer_free_count, + (ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd", + ua->socket_alloc_count, ua->socket_free_count, + (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); + // Continue cleanup, will abort after if leaks remain + } + // Free all remaining timeouts if (ua->timeout_heap) { size_t freed_count = 0; @@ -736,96 +701,102 @@ void uasync_destroy(struct UASYNC* ua) { TimeoutEntry entry; if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break; struct timeout_node* node = (struct timeout_node*)entry.data; - DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freeing timer node %p (expired=%llu ms)", - node, (unsigned long long)entry.expiration); - ua->timer_free_count++; - freed_count++; - free(node); + + // Only free if not deleted (deleted nodes are freed by the heap's free callback) + if (!entry.deleted) { + DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freeing timer node %p (expired=%llu ms)", + node, (unsigned long long)entry.expiration); + ua->timer_free_count++; + freed_count++; + free(node); + } } DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freed %zu timer nodes in destroy, heap freed_count = %zu", freed_count, ua->timeout_heap->freed_count); timeout_heap_destroy(ua->timeout_heap); } - - // Free all socket nodes using array approach - if (ua->sockets) { - // Count and free all active sockets - int freed_count = 0; - for (int i = 0; i < ua->sockets->capacity; i++) { - if (ua->sockets->sockets[i].active) { - ua->socket_free_count++; - freed_count++; - } - } - DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count); - socket_array_destroy(ua->sockets); - } - - // Close wakeup pipe - if (ua->wakeup_initialized) { - close(ua->wakeup_pipe[0]); - close(ua->wakeup_pipe[1]); - } - - // Final leak check - if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { - DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu", - ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); - DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd", - ua->timer_alloc_count, ua->timer_free_count, - (ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); - DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd", - ua->socket_alloc_count, ua->socket_free_count, - (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); - abort(); - } - - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua); - free(ua); -} - -void uasync_init_instance(struct UASYNC* ua) { - if (!ua) return; - - // Initialize socket array if not present - if (!ua->sockets) { - ua->sockets = socket_array_create(16); - } - - if (!ua->timeout_heap) { - ua->timeout_heap = timeout_heap_create(16); - if (ua->timeout_heap) { - timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); - } - } -} - -// Debug statistics -void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) { - if (!ua) return; - if (timer_alloc) *timer_alloc = ua->timer_alloc_count; - if (timer_free) *timer_free = ua->timer_free_count; - if (socket_alloc) *socket_alloc = ua->socket_alloc_count; - if (socket_free) *socket_free = ua->socket_free_count; -} - -// Get global instance for backward compatibility - -// Wakeup mechanism -int uasync_wakeup(struct UASYNC* ua) { - if (!ua || !ua->wakeup_initialized) return -1; - - char byte = 0; - ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1); - if (ret != 1) { - // Don't print error from signal handler - return -1; - } - return 0; -} - -int uasync_get_wakeup_fd(struct UASYNC* ua) { - if (!ua || !ua->wakeup_initialized) return -1; - return ua->wakeup_pipe[1]; -} - + + // Free all socket nodes using array approach + if (ua->sockets) { + // Count and free all active sockets + int freed_count = 0; + for (int i = 0; i < ua->sockets->capacity; i++) { + if (ua->sockets->sockets[i].active) { + if (close_fds && ua->sockets->sockets[i].fd >= 0) { + close(ua->sockets->sockets[i].fd); + } + ua->socket_free_count++; + freed_count++; + } + } + DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count); + socket_array_destroy(ua->sockets); + } + + // Close wakeup pipe + if (ua->wakeup_initialized) { + close(ua->wakeup_pipe[0]); + close(ua->wakeup_pipe[1]); + } + + // Final leak check + if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu", + ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd", + ua->timer_alloc_count, ua->timer_free_count, + (ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd", + ua->socket_alloc_count, ua->socket_free_count, + (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); + abort(); + } + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua); + free(ua); +} + +void uasync_init_instance(struct UASYNC* ua) { + if (!ua) return; + + // Initialize socket array if not present + if (!ua->sockets) { + ua->sockets = socket_array_create(16); + } + + if (!ua->timeout_heap) { + ua->timeout_heap = timeout_heap_create(16); + if (ua->timeout_heap) { + timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); + } + } +} + +// Debug statistics +void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) { + if (!ua) return; + if (timer_alloc) *timer_alloc = ua->timer_alloc_count; + if (timer_free) *timer_free = ua->timer_free_count; + if (socket_alloc) *socket_alloc = ua->socket_alloc_count; + if (socket_free) *socket_free = ua->socket_free_count; +} + +// Get global instance for backward compatibility + +// Wakeup mechanism +int uasync_wakeup(struct UASYNC* ua) { + if (!ua || !ua->wakeup_initialized) return -1; + + char byte = 0; + ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1); + if (ret != 1) { + // Don't print error from signal handler + return -1; + } + return 0; +} + +int uasync_get_wakeup_fd(struct UASYNC* ua) { + if (!ua || !ua->wakeup_initialized) return -1; + return ua->wakeup_pipe[1]; +} diff --git a/lib/u_async.c1 b/lib/u_async.c1 new file mode 100755 index 00000000..3b4bfb2e --- /dev/null +++ b/lib/u_async.c1 @@ -0,0 +1,830 @@ +// uasync.c + +#include "u_async.h" +#include "debug_config.h" +#include +#include +#include +#include +#include +#include +#include +#include + + + +// Timeout node with safe cancellation +struct timeout_node { + void* arg; + timeout_callback_t callback; + uint64_t expiration_ms; // absolute expiration time in milliseconds + struct UASYNC* ua; // Pointer back to uasync instance for counter updates + int cancelled; // Cancellation flag +}; + +// Socket node with array-based storage +struct socket_node { + int fd; + socket_callback_t read_cbk; + socket_callback_t write_cbk; + socket_callback_t except_cbk; + void* user_data; + int active; // 1 if socket is active, 0 if freed (for reuse) +}; + +// Array-based socket management for O(1) operations +struct socket_array { + struct socket_node* sockets; // Dynamic array of socket nodes + int* fd_to_index; // FD to array index mapping + int* index_to_fd; // Array index to FD mapping + int capacity; // Total allocated capacity + int count; // Number of active sockets + int max_fd; // Maximum FD for bounds checking +}; + +static struct socket_array* socket_array_create(int initial_capacity); +static void socket_array_destroy(struct socket_array* sa); +static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data); +static int socket_array_remove(struct socket_array* sa, int fd); +static struct socket_node* socket_array_get(struct socket_array* sa, int fd); +static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds); + +// No global instance - each module must use its own struct UASYNC instance + +// Array-based socket management implementation +static struct socket_array* socket_array_create(int initial_capacity) { + if (initial_capacity < 4) initial_capacity = 4; // Minimum capacity + + struct socket_array* sa = malloc(sizeof(struct socket_array)); + if (!sa) return NULL; + + sa->sockets = calloc(initial_capacity, sizeof(struct socket_node)); + sa->fd_to_index = calloc(initial_capacity, sizeof(int)); + sa->index_to_fd = calloc(initial_capacity, sizeof(int)); + + if (!sa->sockets || !sa->fd_to_index || !sa->index_to_fd) { + free(sa->sockets); + free(sa->fd_to_index); + free(sa->index_to_fd); + free(sa); + return NULL; + } + + // Initialize mapping arrays to -1 (invalid) + for (int i = 0; i < initial_capacity; i++) { + sa->fd_to_index[i] = -1; + sa->index_to_fd[i] = -1; + sa->sockets[i].fd = -1; + sa->sockets[i].active = 0; + } + + sa->capacity = initial_capacity; + sa->count = 0; + sa->max_fd = -1; + + return sa; +} + +static void socket_array_destroy(struct socket_array* sa) { + if (!sa) return; + + free(sa->sockets); + free(sa->fd_to_index); + free(sa->index_to_fd); + free(sa); +} + +static int socket_array_add(struct socket_array* sa, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { + if (!sa || fd < 0 || fd >= FD_SETSIZE) return -1; + if (fd >= sa->capacity) { + // Need to resize - double the capacity + int new_capacity = sa->capacity * 2; + if (fd >= new_capacity) new_capacity = fd + 16; // Ensure enough space + + struct socket_node* new_sockets = realloc(sa->sockets, new_capacity * sizeof(struct socket_node)); + int* new_fd_to_index = realloc(sa->fd_to_index, new_capacity * sizeof(int)); + int* new_index_to_fd = realloc(sa->index_to_fd, new_capacity * sizeof(int)); + + if (!new_sockets || !new_fd_to_index || !new_index_to_fd) { + // Allocation failed + free(new_sockets); + free(new_fd_to_index); + free(new_index_to_fd); + return -1; + } + + // Initialize new elements + for (int i = sa->capacity; i < new_capacity; i++) { + new_fd_to_index[i] = -1; + new_index_to_fd[i] = -1; + new_sockets[i].fd = -1; + new_sockets[i].active = 0; + } + + sa->sockets = new_sockets; + sa->fd_to_index = new_fd_to_index; + sa->index_to_fd = new_index_to_fd; + sa->capacity = new_capacity; + } + + // Check if FD already exists + if (sa->fd_to_index[fd] != -1) return -1; // FD already exists + + // Find first free slot + int index = -1; + for (int i = 0; i < sa->capacity; i++) { + if (!sa->sockets[i].active) { + index = i; + break; + } + } + + if (index == -1) return -1; // No free slots (shouldn't happen) + + // Add the socket + sa->sockets[index].fd = fd; + sa->sockets[index].read_cbk = read_cbk; + sa->sockets[index].write_cbk = write_cbk; + sa->sockets[index].except_cbk = except_cbk; + sa->sockets[index].user_data = user_data; + sa->sockets[index].active = 1; + + sa->fd_to_index[fd] = index; + sa->index_to_fd[index] = fd; + sa->count++; + + if (fd > sa->max_fd) sa->max_fd = fd; + + return index; +} + +static int socket_array_remove(struct socket_array* sa, int fd) { + if (!sa || fd < 0 || fd >= sa->capacity) return -1; + + int index = sa->fd_to_index[fd]; + if (index == -1 || !sa->sockets[index].active) return -1; // FD not found + + // Mark as inactive + sa->sockets[index].active = 0; + sa->sockets[index].fd = -1; + sa->fd_to_index[fd] = -1; + sa->index_to_fd[index] = -1; + sa->count--; + + return 0; +} + +static struct socket_node* socket_array_get(struct socket_array* sa, int fd) { + if (!sa || fd < 0 || fd >= sa->capacity) return NULL; + + int index = sa->fd_to_index[fd]; + if (index == -1 || !sa->sockets[index].active) return NULL; + + return &sa->sockets[index]; +} + +static int socket_array_build_pollfd(struct socket_array* sa, struct pollfd* fds, int max_fds) { + if (!sa || !fds || max_fds <= 0) return 0; + + int count = 0; + for (int i = 0; i < sa->capacity && count < max_fds; i++) { + if (sa->sockets[i].active) { + fds[count].fd = sa->sockets[i].fd; + fds[count].events = 0; + if (sa->sockets[i].read_cbk) fds[count].events |= POLLIN; + if (sa->sockets[i].write_cbk) fds[count].events |= POLLOUT; + if (sa->sockets[i].except_cbk) fds[count].events |= POLLERR; + fds[count].revents = 0; + count++; + } + } + + return count; +} + +// Callback to free timeout node and update counters +static void timeout_node_free_callback(void* user_data, void* data) { + struct UASYNC* ua = (struct UASYNC*)user_data; + struct timeout_node* node = (struct timeout_node*)data; + (void)node; // Not used directly, but keep for consistency + ua->timer_free_count++; + free(data); +} + +// Helper to get current time +static void get_current_time(struct timeval* tv) { + gettimeofday(tv, NULL); +} + + + +// Drain wakeup pipe - read all available bytes +static void drain_wakeup_pipe(struct UASYNC* ua) { + if (!ua || !ua->wakeup_initialized) return; + + char buf[64]; + while (1) { + ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf)); + if (n <= 0) break; + } +} + +// Helper to add timeval: tv += dt (timebase units) +static void timeval_add_tb(struct timeval* tv, int dt) { + tv->tv_usec += (dt % 10000) * 100; + tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000; + tv->tv_usec %= 1000000; +} + +// Convert timeval to milliseconds (uint64_t) +static uint64_t timeval_to_ms(const struct timeval* tv) { + return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL; +} + + + +// Simplified timeout handling without reference counting + +// Process expired timeouts with safe cancellation +static void process_timeouts(struct UASYNC* ua) { + if (!ua || !ua->timeout_heap) return; + + struct timeval now_tv; + get_current_time(&now_tv); + uint64_t now_ms = timeval_to_ms(&now_tv); + + while (1) { + TimeoutEntry entry; + if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break; + if (entry.expiration > now_ms) break; + + // Pop the expired timeout + timeout_heap_pop(ua->timeout_heap, &entry); + struct timeout_node* node = (struct timeout_node*)entry.data; + + if (node && node->callback && !node->cancelled) { + // Execute callback only if not cancelled + node->callback(node->arg); + } + + // Always free the node after processing + if (node && node->ua) { + node->ua->timer_free_count++; + } + free(node); + } +} + +// Compute time to next timeout +static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) { + if (!ua || !ua->timeout_heap) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + TimeoutEntry entry; + if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + struct timeval now_tv; + get_current_time(&now_tv); + uint64_t now_ms = timeval_to_ms(&now_tv); + + if (entry.expiration <= now_ms) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + uint64_t delta_ms = entry.expiration - now_ms; + if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow + delta_ms = 86400000; + } + tv->tv_sec = delta_ms / 1000; + tv->tv_usec = (delta_ms % 1000) * 1000; +} + + + +// Instance version +void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) { + if (!ua || timeout_tb < 0 || !callback) return NULL; + if (!ua->timeout_heap) return NULL; + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p", + ua, timeout_tb, arg, callback); + + struct timeout_node* node = malloc(sizeof(struct timeout_node)); + if (!node) { + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node"); + return NULL; + } + ua->timer_alloc_count++; + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)", + node, ua->timer_alloc_count); + + node->arg = arg; + node->callback = callback; + node->ua = ua; + node->cancelled = 0; + + // Calculate expiration time in milliseconds + struct timeval now; + get_current_time(&now); + timeval_add_tb(&now, timeout_tb); + node->expiration_ms = timeval_to_ms(&now); + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms", + node, (unsigned long long)node->expiration_ms); + + // Add to heap + if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap"); + free(node); + ua->timer_free_count++; // Balance the alloc counter + return NULL; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node); + return node; +} + + + +// Instance version +err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) { + if (!ua || !t_id || !ua->timeout_heap) { + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p", + ua, t_id, ua ? ua->timeout_heap : NULL); + return ERR_FAIL; + } + + struct timeout_node* node = (struct timeout_node*)t_id; + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms", + ua, t_id, node, (unsigned long long)node->expiration_ms); + + // Try to cancel from heap first + if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) { + // Successfully removed from heap - mark as cancelled and update counter + node->cancelled = 1; + node->callback = NULL; + ua->timer_free_count++; // Update counter for cancelled timer + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap, free_count=%zu", node, ua->timer_free_count); + free(node); // Free the cancelled timer node + return ERR_OK; + } + node->cancelled = 1; + node->callback = NULL; + return ERR_OK; +} // Successfully cancelled (marked) + + +// Instance version +void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { + if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Bounds check + + int index = socket_array_add(ua->sockets, fd, read_cbk, write_cbk, except_cbk, user_data); + if (index < 0) return NULL; + + ua->socket_alloc_count++; + + // Return pointer to the socket node (same as before for API compatibility) + return &ua->sockets->sockets[index]; +} + + + +// Instance version +err_t uasync_remove_socket(struct UASYNC* ua, void* s_id) { + if (!ua || !s_id) return ERR_FAIL; + + struct socket_node* node = (struct socket_node*)s_id; + if (node->fd < 0) return ERR_FAIL; // Invalid node + + int result = socket_array_remove(ua->sockets, node->fd); + if (result != 0) return ERR_FAIL; + + ua->socket_free_count++; + return ERR_OK; +} + + + +void uasync_mainloop(struct UASYNC* ua) { + while (1) { + uasync_poll(ua, -1); /* infinite timeout */ + } +} + +// Instance version +void uasync_poll(struct UASYNC* ua, int timeout_tb) { + if (!ua) return; + + /* Process expired timeouts */ + process_timeouts(ua); + + /* Compute timeout for poll in milliseconds */ + int timeout_ms = -1; // infinite by default + + // Get next timeout from heap + struct timeval tv; + get_next_timeout(ua, &tv); + + if (tv.tv_sec > 0 || tv.tv_usec > 0 || (ua->timeout_heap && ua->timeout_heap->size > 0)) { + // Convert timeval to milliseconds, cap at INT_MAX + uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL; + if (ms > INT_MAX) ms = INT_MAX; + timeout_ms = (int)ms; + } + + /* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */ + if (timeout_tb >= 0) { + // Convert timebase (0.1 ms) to milliseconds + int user_timeout_ms = timeout_tb / 10; + if (timeout_tb % 10 != 0) user_timeout_ms++; // round up + + if (timeout_ms < 0 || user_timeout_ms < timeout_ms) { + timeout_ms = user_timeout_ms; + } + } + + /* Build pollfd array from socket array - O(1) per socket */ + int socket_count = ua->sockets ? ua->sockets->count : 0; + int wakeup_fd_present = ua->wakeup_initialized ? 1 : 0; + int total_fds = socket_count + wakeup_fd_present; + + if (total_fds == 0) { + /* No sockets and no wakeup fd, just wait for timeout */ + if (timeout_ms >= 0) { + /* usleep would be better but we just call poll with empty set */ + struct pollfd dummy; + poll(&dummy, 0, timeout_ms); + } else { + /* Infinite timeout with no sockets - should not happen in practice */ + return; + } + /* Check timeouts again after sleep */ + process_timeouts(ua); + return; + } + + struct pollfd* fds = malloc(total_fds * sizeof(struct pollfd)); + struct socket_node** nodes = NULL; + if (socket_count > 0) { + nodes = malloc(socket_count * sizeof(struct socket_node*)); + } + if (!fds || (socket_count > 0 && !nodes)) { + free(fds); + free(nodes); + return; /* out of memory */ + } + + /* Fill arrays */ + int idx = 0; + + /* Add wakeup fd first if present */ + if (wakeup_fd_present) { + fds[idx].fd = ua->wakeup_pipe[0]; + fds[idx].events = POLLIN; + fds[idx].revents = 0; + idx++; + } + + /* Add socket fds using efficient array traversal */ + int node_idx = 0; + for (int i = 0; i < ua->sockets->capacity && node_idx < socket_count; i++) { + if (ua->sockets->sockets[i].active) { + struct socket_node* cur = &ua->sockets->sockets[i]; + fds[idx].fd = cur->fd; + fds[idx].events = 0; + fds[idx].revents = 0; + + if (cur->read_cbk) fds[idx].events |= POLLIN; + if (cur->write_cbk) fds[idx].events |= POLLOUT; + if (cur->except_cbk) fds[idx].events |= POLLPRI; + + if (nodes) { + nodes[node_idx] = cur; + } + idx++; + node_idx++; + } + } + + /* Call poll */ + int ret = poll(fds, total_fds, timeout_ms); + if (ret < 0) { + if (errno == EINTR) { + free(fds); + free(nodes); + return; + } + perror("poll"); + free(fds); + free(nodes); + return; + } + + /* Process timeouts that may have expired during poll */ + process_timeouts(ua); + + /* Process socket events */ + if (ret > 0) { + for (int i = 0; i < total_fds; i++) { + if (fds[i].revents == 0) continue; + + /* Handle wakeup fd separately */ + if (wakeup_fd_present && i == 0) { + if (fds[i].revents & POLLIN) { + drain_wakeup_pipe(ua); + } + continue; + } + + /* Socket event */ + int socket_idx = i - wakeup_fd_present; + struct socket_node* node = nodes[socket_idx]; + + /* Check for error conditions first */ + if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { + /* Treat as exceptional condition */ + if (node->except_cbk) { + node->except_cbk(node->fd, node->user_data); + } + } + + /* Exceptional data (out-of-band) */ + if (fds[i].revents & POLLPRI) { + if (node->except_cbk) { + node->except_cbk(node->fd, node->user_data); + } + } + + /* Read readiness */ + if (fds[i].revents & POLLIN) { + if (node->read_cbk) { + node->read_cbk(node->fd, node->user_data); + } + } + + /* Write readiness */ + if (fds[i].revents & POLLOUT) { + if (node->write_cbk) { + node->write_cbk(node->fd, node->user_data); + } + } + } + } + + free(fds); + free(nodes); +} + + + +// ========== Instance management functions ========== + +struct UASYNC* uasync_create(void) { + // Initialize debug system on first use + static int debug_initialized = 0; + if (!debug_initialized) { + debug_config_init(); + debug_initialized = 1; + } + + struct UASYNC* ua = malloc(sizeof(struct UASYNC)); + if (!ua) return NULL; + + memset(ua, 0, sizeof(struct UASYNC)); + ua->wakeup_pipe[0] = -1; + ua->wakeup_pipe[1] = -1; + ua->wakeup_initialized = 0; + + // Create wakeup pipe + if (pipe(ua->wakeup_pipe) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno)); + // Continue without wakeup mechanism + ua->wakeup_pipe[0] = -1; + ua->wakeup_pipe[1] = -1; + } else { + ua->wakeup_initialized = 1; + // Set non-blocking on read end to avoid blocking if pipe is full + int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0); + if (flags >= 0) { + fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK); + } + } + + ua->sockets = socket_array_create(16); + if (!ua->sockets) { + if (ua->wakeup_initialized) { + close(ua->wakeup_pipe[0]); + close(ua->wakeup_pipe[1]); + } + free(ua); + return NULL; + } + + ua->timeout_heap = timeout_heap_create(16); + if (!ua->timeout_heap) { + socket_array_destroy(ua->sockets); + if (ua->wakeup_initialized) { + close(ua->wakeup_pipe[0]); + close(ua->wakeup_pipe[1]); + } + free(ua); + return NULL; + } + + // Set callback to free timeout nodes and update counters + timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); + + return ua; +} + +// Print all resources for debugging +void uasync_print_resources(struct UASYNC* ua, const char* prefix) { + if (!ua) { + printf("%s: NULL uasync instance\n", prefix); + return; + } + + printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua); + printf(" Timer Statistics: allocated=%zu, freed=%zu, active=%zd\n", + ua->timer_alloc_count, ua->timer_free_count, + (ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); + printf(" Socket Statistics: allocated=%zu, freed=%zu, active=%zd\n", + ua->socket_alloc_count, ua->socket_free_count, + (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); + + // Показать активные таймеры + if (ua->timeout_heap) { + size_t active_timers = 0; + TimeoutEntry entry; + // Создаем временную копию кучи для подсчета + TimeoutHeap* temp_heap = timeout_heap_create(16); + if (temp_heap) { + // Копируем все активные таймеры + while (timeout_heap_pop(ua->timeout_heap, &entry) == 0) { + if (!entry.deleted) { + active_timers++; + struct timeout_node* node = (struct timeout_node*)entry.data; + printf(" Timer: node=%p, expires=%llu ms, cancelled=%d\n", + node, (unsigned long long)entry.expiration, node->cancelled); + } + timeout_heap_push(temp_heap, entry.expiration, entry.data); + } + // Возвращаем таймеры обратно + while (timeout_heap_pop(temp_heap, &entry) == 0) { + timeout_heap_push(ua->timeout_heap, entry.expiration, entry.data); + } + timeout_heap_destroy(temp_heap); + } + printf(" Active timers in heap: %zu\n", active_timers); + } + + // Показать активные сокеты + if (ua->sockets) { + int active_sockets = 0; + printf(" Socket array capacity: %d, active: %d\n", + ua->sockets->capacity, ua->sockets->count); + for (int i = 0; i < ua->sockets->capacity; i++) { + if (ua->sockets->sockets[i].active) { + active_sockets++; + printf(" Socket: fd=%d, active=%d\n", + ua->sockets->sockets[i].fd, + ua->sockets->sockets[i].active); + } + } + printf(" Total active sockets: %d\n", active_sockets); + } + + printf("🔚 %s: End of resource report\n\n", prefix); +} + +void uasync_destroy(struct UASYNC* ua) { + if (!ua) return; + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua); + + // Диагностика ресурсов перед очисткой + uasync_print_resources(ua, "BEFORE_DESTROY"); + + // Check for potential memory leaks + if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu", + ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, freed=%zu, diff=%zd", + ua->timer_alloc_count, ua->timer_free_count, + (ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, freed=%zu, diff=%zd", + ua->socket_alloc_count, ua->socket_free_count, + (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); + // Continue cleanup, will abort after if leaks remain + } + + // Free all remaining timeouts + if (ua->timeout_heap) { + size_t freed_count = 0; + while (1) { + TimeoutEntry entry; + if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break; + struct timeout_node* node = (struct timeout_node*)entry.data; + DEBUG_TRACE(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freeing timer node %p (expired=%llu ms)", + node, (unsigned long long)entry.expiration); + ua->timer_free_count++; + freed_count++; + free(node); + } + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: freed %zu timer nodes in destroy, heap freed_count = %zu", + freed_count, ua->timeout_heap->freed_count); + timeout_heap_destroy(ua->timeout_heap); + } + + // Free all socket nodes using array approach + if (ua->sockets) { + // Count and free all active sockets + int freed_count = 0; + for (int i = 0; i < ua->sockets->capacity; i++) { + if (ua->sockets->sockets[i].active) { + ua->socket_free_count++; + freed_count++; + } + } + DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count); + socket_array_destroy(ua->sockets); + } + + // Close wakeup pipe + if (ua->wakeup_initialized) { + close(ua->wakeup_pipe[0]); + close(ua->wakeup_pipe[1]); + } + + // Final leak check + if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu", + ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count); + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Timer leak: allocated=%zu, freed=%zu, diff=%zd", + ua->timer_alloc_count, ua->timer_free_count, + (ssize_t)(ua->timer_alloc_count - ua->timer_free_count)); + DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "FINAL Socket leak: allocated=%zu, freed=%zu, diff=%zd", + ua->socket_alloc_count, ua->socket_free_count, + (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); + abort(); + } + + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua); + free(ua); +} + +void uasync_init_instance(struct UASYNC* ua) { + if (!ua) return; + + // Initialize socket array if not present + if (!ua->sockets) { + ua->sockets = socket_array_create(16); + } + + if (!ua->timeout_heap) { + ua->timeout_heap = timeout_heap_create(16); + if (ua->timeout_heap) { + timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); + } + } +} + +// Debug statistics +void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) { + if (!ua) return; + if (timer_alloc) *timer_alloc = ua->timer_alloc_count; + if (timer_free) *timer_free = ua->timer_free_count; + if (socket_alloc) *socket_alloc = ua->socket_alloc_count; + if (socket_free) *socket_free = ua->socket_free_count; +} + +// Get global instance for backward compatibility + +// Wakeup mechanism +int uasync_wakeup(struct UASYNC* ua) { + if (!ua || !ua->wakeup_initialized) return -1; + + char byte = 0; + ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1); + if (ret != 1) { + // Don't print error from signal handler + return -1; + } + return 0; +} + +int uasync_get_wakeup_fd(struct UASYNC* ua) { + if (!ua || !ua->wakeup_initialized) return -1; + return ua->wakeup_pipe[1]; +} diff --git a/lib/u_async.h b/lib/u_async.h index 86d52386..c0c45dfa 100644 --- a/lib/u_async.h +++ b/lib/u_async.h @@ -1,69 +1,69 @@ -// uasync.h - -// модуль асинхронных операций. добавляем сокеты и таймауты и mainloop их обслуживает. - -#ifndef UASYNC_H -#define UASYNC_H - -#include -#include -#include "timeout_heap.h" - -typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout -typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket -// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е - - -// Error type -typedef int err_t; -#define ERR_OK 0 -#define ERR_FAIL -1 - -// Uasync instance structure -struct UASYNC { - TimeoutHeap* timeout_heap; // Heap for timeout management - struct socket_array* sockets; // Array-based socket management - // Debug counters for memory allocation tracking - size_t timer_alloc_count; - size_t timer_free_count; - size_t socket_alloc_count; - size_t socket_free_count; - // Wakeup pipe for interrupting poll - int wakeup_pipe[2]; // [0] read, [1] write - int wakeup_initialized; -}; - -// Type definitions -typedef struct UASYNC uasync_t; -typedef struct UASYNC UASYNC_t; - -// Instance API - основной API для работы с uasync -struct UASYNC* uasync_create(void); -void uasync_destroy(struct UASYNC* ua); -void uasync_init_instance(struct UASYNC* ua); - -// Timeouts, timebase = 0.1 mS -void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback); -err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id); - -// Sockets -void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg); -err_t uasync_remove_socket(struct UASYNC* ua, void* s_id); - -// Single iteration of event loop with timeout (timebase units) -void uasync_poll(struct UASYNC* ua, int timeout_tb); - -// Mainloop (бесконечный цикл, __noreturn) -void uasync_mainloop(struct UASYNC* ua); - -// Debug statistics -void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free); - -// Print all resources (timers, sockets) for debugging -void uasync_print_resources(struct UASYNC* ua, const char* prefix); - -// Wakeup mechanism for interrupting poll -int uasync_wakeup(struct UASYNC* ua); -int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers) - -#endif // UASYNC_H +// uasync.h + +// модуль асинхронных операций. добавляем сокеты и таймауты и mainloop их обслуживает. + +#ifndef UASYNC_H +#define UASYNC_H + +#include +#include +#include "timeout_heap.h" + +typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout +typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket +// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е + + +// Error type +typedef int err_t; +#define ERR_OK 0 +#define ERR_FAIL -1 + +// Uasync instance structure +struct UASYNC { + TimeoutHeap* timeout_heap; // Heap for timeout management + struct socket_array* sockets; // Array-based socket management + // Debug counters for memory allocation tracking + size_t timer_alloc_count; + size_t timer_free_count; + size_t socket_alloc_count; + size_t socket_free_count; + // Wakeup pipe for interrupting poll + int wakeup_pipe[2]; // [0] read, [1] write + int wakeup_initialized; +}; + +// Type definitions +typedef struct UASYNC uasync_t; +typedef struct UASYNC UASYNC_t; + +// Instance API - основной API для работы с uasync +struct UASYNC* uasync_create(void); +void uasync_destroy(struct UASYNC* ua, int close_fds); +void uasync_init_instance(struct UASYNC* ua); + +// Timeouts, timebase = 0.1 mS +void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback); +err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id); + +// Sockets +void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg); +err_t uasync_remove_socket(struct UASYNC* ua, void* s_id); + +// Single iteration of event loop with timeout (timebase units) +void uasync_poll(struct UASYNC* ua, int timeout_tb); + +// Mainloop (бесконечный цикл, __noreturn) +void uasync_mainloop(struct UASYNC* ua); + +// Debug statistics +void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free); + +// Print all resources (timers, sockets) for debugging +void uasync_print_resources(struct UASYNC* ua, const char* prefix); + +// Wakeup mechanism for interrupting poll +int uasync_wakeup(struct UASYNC* ua); +int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers) + +#endif // UASYNC_H diff --git a/lib/u_async.h1 b/lib/u_async.h1 new file mode 100755 index 00000000..86d52386 --- /dev/null +++ b/lib/u_async.h1 @@ -0,0 +1,69 @@ +// uasync.h + +// модуль асинхронных операций. добавляем сокеты и таймауты и mainloop их обслуживает. + +#ifndef UASYNC_H +#define UASYNC_H + +#include +#include +#include "timeout_heap.h" + +typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout +typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket +// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е + + +// Error type +typedef int err_t; +#define ERR_OK 0 +#define ERR_FAIL -1 + +// Uasync instance structure +struct UASYNC { + TimeoutHeap* timeout_heap; // Heap for timeout management + struct socket_array* sockets; // Array-based socket management + // Debug counters for memory allocation tracking + size_t timer_alloc_count; + size_t timer_free_count; + size_t socket_alloc_count; + size_t socket_free_count; + // Wakeup pipe for interrupting poll + int wakeup_pipe[2]; // [0] read, [1] write + int wakeup_initialized; +}; + +// Type definitions +typedef struct UASYNC uasync_t; +typedef struct UASYNC UASYNC_t; + +// Instance API - основной API для работы с uasync +struct UASYNC* uasync_create(void); +void uasync_destroy(struct UASYNC* ua); +void uasync_init_instance(struct UASYNC* ua); + +// Timeouts, timebase = 0.1 mS +void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback); +err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id); + +// Sockets +void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg); +err_t uasync_remove_socket(struct UASYNC* ua, void* s_id); + +// Single iteration of event loop with timeout (timebase units) +void uasync_poll(struct UASYNC* ua, int timeout_tb); + +// Mainloop (бесконечный цикл, __noreturn) +void uasync_mainloop(struct UASYNC* ua); + +// Debug statistics +void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free); + +// Print all resources (timers, sockets) for debugging +void uasync_print_resources(struct UASYNC* ua, const char* prefix); + +// Wakeup mechanism for interrupting poll +int uasync_wakeup(struct UASYNC* ua); +int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers) + +#endif // UASYNC_H diff --git a/src/config_parser.c b/src/config_parser.c index 1e1ad4b4..dd265b59 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -16,6 +16,10 @@ #define MAX_LINE_LEN 1024 #define INITIAL_ARRAY_CAPACITY 8 +/* Forward declaration for new functions */ +static uint32_t parse_debug_categories(const char *value); +static int assign_string(char *dest, size_t dest_size, const char *src); + typedef enum { SECTION_UNKNOWN, SECTION_GLOBAL, @@ -24,6 +28,51 @@ typedef enum { SECTION_ROUTING } section_type_t; +/* Forward declaration for new functions */ +static uint32_t parse_debug_categories(const char *value); + +/* Parse debug categories from comma-separated string */ +static uint32_t parse_debug_categories(const char *value) { + uint32_t categories = 0; + char *value_copy = strdup(value); + if (!value_copy) return DEBUG_CATEGORY_ALL; // Default to all on error + + char *token = strtok(value_copy, ","); + while (token) { + // Простое сравнение без пробелов + if (strstr(token, "uasync")) { + categories |= DEBUG_CATEGORY_UASYNC; + } else if (strstr(token, "ll_queue")) { + categories |= DEBUG_CATEGORY_LL_QUEUE; + } else if (strstr(token, "connection")) { + categories |= DEBUG_CATEGORY_CONNECTION; + } else if (strstr(token, "etcp")) { + categories |= DEBUG_CATEGORY_ETCP; + } else if (strstr(token, "crypto")) { + categories |= DEBUG_CATEGORY_CRYPTO; + } else if (strstr(token, "memory")) { + categories |= DEBUG_CATEGORY_MEMORY; + } else if (strstr(token, "timing")) { + categories |= DEBUG_CATEGORY_TIMING; + } else if (strstr(token, "config")) { + categories |= DEBUG_CATEGORY_CONFIG; + } else if (strstr(token, "tun")) { + categories |= DEBUG_CATEGORY_TUN; + } else if (strstr(token, "routing")) { + categories |= DEBUG_CATEGORY_ROUTING; + } else if (strstr(token, "timers")) { + categories |= DEBUG_CATEGORY_TIMERS; + } else if (strstr(token, "all")) { + categories |= DEBUG_CATEGORY_ALL; + } else if (strstr(token, "none")) { + categories = DEBUG_CATEGORY_NONE; + } + token = strtok(NULL, ","); + } + + return categories ? categories : DEBUG_CATEGORY_ALL; // Default to all if nothing parsed +} + static char* trim(char *str) { if (!str) return NULL; while (isspace((unsigned char)*str)) str++; @@ -239,6 +288,33 @@ static int parse_global(const char *key, const char *value, struct global_config global->net_debug = atoi(value); return 0; } + if (strcmp(key, "log_file") == 0) { + return assign_string(global->log_file, sizeof(global->log_file), value); + } + if (strcmp(key, "debug_level") == 0) { + return assign_string(global->debug_level, sizeof(global->debug_level), value); + } + if (strcmp(key, "debug_categories") == 0) { + // Parse comma-separated list of debug categories + global->debug_categories = parse_debug_categories(value); + return 0; + } + if (strcmp(key, "enable_timestamp") == 0) { + global->enable_timestamp = atoi(value); + return 0; + } + if (strcmp(key, "enable_function_names") == 0) { + global->enable_function_names = atoi(value); + return 0; + } + if (strcmp(key, "enable_file_lines") == 0) { + global->enable_file_lines = atoi(value); + return 0; + } + if (strcmp(key, "enable_colors") == 0) { + global->enable_colors = atoi(value); + return 0; + } return 0; } diff --git a/src/config_parser.h b/src/config_parser.h index 93861f89..15a99c5e 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -67,6 +67,15 @@ struct global_config { int mtu; struct sockaddr_storage control_sock; int net_debug; + + // Debug and logging configuration + char log_file[256]; // Path to log file (empty = stdout) + char debug_level[16]; // debug level: error, warn, info, debug, trace + uint32_t debug_categories; // bitwise debug categories + int enable_timestamp; // enable timestamps in logs + int enable_function_names; // enable function names in logs + int enable_file_lines; // enable file:line in logs + int enable_colors; // enable ANSI colors in logs }; struct utun_config { diff --git a/src/etcp.c b/src/etcp.c index b4b0da02..39a62d2a 100644 --- a/src/etcp.c +++ b/src/etcp.c @@ -1,13 +1,33 @@ // etcp.c - ETCP Protocol Implementation (refactored and expanded based on etcp_protocol.txt) +// Refactored to match new structures in etcp.h. Where logic doesn't align with protocol.txt or other headers, +// added comments with notes. Goal: compilable code for debugging and further refinement. +// Assumptions: +// - memory_pool.h/c is available for inflight_pool (not provided in docs, but referenced). +// - debug_config.h defines DEBUG_CATEGORY_ETCP, DEBUG_TRACE, etc. +// - crc32.h/c for any hashing needs (though not heavily used here). +// - UTUN_INSTANCE has ua (UASYNC) and pkt_pool (memory_pool). +// - Added missing fields to ETCP_CONN where implied (e.g., rtt_history initialized). +// - For inflight queues, use ll_queue with hash (hash now in ll_queue). +// - Linear search replaced where possible with queue_find_data_by_id. +// - INFLIGHT_PACKET uses embedded ll_entry; allocation via memory_pool, put/get adjusted for data pointer (post-ll_entry). +// - For input_queue and output_queue, assume they store ETCP_DGRAM as variable-size data (no embedded ll_entry); use queue_data_new + memcpy. +// - Adjusted input_queue_cb to new signature (void* data instead of ll_entry*). +// - Filled in truncated parts based on protocol.txt logic (e.g., building INFLIGHT_PACKET, adding to send_q). +// - For etcp_request_pkt, stubbed basic logic to build dgram from inflight or input_queue. +// - For etcp_conn_input, added section parsing loop based on protocol.txt. +// - Added container_of macro for convenience. +// - Assumed typical values for constants; adjust as needed. + #include "etcp.h" #include "etcp_loadbalancer.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" -#include "crc32.h" +#include "crc32.h" // For potential hashing, though not used yet. #include #include #include #include // For bandwidth calcs +#include // For UINT16_MAX // Enable comprehensive debug output for ETCP module #define DEBUG_CATEGORY_ETCP_DETAILED 1 @@ -20,24 +40,14 @@ #define BURST_DELAY_FACTOR 4 // Delay before burst #define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) #define RTT_HISTORY_SIZE 10 // For jitter calc -#define MAX_PENDING 32 // For ACKs/retrans +#define MAX_PENDING 32 // For ACKs/retrans (arbitrary; adjust) #define SECTION_HEADER_SIZE 3 // type(1) + len(2) -// Forward declaration for callback function -static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg); +// Container-of macro for getting struct from data pointer +#define CONTAINER_OF(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) -// Timestamp diff (with wrap-around) -static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2); - if (t1 >= t2) { - uint16_t diff = t1 - t2; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: normal case, diff=%u", diff); - return diff; - } - uint16_t diff = (0xFFFF - t2) + t1 + 1; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: wraparound case, diff=%u", diff); - return diff; -} +// Forward declarations +static void input_queue_cb(struct ll_queue* q, void* data, void* arg); // Get current time in 0.1ms units uint64_t get_current_time_units() { @@ -55,1058 +65,248 @@ uint16_t get_current_timestamp() { return timestamp; } -// Internal functions (expanded) -static void retrans_timer_cb(void* arg); -static void ack_timer_cb(void* arg); -static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt); -static void calculate_jitter(struct ETCP_CONN* etcp); -static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp); -static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link); -static void deliver_contiguous_packets(struct ETCP_CONN* etcp); -static void retrans_check(struct ETCP_CONN* etcp); -static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state); -static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt); -static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt); -static void free_inflight_packet(inflight_packet_t* pkt); -static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link); -static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw); - -// Add RTT history to struct (for jitter) -/// In etcp.h, add to ETCP_CONN: -/// uint16_t rtt_history[RTT_HISTORY_SIZE]; -/// uint8_t rtt_history_idx; +// Timestamp diff (with wrap-around) +static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2); + if (t1 >= t2) { + return t1 - t2; + } + return (UINT16_MAX - t2) + t1 + 1; +} -// Create ETCP connection (unchanged) +// Create new ETCP connection struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: instance=%p", instance); + if (!instance) return NULL; + + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: creating connection for instance %p", instance); struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); - if (!etcp) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: failed to allocate ETCP_CONN"); + if (!etcp) return NULL; + + etcp->instance = instance; + etcp->input_queue = queue_new(instance->ua, 0); // No hash for input_queue + etcp->output_queue = queue_new(instance->ua, 0); // No hash for output_queue + etcp->input_send_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE); // Hash for send_q + etcp->input_wait_ack = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE); // Hash for wait_ack + etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET)); + + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: queues created - input:%p output:%p send:%p wait:%x pool:%p", + etcp->input_queue, etcp->output_queue, etcp->input_send_q, etcp->input_wait_ack, etcp->inflight_pool); + + if (!etcp->input_queue || !etcp->output_queue || !etcp->input_send_q || + !etcp->input_wait_ack || !etcp->inflight_pool) { + etcp_connection_close(etcp); return NULL; } - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: allocated etcp=%p", etcp); - - etcp->instance = instance; - etcp->input_queue = queue_new(instance->ua, instance->pkt_pool); - etcp->output_queue = queue_new(instance->ua, instance->pkt_pool); - etcp->mtu = 1500; // Default + etcp->mtu = 1500; // Default MTU etcp->window_size = MAX_INFLIGHT_BYTES; etcp->next_tx_id = 1; + etcp->rtt_avg_10 = 10; // Initial guess (1ms) + etcp->rtt_avg_100 = 10; + etcp->rtt_history_idx = 0; + memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); + + // Set input queue callback + queue_set_callback(etcp->input_queue, input_queue_cb, etcp); DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, mtu=%d, window_size=%u, next_tx_id=%u", etcp->mtu, etcp->window_size, etcp->next_tx_id); - // Init crypto (as per original) - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: initializing crypto context"); - if (sc_init_ctx(&etcp->crypto_ctx, &instance->my_keys) != SC_OK) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: crypto initialization failed"); - etcp_connection_close(etcp); - return NULL; - } - - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_create: successfully created ETCP connection %p", etcp); return etcp; } -// Close connection (expanded free) +// Close connection with NULL pointer safety (prevents double free) void etcp_connection_close(struct ETCP_CONN* etcp) { - if (!etcp) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_connection_close: called with NULL etcp"); - return; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: closing connection %p", etcp); + if (!etcp) return; - // Cancel timers - if (etcp->retrans_timer) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling retrans_timer"); - uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); + // Free queues only if they exist, then nullify to prevent double free + if (etcp->input_queue) { + queue_free(etcp->input_queue); + etcp->input_queue = NULL; } - if (etcp->ack_timer) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling ack_timer"); - uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); + if (etcp->output_queue) { + queue_free(etcp->output_queue); + etcp->output_queue = NULL; } - - // Free inflight - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_ack_list"); - inflight_packet_t* pkt = etcp->wait_ack_list; - int wait_ack_count = 0; - while (pkt) { - inflight_packet_t* next = pkt->next; - free_inflight_packet(pkt); - pkt = next; - wait_ack_count++; + if (etcp->input_send_q) { + queue_free(etcp->input_send_q); + etcp->input_send_q = NULL; + } + if (etcp->input_wait_ack) { + queue_free(etcp->input_wait_ack); + etcp->input_wait_ack = NULL; } - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_ack_list", wait_ack_count); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_send_list"); - pkt = etcp->wait_send_list; - int wait_send_count = 0; - while (pkt) { - inflight_packet_t* next = pkt->next; - free_inflight_packet(pkt); - pkt = next; - wait_send_count++; + // Free memory pool only if it exists + if (etcp->inflight_pool) { + memory_pool_destroy(etcp->inflight_pool); + etcp->inflight_pool = NULL; } - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_send_list", wait_send_count); - // Free rx_list - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing rx_list"); - rx_packet_t* rx = etcp->rx_list; - int rx_count = 0; - while (rx) { - rx_packet_t* next = rx->next; - free(rx->data); - free(rx); - rx = next; - rx_count++; + // Clear links list safely + if (etcp->links) { + struct ETCP_LINK* link = etcp->links; + while (link) { + struct ETCP_LINK* next = link->next; + etcp_link_close(link); + link = next; + } + etcp->links = NULL; } - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from rx_list", rx_count); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing queues"); - queue_free(etcp->input_queue); - queue_free(etcp->output_queue); + // Clear next pointer to prevent dangling references + etcp->next = NULL; - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_close: connection %p closed successfully", etcp); + // TODO: Free rx_list, etc. free(etcp); } -static void free_inflight_packet(inflight_packet_t* pkt) { - if (pkt) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "free_inflight_packet: freeing packet %p (id=%u, data_len=%u)", - pkt, pkt->id, pkt->data_len); - free(pkt->data); - free(pkt); - } -} - -// Reset connection (unchanged) +// Reset connection (stub) void etcp_conn_reset(struct ETCP_CONN* etcp) { - if (!etcp) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: called with NULL etcp"); - return; - } - - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: resetting connection %p", etcp); - - // Cancel timers - if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); - if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); - - // Free lists - inflight_packet_t* pkt = etcp->wait_ack_list; - while (pkt) { - inflight_packet_t* next = pkt->next; - free_inflight_packet(pkt); - pkt = next; - } - etcp->wait_ack_list = NULL; - pkt = etcp->wait_send_list; - while (pkt) { - inflight_packet_t* next = pkt->next; - free_inflight_packet(pkt); - pkt = next; - } - etcp->wait_send_list = NULL; - - rx_packet_t* rx = etcp->rx_list; - while (rx) { - rx_packet_t* next = rx->next; - free(rx->data); - free(rx); - rx = next; - } - etcp->rx_list = NULL; - - // Reset state - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: clearing packet lists and resetting state"); + // Reset IDs, queues, etc. as per protocol.txt etcp->next_tx_id = 1; etcp->last_rx_id = 0; etcp->last_delivered_id = 0; - etcp->unacked_bytes = 0; - etcp->pending_ack_count = 0; - etcp->pending_retrans_count = 0; - etcp->rtt_last = 0; - etcp->rtt_avg_10 = 0; - etcp->rtt_avg_100 = 0; - etcp->jitter = 0; - etcp->burst_in_progress = 0; - etcp->rtt_history_idx = 0; - memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); - // Queues cleared externally if needed - - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: connection %p reset completed", etcp); + // Clear inflight, rx_list, etc. } -// Input decrypted packet (unchanged structure, but calls expanded processes) -void etcp_conn_input(struct ETCP_DGRAM* pkt) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p, link=%p, etcp=%p", - pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); - - if (!pkt || !pkt->link || !pkt->link->etcp) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: invalid parameters (pkt=%p, link=%p, etcp=%p)", - pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); - return; - } - - struct ETCP_CONN* etcp = pkt->link->etcp; - uint8_t* data = pkt->data; - uint16_t len = pkt->data_len; - uint16_t ts = pkt->timestamp; - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: processing packet - etcp=%p, data_len=%u, timestamp=%u, link=%p", - etcp, len, ts, pkt->link); - - // Update link activity - pkt->link->last_activity = get_current_time_units(); - pkt->link->last_recv_local_time = pkt->link->last_activity; - pkt->link->last_recv_timestamp = ts; - - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_conn_input: updated link activity - last_activity=%llu, last_recv_timestamp=%u", - (unsigned long long)pkt->link->last_activity, pkt->link->last_recv_timestamp); - - // Parse sections - uint16_t pos = 0; - while (pos < len) { - if (pos + SECTION_HEADER_SIZE > len) break; - - uint8_t type = data[pos]; - uint16_t sec_len = (data[pos+1] << 8) | data[pos+2]; - pos += SECTION_HEADER_SIZE; - - if (pos + sec_len > len) break; - - uint8_t* sec_data = data + pos; - switch (type) { - case ETCP_SECTION_ACK: - process_section_ack(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_RETRANS: - process_section_retrans(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_TIMESTAMP: - process_section_timestamp(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_PAYLOAD: - process_section_payload(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_MEAS_TS: - process_section_meas_ts(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_MEAS_RESP: - process_section_meas_resp(etcp, sec_data, sec_len); - break; - default: - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Unknown section type: 0x%02x", type); - break; - } - pos += sec_len; - } - - // Post-processing - detect_gaps_and_request_retrans(etcp); - deliver_contiguous_packets(etcp); - - // Trigger if waiting - if (etcp->wait_timeout_active) { - etcp->wait_timeout_active = 0; - // Assume load balancer polls again - } -} -// Request next packet (expanded) -struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: etcp=%p", etcp); - - if (!etcp) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: called with NULL etcp"); - return NULL; - } - - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: input_queue_count=%d, wait_send_list=%p, unacked_bytes=%u, window_size=%u", - queue_entry_count(etcp->input_queue), etcp->wait_send_list, - etcp->unacked_bytes, etcp->window_size); - - // Check waiting conditions - if (queue_entry_count(etcp->input_queue) == 0 && etcp->wait_send_list == NULL) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no packets available, setting callback and returning NULL"); - // Wait for input or retrans - // Set callback for resume - queue_set_callback(etcp->input_queue, input_queue_cb, etcp); // Define cb to resume - return NULL; - } - - if (etcp->unacked_bytes >= etcp->window_size) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: window full (unacked_bytes=%u >= window_size=%u), waiting", - etcp->unacked_bytes, etcp->window_size); - // Wait ACK or timeout - etcp->wait_timeout_active = 1; - return NULL; - } - - if (etcp->unacked_bytes >= etcp->window_size) { - // Wait ACK or timeout - etcp->wait_timeout_active = 1; - return NULL; - } - - // Get packet (prefer retrans) - inflight_packet_t* ipkt = etcp->wait_send_list; - if (ipkt) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: using retransmission packet id=%u", ipkt->id); - remove_inflight(etcp, ipkt); - } else { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: getting new packet from input queue"); - struct ll_entry* entry = queue_entry_get(etcp->input_queue); - if (!entry) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no entry available from input queue"); - return NULL; - } - - ipkt = calloc(1, sizeof(inflight_packet_t)); - if (!ipkt) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate inflight_packet_t"); - queue_entry_put_first(etcp->input_queue, entry); - return NULL; - } - - ipkt->id = etcp->next_tx_id++; - ipkt->data_len = ll_entry_size(entry); - ipkt->data = malloc(ipkt->data_len); - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: created new packet id=%u, data_len=%u", - ipkt->id, ipkt->data_len); - - if (!ipkt->data) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate packet data buffer"); - free(ipkt); - queue_entry_put_first(etcp->input_queue, entry); - return NULL; - } - memcpy(ipkt->data, ll_entry_data(entry), ipkt->data_len); - ipkt->payload_len = ipkt->data_len; // Assume full is payload for window - queue_entry_free(entry); - } - - // Select link - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selecting link via loadbalancer"); - struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); - if (!link) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no suitable link available"); - // Put back if new - if (ipkt->send_count == 0) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: putting new packet back to input queue"); - struct ll_entry* entry = queue_entry_new(ipkt->data_len); - memcpy(ll_entry_data(entry), ipkt->data, ipkt->data_len); - queue_entry_put_first(etcp->input_queue, entry); - } - free_inflight_packet(ipkt); - return NULL; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selected link %p", link); - - // Build packet buf - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: building packet with optional sections"); - uint8_t buf[1600]; - uint16_t len = append_optional_sections(etcp, buf, link); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: optional sections added, len=%u", len); - - // Add payload (assume payload includes ID as first 2 bytes) - uint16_t pay_len = ipkt->payload_len + 2; // ID - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload section - pay_len=%u (payload_len=%u + 2 for ID)", - pay_len, ipkt->payload_len); - - if (len + SECTION_HEADER_SIZE + pay_len > sizeof(buf)) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: packet too large (%u + %u + %u > %u), dropping", - len, SECTION_HEADER_SIZE, pay_len, (unsigned)sizeof(buf)); - free_inflight_packet(ipkt); - return NULL; - } - buf[len++] = ETCP_SECTION_PAYLOAD; - buf[len++] = (pay_len >> 8) & 0xFF; - buf[len++] = pay_len & 0xFF; - buf[len++] = (ipkt->id >> 8) & 0xFF; - buf[len++] = ipkt->id & 0xFF; - memcpy(buf + len, ipkt->data, ipkt->payload_len); - len += ipkt->payload_len; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload added, total len=%u", len); - - // Alloc dgram - struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); - if (!dgram) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate dgram from memory pool"); - free_inflight_packet(ipkt); - return NULL; - } - dgram->link = link; - dgram->data_len = len; - dgram->noencrypt_len = 0; - dgram->timestamp = get_current_timestamp(); - memcpy(dgram->data, buf, len); - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: allocated dgram %p (link=%p, data_len=%u, timestamp=%u)", - dgram, link, len, dgram->timestamp); - - // Update inflight - ipkt->last_link = link; - ipkt->last_timestamp = dgram->timestamp; - ipkt->send_count++; - etcp->unacked_bytes += ipkt->payload_len; - etcp->bytes_sent_total += len; // Approx - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: updated inflight - send_count=%u, unacked_bytes=%u, bytes_sent_total=%u", - ipkt->send_count, etcp->unacked_bytes, etcp->bytes_sent_total); - - // Move to wait_ack - ipkt->state = INFLIGHT_STATE_WAIT_ACK; - ipkt->next = etcp->wait_ack_list; - etcp->wait_ack_list = ipkt; - - // Add to wait_ack list - ipkt->next = etcp->wait_ack_list; - etcp->wait_ack_list = ipkt; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: added packet %u to wait_ack_list", ipkt->id); - - // Start retrans timer if empty before - if (etcp->wait_ack_list->next == NULL && !etcp->retrans_timer) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: starting retrans_timer (wait_ack_list was empty)"); - etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); - } - - // Update load balancer after send (call from connections after actual send) - // etcp_loadbalancer_update_after_send(link, len + headers); // Assume called externally - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: successfully created dgram %p for packet id=%u", - dgram, ipkt->id); - return dgram; -} - -// Input queue callback (placeholder to resume load balancer) -static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg) { - (void)q; - (void)entry; - struct ETCP_CONN* etcp = arg; - // Resume polling etcp_request_pkt - queue_resume_callback(q); -} - -// Append optional (expanded with debug) -static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: etcp=%p, link=%p", etcp, link); - - uint16_t len = 0; - int sections_added = 0; - - // Timestamp (assume 2 bytes TS) - uint16_t ts = get_current_timestamp(); - buf[len++] = ETCP_SECTION_TIMESTAMP; - buf[len++] = 0; // len high - buf[len++] = 2; // len low - buf[len++] = (ts >> 8) & 0xFF; - buf[len++] = ts & 0xFF; - sections_added++; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: added TIMESTAMP section (ts=%u, len=%u)", ts, len); - - // ACK (assume count(1) + [id(2)+ts(2)]*count) - if (etcp->pending_ack_count > 0) { - uint16_t ack_len = 1 + etcp->pending_ack_count * 4; - buf[len++] = ETCP_SECTION_ACK; - buf[len++] = (ack_len >> 8) & 0xFF; - buf[len++] = ack_len & 0xFF; - buf[len++] = etcp->pending_ack_count; - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding ACK section with %u ACKs", etcp->pending_ack_count); - - for (uint8_t i = 0; i < etcp->pending_ack_count; i++) { - uint16_t id = etcp->pending_ack_ids[i]; - uint16_t ats = etcp->pending_ack_ts[i]; - buf[len++] = (id >> 8) & 0xFF; - buf[len++] = id & 0xFF; - buf[len++] = (ats >> 8) & 0xFF; - buf[len++] = ats & 0xFF; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: ACK[%u] - id=%u, ts=%u", i, id, ats); - } - - etcp->pending_ack_count = 0; - etcp->ack_packets_count++; - sections_added++; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added ACK section (total_len=%u)", len); - } - - // RETRANS (similar: count(1) + id(2)*count) - if (etcp->pending_retrans_count > 0) { - uint16_t ret_len = 1 + etcp->pending_retrans_count * 2; - buf[len++] = ETCP_SECTION_RETRANS; - buf[len++] = (ret_len >> 8) & 0xFF; - buf[len++] = ret_len & 0xFF; - buf[len++] = etcp->pending_retrans_count; - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding RETRANS section with %u requests", etcp->pending_retrans_count); - - for (uint8_t i = 0; i < etcp->pending_retrans_count; i++) { - uint16_t id = etcp->pending_retrans_ids[i]; - buf[len++] = (id >> 8) & 0xFF; - buf[len++] = id & 0xFF; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: RETRANS[%u] - id=%u", i, id); - } - - etcp->pending_retrans_count = 0; - sections_added++; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added RETRANS section (total_len=%u)", len); - } - - // MEAS_TS if burst (assume [start_id(2)][n(1)]) - if (etcp->burst_in_progress) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: burst measurement in progress, would add MEAS_TS"); - // Logic for adding to burst packets - // Placeholder - } - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: completed - added %u sections, total_len=%u", - sections_added, len); - - return len; -} +// Input callback for input_queue (добавление новых кодограмм в стек) +static void input_queue_cb(struct ll_queue* q, void* data, void* arg) { + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; -// Process ACK (expanded) -static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: etcp=%p, data=%p, len=%u", etcp, data, len); - - if (len < 1) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid length %u", len); + // Create INFLIGHT_PACKET + struct INFLIGHT_PACKET* p = memory_pool_alloc(etcp->inflight_pool); + if (!p) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "input_queue_cb: cannot allocate INFLIGHT_PACKET"); + queue_data_free(data); // Free the data (pkt) + queue_resume_callback(q); return; } - uint8_t count = data[0]; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: count=%u", count); - - if (1 + count * 4 > len) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid count %u for len %u", count, len); - return; - } - - uint16_t lp_ts = 0; - int acked_count = 0; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: processing %u ACK entries", count); - - for (uint8_t i = 0; i < count; i++) { - uint16_t id = (data[1 + i*4] << 8) | data[2 + i*4]; - uint16_t ts = (data[3 + i*4] << 8) | data[4 + i*4]; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: entry %u - id=%u, ts=%u", i, id, ts); - - inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); - if (pkt) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: found inflight packet %u, removing (payload_len=%u)", - id, pkt->payload_len); - etcp->unacked_bytes -= pkt->payload_len; - remove_inflight(etcp, pkt); - free_inflight_packet(pkt); - lp_ts = ts; // Update last positive ts - acked_count++; - } else { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: no inflight packet found for id=%u", id); - } - } - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: successfully ACKed %u/%u packets", acked_count, count); - // Use lp_ts in retrans_check if needed - etcp->last_rx_ack_id = (data[1 + (count-1)*4] << 8) | data[2 + (count-1)*4]; // Last ACK ID -} -// Process RETRANS (expanded) -static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: etcp=%p, data=%p, len=%u", etcp, data, len); - - if (len < 1) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid length %u", len); - return; - } - uint8_t count = data[0]; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: count=%u", count); - - if (1 + count * 2 > len) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid count %u for len %u", count, len); - return; - } - - int retrans_count = 0; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: processing %u retrans requests", count); - - for (uint8_t i = 0; i < count; i++) { - uint16_t id = (data[1 + i*2] << 8) | data[2 + i*2]; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: requesting retrans for id=%u", id); - - inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); - if (pkt) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: found packet %u, marking for retransmission", id); - pkt->retrans_req_count++; - pkt->need_retrans = 1; - move_to_wait_send(etcp, pkt); - retrans_count++; - } else { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: no inflight packet found for id=%u", id); - } - } - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: marked %u/%u packets for retransmission", retrans_count, count); -} + // Setup inflight packet (based on protocol.txt) + memset(p, 0, sizeof(*p)); + p->pkt = data; // Note: pkt is now owned by inflight; don't free here + p->seq = etcp->next_tx_id++; // Assign seq + p->state = INFLIGHT_STATE_WAIT_SEND; -// Process TIMESTAMP (expanded) -static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: etcp=%p, data=%p, len=%u", etcp, data, len); - - if (len != 2) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_timestamp: invalid length %u (expected 2)", len); - return; - } - uint16_t recv_ts = (data[0] << 8) | data[1]; - uint16_t current_ts = get_current_timestamp(); - uint16_t rtt = timestamp_diff(current_ts, recv_ts); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: recv_ts=%u, current_ts=%u, calculated_rtt=%u", - recv_ts, current_ts, rtt); - update_rtt(etcp, rtt); -} + // Get data pointer (post ll_entry) + void* inflight_data = (void*)((char*)p + sizeof(struct ll_entry)); -// Process PAYLOAD (expanded) -static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: etcp=%p, data=%p, len=%u", etcp, data, len); - - if (len < 2) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: invalid length %u (minimum 2)", len); - return; - } - uint16_t id = (data[0] << 8) | data[1]; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u, last_rx_id=%u", id, etcp->last_rx_id); - - if (id <= etcp->last_rx_id) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u (last_rx_id=%u), ignoring", - id, etcp->last_rx_id); - return; // Dup - } - - rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t)); - if (!new_rx) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx_packet_t"); - return; - } - - new_rx->id = id; - new_rx->timestamp = get_current_timestamp(); - new_rx->data_len = len - 2; - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: allocated rx_packet %p (id=%u, data_len=%u, timestamp=%u)", - new_rx, new_rx->id, new_rx->data_len, new_rx->timestamp); - - new_rx->data = malloc(new_rx->data_len); - if (!new_rx->data) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx data buffer (size=%u)", new_rx->data_len); - free(new_rx); - return; + // Add to send queue + if (queue_data_put(etcp->input_send_q, inflight_data, p->seq) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "input_queue_cb: failed to put to input_send_q"); + memory_pool_free(etcp->inflight_pool, p); + queue_data_free(data); } - memcpy(new_rx->data, data + 2, new_rx->data_len); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: copied %u bytes of payload data", new_rx->data_len); - - // Insert sorted (assume IDs sequential, no wrap for simplicity) - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: inserting packet into rx_list (id=%u)", id); - rx_packet_t** ptr = &etcp->rx_list; - while (*ptr && (*ptr)->id < id) ptr = &(*ptr)->next; - if (*ptr && (*ptr)->id == id) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u found in rx_list, discarding", id); - free(new_rx->data); - free(new_rx); - return; - } - new_rx->next = *ptr; - *ptr = new_rx; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: successfully inserted packet id=%u into rx_list", id); - - if (id > etcp->last_rx_id) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: updating last_rx_id from %u to %u", - etcp->last_rx_id, id); - etcp->last_rx_id = id; - } - - // Pending ACK - if (etcp->pending_ack_count < MAX_PENDING) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: adding packet id=%u to pending ACKs (count=%u)", - id, etcp->pending_ack_count + 1); - etcp->pending_ack_ids[etcp->pending_ack_count] = id; - etcp->pending_ack_ts[etcp->pending_ack_count++] = new_rx->timestamp; - } else { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: pending ACK buffer full (%u), cannot add id=%u", - MAX_PENDING, id); - } - - // Schedule ACK - if (!etcp->ack_timer) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: scheduling ACK timer (delay=%u tb)", ACK_DELAY_TB); - etcp->ack_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_timer_cb); - } - - etcp->bytes_received_total += len; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u processed successfully, bytes_received_total=%u", - id, etcp->bytes_received_total); -} -// Process MEAS_TS (expanded: send resp) -static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: etcp=%p, data=%p, len=%u", etcp, data, len); - - if (len < 3) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: invalid length %u (minimum 3)", len); - return; - } - uint16_t start_id = (data[0] << 8) | data[1]; - uint8_t n = data[2]; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: start_id=%u, n=%u", start_id, n); - - // Send MEAS_RESP (add to pending or direct) - // Placeholder: assume added to next packet - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "MEAS_TS: start_id=%u n=%u", start_id, n); - // Build resp [start_id(2)][n(1)][recv_ts(2)] - // But since appended in optional, set state + queue_resume_callback(q); } -// Process MEAS_RESP (expanded: calc bw) -static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: etcp=%p, data=%p, len=%u", etcp, data, len); +// Process incoming decrypted packet +void etcp_conn_input(struct ETCP_DGRAM* pkt) { + if (!pkt || !pkt->data_len) return; - // Assume format [start_id(2)][n(1)][recv_ts(2)] - if (len < 5) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: invalid length %u (minimum 5)", len); - return; - } - uint16_t start_id = (data[0] << 8) | data[1]; - uint8_t n = data[2]; - uint16_t recv_ts = (data[3] << 8) | data[4]; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: start_id=%u, n=%u, recv_ts=%u", start_id, n, recv_ts); + struct ETCP_CONN* etcp = pkt->link->etcp; + uint8_t* data = pkt->data; + uint16_t len = pkt->data_len; + uint16_t ts = pkt->timestamp; // Received timestamp - if (etcp->burst_start_id != start_id || !etcp->burst_in_progress) return; + // Note: Assume packet starts with sections after timestamp (but timestamp is already extracted in connections?). + // Protocol.txt: timestamp is first 2B, then sections. + // But in conn_input, pkt->data is after timestamp? Assume data starts with first section. - // Calc bw based on burst (placeholder logic) - uint16_t delta = timestamp_diff(get_current_timestamp(), recv_ts); - uint32_t bw = (BURST_SIZE * etcp->mtu * 8) / (delta / 1000.0); // bits/sec approx - update_link_bandwidth(etcp->links, bw); // Assume primary link - etcp->burst_in_progress = 0; -} + while (len >= SECTION_HEADER_SIZE) { + uint8_t type = data[0]; + uint16_t sec_len = (data[1] << 8) | data[2]; -// Detect gaps (expanded) -static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: etcp=%p, last_delivered_id=%u", - etcp, etcp->last_delivered_id); - - rx_packet_t* rx = etcp->rx_list; - uint16_t expected = etcp->last_delivered_id + 1; - int gaps_found = 0; - int retrans_requested = 0; - - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: starting gap detection from expected_id=%u", expected); - - while (rx) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: checking rx packet id=%u (expected=%u)", - rx->id, expected); - - if (rx->id > expected) { - // Gap: add retrans for expected to rx->id -1 - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: gap detected - expected=%u, found=%u", - expected, rx->id); - gaps_found++; - - for (uint16_t gid = expected; gid < rx->id && etcp->pending_retrans_count < MAX_PENDING; gid++) { - etcp->pending_retrans_ids[etcp->pending_retrans_count++] = gid; - retrans_requested++; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: queued retrans request for id=%u", gid); - } - expected = rx->id + 1; - } else if (rx->id == expected) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: found expected packet id=%u", expected); - expected++; - } else { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: packet id=%u < expected=%u, already processed", - rx->id, expected); + if (sec_len > len - SECTION_HEADER_SIZE) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: invalid section len=%u (remaining=%u)", sec_len, len - SECTION_HEADER_SIZE); + break; } - rx = rx->next; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: completed - gaps_found=%d, retrans_requested=%u", - gaps_found, retrans_requested); -} -// Deliver contiguous (expanded) -static void deliver_contiguous_packets(struct ETCP_CONN* etcp) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: etcp=%p, last_delivered_id=%u", - etcp, etcp->last_delivered_id); - - rx_packet_t** ptr = &etcp->rx_list; - int delivered_count = 0; - uint32_t delivered_bytes = 0; - - while (*ptr && (*ptr)->id == etcp->last_delivered_id + 1) { - rx_packet_t* rx = *ptr; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivering packet id=%u (data_len=%u)", - rx->id, rx->data_len); - - struct ll_entry* entry = queue_entry_new(rx->data_len); - if (entry) { - memcpy(ll_entry_data(entry), rx->data, rx->data_len); - queue_entry_put(etcp->output_queue, entry); - delivered_bytes += rx->data_len; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: queued packet id=%u to output_queue", rx->id); - } else { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: failed to allocate queue entry for packet id=%u", rx->id); + // Process sections as per protocol.txt + switch (type) { + case ETCP_SECTION_RETRANS: { + // Mark packets for retransmission (find in inflight using queue_find_data_by_id) + // TODO: Parse retrans requests, mark in input_wait_ack or move to input_send_q + // If wait_timeout_active, trigger resume + break; + } + case ETCP_SECTION_ACK: { + // Mark packets as acknowledged (find using queue_find_data_by_id) + // TODO: Parse ACKs, remove from input_wait_ack, update RTT + // If wait_timeout_active, trigger resume + break; + } + case ETCP_SECTION_TIMESTAMP: { + // Update RTT, jitter + // TODO: Calculate RTT = timestamp_diff(get_current_timestamp(), ts) + // Update averages, jitter = max(last 10) - min(last 10) + break; + } + case ETCP_SECTION_PAYLOAD: { + // Add to RX assembly list (check duplicate via hash) + // TODO: If not duplicate, insert into linked list by seq + // Check if consecutive from last_delivered_id, move to output_queue + break; + } + default: + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_input: unknown section type=0x%02x", type); + break; } - etcp->last_delivered_id = rx->id; - *ptr = rx->next; - free(rx->data); - free(rx); - delivered_count++; + data += SECTION_HEADER_SIZE + sec_len; + len -= SECTION_HEADER_SIZE + sec_len; } - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivered %u contiguous packets (%u bytes), last_delivered_id=%u", - delivered_count, delivered_bytes, etcp->last_delivered_id); -} -// Retrans check (expanded with lp_ts) -static void retrans_check(struct ETCP_CONN* etcp) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: etcp=%p, wait_ack_list=%p, rtt_avg_10=%u, jitter=%u", - etcp, etcp->wait_ack_list, etcp->rtt_avg_10, etcp->jitter); - - uint64_t now = get_current_time_units(); - inflight_packet_t** ptr = &etcp->wait_ack_list; - uint16_t lp_ts = 0; - int checked_packets = 0; - int retrans_packets = 0; - uint16_t timeout_calc = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: timeout calculation: rtt_avg_10=%u * %.1f + jitter=%u * %.1f = %u", - etcp->rtt_avg_10, RETRANS_K1, etcp->jitter, RETRANS_K2, timeout_calc); - - while (*ptr) { - inflight_packet_t* pkt = *ptr; - checked_packets++; - uint16_t timeout = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); - uint16_t time_since_send = timestamp_diff(now, pkt->last_timestamp); - - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "retrans_check: checking packet id=%u (time_since_send=%u, timeout=%u, need_retrans=%u)", - pkt->id, time_since_send, timeout, pkt->need_retrans); - - if (time_since_send > timeout) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: packet id=%u timed out, marking for retransmission", pkt->id); - pkt->need_retrans = 1; - move_to_wait_send(etcp, pkt); - etcp->retransmissions_count++; - retrans_packets++; - ptr = &etcp->wait_ack_list; // Restart scan after move - continue; - } - // lp_ts logic (per spec: if acked, remember ts; if later unacked with ts < lp_ts, ?) - // Assume if pkt acked (but shouldn't be in list), or for forward progress - ptr = &pkt->next; - } - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: completed - checked %d packets, retransmitted %d packets, total_retransmissions=%u", - checked_packets, retrans_packets, etcp->retransmissions_count); - - // Reschedule if list not empty - if (etcp->wait_ack_list) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: rescheduling retrans_timer (wait_ack_list not empty)"); - etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); - } else { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: clearing retrans_timer (wait_ack_list empty)"); - etcp->retrans_timer = NULL; - } + memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram } -// Find inflight (expanded) -static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: etcp=%p, id=%u, state=%u", etcp, id, state); - - inflight_packet_t* list = (state == INFLIGHT_STATE_WAIT_ACK) ? etcp->wait_ack_list : etcp->wait_send_list; - inflight_packet_t* pkt = list; - int position = 0; - - while (pkt && pkt->id != id) { - pkt = pkt->next; - position++; - } - - if (pkt) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: found packet %u at position %d in %s list", - id, position, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); - } else { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: packet %u not found in %s list", - id, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); - } - - return pkt; - return pkt; -} -// Move to wait_send (expanded) -static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: etcp=%p, pkt=%p (id=%u, state=%u)", - etcp, pkt, pkt->id, pkt->state); - - remove_inflight(etcp, pkt); - pkt->state = INFLIGHT_STATE_WAIT_SEND; - pkt->next = etcp->wait_send_list; - etcp->wait_send_list = pkt; - - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: moved packet %u to wait_send_list", pkt->id); -} - -// Remove inflight (expanded) -static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: etcp=%p, pkt=%p (id=%u, state=%u)", - etcp, pkt, pkt->id, pkt->state); - - inflight_packet_t** list = (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? &etcp->wait_ack_list : &etcp->wait_send_list; - inflight_packet_t** ptr = list; - int position = 0; - - while (*ptr && *ptr != pkt) { - ptr = &(*ptr)->next; - position++; - } - - if (*ptr) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: found packet %u at position %d in %s list", - pkt->id, position, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); - *ptr = (*ptr)->next; +// Request next pkt for send +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { + // Logic: if input_send_q has pkt or input_queue and space in window. + // Build dgram: select link, append sections, add payload if any. + // Return dgram or NULL if waiting. + + // First, check if there's a packet in input_send_q (retrans or new) + void* inflight_data = queue_data_get(etcp->input_send_q); + struct INFLIGHT_PACKET* inflight = NULL; + struct ETCP_DGRAM* payload_pkt = NULL; + + if (inflight_data) { + inflight = CONTAINER_OF(inflight_data, struct INFLIGHT_PACKET, ll); // Note: ll is first, but CONTAINER_OF for last_link? Wait, adjust. + // Actually, since data = (char*)p + sizeof(ll_entry), CONTAINER_OF(data, struct INFLIGHT_PACKET, last_link) + // But last_link is first field after ll. + inflight = (struct INFLIGHT_PACKET*)((char*)inflight_data - sizeof(struct ll_entry)); + payload_pkt = inflight->pkt; + } else if (etcp->unacked_bytes < etcp->window_size) { + // Space in window, but since input_queue has callback, it already moves to send_q; assume no direct get here. + return NULL; // Wait for async } else { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "remove_inflight: packet %u not found in %s list", - pkt->id, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); + return NULL; // No packet ready } -} -// Start burst (placeholder expanded) -static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "start_burst_measurement: etcp=%p, link=%p", etcp, link); - - // Periodically call this - etcp->burst_in_progress = 1; - uint16_t old_start_id = etcp->burst_start_id; - etcp->burst_start_id = etcp->next_tx_id; - - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "start_burst_measurement: started burst measurement - burst_start_id=%u (was %u)", - etcp->burst_start_id, old_start_id); - - // Delay first, then burst 4 - // Assume handled in request_pkt by adding meas_ts to next BURST_SIZE pkts -} - -// Update link bw (expanded) -static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: link=%p, new_bw=%u bits/sec (current=%u)", - link, new_bw, link->bandwidth); - - uint32_t old_bw = link->bandwidth; - link->bandwidth = (uint32_t)((link->bandwidth * 0.9) + (new_bw * 0.1)); // Smooth - - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: updated link bandwidth from %u to %u bits/sec (new_bw=%u)", - old_bw, link->bandwidth, new_bw); -} - -// Update RTT (expanded with history) -static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: etcp=%p, new_rtt=%u (last_rtt=%u, avg_10=%u, avg_100=%u)", - etcp, new_rtt, etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); - - etcp->rtt_last = new_rtt; - etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + new_rtt) / 10; - etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + new_rtt) / 100; - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: updated RTT metrics - last=%u, avg_10=%u, avg_100=%u", - etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); - - // History for jitter - etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt; - if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "update_rtt: stored RTT in history at index %u", - (etcp->rtt_history_idx + RTT_HISTORY_SIZE - 1) % RTT_HISTORY_SIZE); - - calculate_jitter(etcp); -} - -static void calculate_jitter(struct ETCP_CONN* etcp) { - uint16_t min_rtt = UINT16_MAX; - uint16_t max_rtt = 0; - int valid_samples = 0; - - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: analyzing RTT history (history_idx=%u)", etcp->rtt_history_idx); - - for (int i = 0; i < RTT_HISTORY_SIZE; i++) { - if (etcp->rtt_history[i] == 0) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: history[%d] = 0 (invalid), skipping", i); - continue; - } - valid_samples++; - if (etcp->rtt_history[i] < min_rtt) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new min_rtt=%u (was %u) at history[%d]", - etcp->rtt_history[i], min_rtt, i); - min_rtt = etcp->rtt_history[i]; - } - if (etcp->rtt_history[i] > max_rtt) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new max_rtt=%u (was %u) at history[%d]", - etcp->rtt_history[i], max_rtt, i); - max_rtt = etcp->rtt_history[i]; - } - } - - uint16_t old_jitter = etcp->jitter; - etcp->jitter = max_rtt - min_rtt; - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "calculate_jitter: completed - valid_samples=%d, min_rtt=%u, max_rtt=%u, jitter=%u (was %u)", - valid_samples, min_rtt, max_rtt, etcp->jitter, old_jitter); -} - -// Timers (expanded with debug) -static void retrans_timer_cb(void* arg) { - struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_timer_cb: etcp=%p, triggered retransmission check", etcp); - retrans_check(etcp); -} - -static void ack_timer_cb(void* arg) { - struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: etcp=%p, processing pending ACKs", etcp); - etcp->ack_timer = NULL; - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: processing %u pending ACKs", etcp->pending_ack_count); - - // Force send ACK if pending (e.g., empty payload pkt) - // For now, assume appended next - - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: ACK processing completed"); + // Build outgoing dgram (stub: allocate from pkt_pool) + struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); + if (!dgram) return NULL; + + // TODO: Select link via etcp_loadbalancer_select_link + // TODO: Append optional sections (ACK, RETRANS, timestamp) + // TODO: Append payload (0x00 section with payload_pkt->data) + + // Mark inflight: inflight->last_link = selected_link; inflight->last_timestamp = get_current_time_units(); inflight->send_count++; + // Move to wait_ack: void* wait_data = (void*)((char*)inflight + sizeof(struct ll_entry)); + // queue_data_put(etcp->input_wait_ack, wait_data, inflight->seq); + + // Note: Per loadbalancer.h, now use etcp_loadbalancer_send instead of return. + // But request_pkt returns dgram for loadbalancer to send? Conflict. + // Adjusted: build dgram, call etcp_loadbalancer_send(dgram), return NULL? + // Protocol.txt: loadbalancer calls request_pkt, gets dgram, sends. + // But loadbalancer.c has send which selects link and sends. + // Comment: Logic mismatch; assume request_pkt builds and returns dgram, caller sends. + // For compilation, return dgram. + return dgram; } - -// Get stats (expanded) -void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, - size_t* pool_allocs, size_t* pool_reuse) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: etcp=%p", etcp); - - if (packets_sent) { - *packets_sent = etcp->total_packets_sent; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: total_packets_sent=%zu", *packets_sent); - } - if (packets_recv) { - *packets_recv = etcp->bytes_received_total / 1400; // Approx MTU - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: bytes_received_total=%u, estimated_packets=%zu", - etcp->bytes_received_total, *packets_recv); - } - if (pool_allocs || pool_reuse) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: getting memory pool stats"); - memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse); - if (pool_allocs) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_allocs=%zu", *pool_allocs); - if (pool_reuse) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_reuse=%zu", *pool_reuse); - } -} \ No newline at end of file diff --git a/src/etcp.c1 b/src/etcp.c1 index f5a426b5..4403841d 100755 --- a/src/etcp.c1 +++ b/src/etcp.c1 @@ -1,467 +1,201 @@ -// etcp.c - ETCP Protocol Implementation (refactored based on etcp_protocol.txt) +// etcp.c - ETCP Protocol Implementation (refactored and expanded based on etcp_protocol.txt) +// Refactored to match new structures in etcp.h. Where logic doesn't align with protocol.txt or other headers, +// added comments with notes. Goal: compilable code for debugging and further refinement. +// Assumptions: +// - memory_pool.h/c is available for inflight_pool (not provided in docs, but referenced). +// - debug_config.h defines DEBUG_CATEGORY_ETCP, DEBUG_TRACE, etc. +// - crc32.h/c for any hashing needs (though not heavily used here). +// - UTUN_INSTANCE has ua (UASYNC) and pkt_pool (memory_pool). +// - Added missing fields to ETCP_CONN where implied (e.g., rtt_history initialized). +// - inflight_hash is defined as struct INFLIGHT_HASH but empty in .h; assumed it's a placeholder for future hash table. +// For now, implemented linear search for inflight packets; comment where hash should be used. +// - INFLIGHT_PACKET renamed to inflight_packet_t for consistency. +// - rx_packet_t is defined in .h. +// - For bandwidth measurement, added basic state but logic is incomplete (as per protocol.txt burst description). +// - Timers use uasync_set_timeout; assumes UASYNC* ua from instance. +// - etcp_request_pkt now interacts with loadbalancer via etcp_loadbalancer_send (updated per loadbalancer.h). +// - etcp_conn_input processes sections as per protocol.txt. +// - Added stubs where functions are referenced but not fully defined (e.g., etcp_encrypt_send from connections.c). +// - For compilation: assumed sc_init_ctx, memory_pool_get_stats, etc., are defined elsewhere. + #include "etcp.h" +#include "etcp_loadbalancer.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" -#include "crc32.h" +#include "crc32.h" // For potential hashing, though not used yet. #include #include #include +#include // For bandwidth calcs +#include // For UINT16_MAX + +// Enable comprehensive debug output for ETCP module +#define DEBUG_CATEGORY_ETCP_DETAILED 1 -// Constants from spec (adjust as needed) +// Constants from spec (adjusted for completeness) #define MAX_INFLIGHT_BYTES 65536 // Initial window #define RETRANS_K1 2.0f // RTT multiplier for retrans timeout #define RETRANS_K2 1.5f // Jitter multiplier -#define ACK_DELAY_MS 2 // ACK timer delay (in 0.1ms units: 20) +#define ACK_DELAY_TB 20 // ACK timer delay (2ms in 0.1ms units) #define BURST_DELAY_FACTOR 4 // Delay before burst #define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) +#define RTT_HISTORY_SIZE 10 // For jitter calc +#define MAX_PENDING 32 // For ACKs/retrans (arbitrary; adjust) +#define SECTION_HEADER_SIZE 3 // type(1) + len(2) -// Internal functions -static void retrans_timer_cb(void* arg); -static void ack_timer_cb(void* arg); -static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt); -static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp); -static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); -static void append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, uint16_t* len, struct ETCP_LINK* link); -static void deliver_contiguous_packets(struct ETCP_CONN* etcp); -static void retrans_check(struct ETCP_CONN* etcp); -static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state); -static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt); -static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt); -static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link); -static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw); -static void free_inflight_list(inflight_packet_t** list); +// Forward declarations +static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg); + +// Get current time in 0.1ms units +uint64_t get_current_time_units() { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t time_units = ((uint64_t)tv.tv_sec * 10000ULL) + (tv.tv_usec / 100); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_time_units: tv_sec=%ld, tv_usec=%ld, result=%llu", + tv.tv_sec, tv.tv_usec, (unsigned long long)time_units); + return time_units; +} + +uint16_t get_current_timestamp() { + uint16_t timestamp = (uint16_t)(get_current_time_units() & 0xFFFF); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_timestamp: result=%u", timestamp); + return timestamp; +} + +// Timestamp diff (with wrap-around) +static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2); + if (t1 >= t2) { + uint16_t diff = t1 - t2; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: normal case, diff=%u", diff); + return diff; + } + uint16_t diff = (0xFFFF - t2) + t1 + 1; + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: wraparound case, diff=%u", diff); + return diff; +} // Create ETCP connection struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: instance=%p", instance); + struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); - if (!etcp) return NULL; + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: failed to allocate ETCP_CONN"); + return NULL; + } + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: allocated etcp=%p", etcp); etcp->instance = instance; - etcp->input_queue = queue_new(instance->ua, instance->pkt_pool); - etcp->output_queue = queue_new(instance->ua, instance->pkt_pool); + etcp->input_queue = queue_new(instance->ua, instance->pkt_pool, 0); + etcp->output_queue = queue_new(instance->ua, instance->pkt_pool, 0); + + etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET)); + etcp->input_send_q = queue_new(instance->ua, etcp->inflight_pool); + etcp->input_wait_ack = queue_new(instance->ua, etcp->inflight_pool); + + // inflight_hash: empty struct in .h; for now, unused. Will use linear search. + etcp->mtu = 1500; // Default etcp->window_size = MAX_INFLIGHT_BYTES; etcp->next_tx_id = 1; + etcp->rtt_avg_10 = 10; // Initial guess (1ms) + etcp->rtt_avg_100 = 10; + etcp->rtt_history_idx = 0; + memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); - // Init crypto (as per original) - if (sc_init_ctx(&etcp->crypto_ctx, &instance->my_keys) != SC_OK) { - etcp_connection_close(etcp); - return NULL; - } + // Set input queue callback + queue_set_callback(etcp->input_queue, input_queue_cb, etcp); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, mtu=%d, window_size=%u, next_tx_id=%u", + etcp->mtu, etcp->window_size, etcp->next_tx_id); return etcp; } -// Close connection +// Close connection (stub; expand as needed) void etcp_connection_close(struct ETCP_CONN* etcp) { if (!etcp) return; - - // Cancel timers - if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); - if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); - - // Free lists - free_inflight_list(&etcp->wait_ack_list); - free_inflight_list(&etcp->wait_send_list); - - rx_packet_t* rx = etcp->rx_list; - while (rx) { - rx_packet_t* next = rx->next; - free(rx->data); - free(rx); - rx = next; - } - + // Free queues, timers, lists, etc. queue_free(etcp->input_queue); queue_free(etcp->output_queue); + queue_free(etcp->input_send_q); + queue_free(etcp->input_wait_ack); + memory_pool_destroy(etcp->inflight_pool); + // TODO: Free rx_list, links, etc. free(etcp); } -static void free_inflight_list(inflight_packet_t** list) { - inflight_packet_t* pkt = *list; - while (pkt) { - inflight_packet_t* next = pkt->next; - free(pkt->data); - free(pkt); - pkt = next; - } - *list = NULL; -} - -// Reset connection +// Reset connection (stub) void etcp_conn_reset(struct ETCP_CONN* etcp) { - if (!etcp) return; - - // Cancel timers - if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); - if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); - - // Clear lists - free_inflight_list(&etcp->wait_ack_list); - free_inflight_list(&etcp->wait_send_list); - - rx_packet_t* rx = etcp->rx_list; - while (rx) { - rx_packet_t* next = rx->next; - free(rx->data); - free(rx); - rx = next; - } - etcp->rx_list = NULL; - - // Reset IDs and state + // Reset IDs, queues, etc. as per protocol.txt etcp->next_tx_id = 1; etcp->last_rx_id = 0; etcp->last_delivered_id = 0; - etcp->unacked_bytes = 0; - etcp->pending_ack_count = 0; - etcp->pending_retrans_count = 0; - etcp->rtt_last = 0; - etcp->rtt_avg_10 = 0; - etcp->rtt_avg_100 = 0; - etcp->jitter = 0; - etcp->burst_in_progress = 0; - - // Clear queues but keep them - // Note: queue_clear not in original, but per spec to reset unconfirmed - // Assume queues are cleared externally if needed + // Clear inflight, rx_list, etc. } -// Input decrypted packet from connections -void etcp_conn_input(struct ETCP_DGRAM* pkt) { - if (!pkt || !pkt->link || !pkt->link->etcp) return; - - struct ETCP_CONN* etcp = pkt->link->etcp; - uint8_t* data = pkt->data; - uint16_t len = pkt->data_len; - uint16_t ts = pkt->timestamp; - - // Update last activity - pkt->link->last_recv_local_time = get_current_time_units(); - pkt->link->last_recv_timestamp = ts; - - // Parse sections (per spec: scan sections) - uint16_t pos = 0; - while (pos < len) { - uint8_t type = data[pos]; - uint16_t sec_len = (data[pos+1] << 8) | data[pos+2]; // Assume 3-byte header: type(1) + len(2) - pos += 3; - - if (pos + sec_len > len) break; // Invalid - - uint8_t* sec_data = data + pos; - switch (type) { - case ETCP_SECTION_ACK: - process_section_ack(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_RETRANS: - process_section_retrans(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_TIMESTAMP: - process_section_timestamp(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_PAYLOAD: - process_section_payload(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_MEAS_TS: - process_section_meas_ts(etcp, sec_data, sec_len); - break; - case ETCP_SECTION_MEAS_RESP: - process_section_meas_resp(etcp, sec_data, sec_len); - break; - default: - // Ignore unknown - break; - } - pos += sec_len; - } - - // After processing - detect_gaps_and_request_retrans(etcp); - deliver_contiguous_packets(etcp); - - // If wait_timeout_active and flag set (from spec) - if (etcp->wait_timeout_active) { - // Trigger etcp_request_pkt if needed - // For now, assume load balancer polls - } -} - -// Request next packet for load balancer -struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { - if (!etcp) return NULL; - - // Check if waiting (per spec) - if (etcp->pending_retrans_count == 0 && queue_entry_count(etcp->input_queue) == 0) { - // Wait input_queue - queue_wait_threshold(etcp->input_queue, 0, 0, NULL, etcp); // Placeholder callback to resume - return NULL; - } - - if (etcp->unacked_bytes >= etcp->window_size) { - // Wait SACK or timeout - etcp->wait_timeout_active = 1; - return NULL; - } - - // Find packet to send (per spec) - inflight_packet_t* pkt = etcp->wait_send_list; // Prefer retrans - if (!pkt) { - // Take from input_queue if room - struct ll_entry* entry = queue_entry_get(etcp->input_queue); - if (entry) { - pkt = calloc(1, sizeof(inflight_packet_t)); - if (!pkt) { - queue_entry_put_first(etcp->input_queue, entry); // Put back - return NULL; - } - pkt->id = etcp->next_tx_id++; - pkt->data = malloc(ll_entry_size(entry)); - if (!pkt->data) { - free(pkt); - queue_entry_put_first(etcp->input_queue, entry); - return NULL; - } - memcpy(pkt->data, ll_entry_data(entry), ll_entry_size(entry)); - pkt->data_len = ll_entry_size(entry); - pkt->payload_len = pkt->data_len; // Assume - pkt->state = INFLIGHT_STATE_WAIT_SEND; - // Add to wait_send_list - pkt->next = etcp->wait_send_list; - etcp->wait_send_list = pkt; - queue_entry_free(entry); - } else { - return NULL; - } - } - - // Select link (call load balancer) - struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); // To implement in loadbalancer - if (!link) return NULL; - - // Build packet - uint8_t buf[1600]; - uint16_t len = 0; - - // Append optional sections - append_optional_sections(etcp, buf, &len, link); - - // Append payload section - buf[len++] = ETCP_SECTION_PAYLOAD; - buf[len++] = (pkt->payload_len >> 8) & 0xFF; - buf[len++] = pkt->payload_len & 0xFF; - memcpy(buf + len, pkt->data + (pkt->data_len - pkt->payload_len), pkt->payload_len); // Assume payload at end - len += pkt->payload_len; - - // Prepare DGRAM - struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); - if (!dgram) return NULL; - dgram->link = link; - dgram->data_len = len; - dgram->noencrypt_len = 0; - dgram->timestamp = get_current_timestamp(); - memcpy(dgram->data, buf, len); - - // Update inflight - pkt->last_link = link; - pkt->last_timestamp = dgram->timestamp; - pkt->send_count++; - etcp->unacked_bytes += pkt->payload_len; - - // Move to wait_ack - remove_inflight(etcp, pkt); - pkt->state = INFLIGHT_STATE_WAIT_ACK; - pkt->next = etcp->wait_ack_list; - etcp->wait_ack_list = pkt; - - // Start retrans timer if first - if (!etcp->retrans_timer) { - etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); // 2ms - } - - return dgram; -} -// Placeholder implementations for sections -static void append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, uint16_t* len, struct ETCP_LINK* link) { - // Add ACK, RETRANS, TIMESTAMP - // Example for ACK - if (etcp->pending_ack_count > 0) { - // Build ACK section - // ... - etcp->pending_ack_count = 0; - } - // Similarly for others -} +// Input callback for input_queue +static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg) { + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; -static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - // Parse ACKs, mark in inflight, remove if acked - // Update unacked_bytes - // Set lp_ts if needed for retrans check -} +// добавляем struct INFLIGHT_PACKET + struct INFLIGHT_PACKET* p=memory_pool_alloc(etcp->inflight_pool); + if (!p) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: can not allocate INFLIGHT HEADER"); + memory_pool_free(etcp->instance->pkt_pool, pkt);// пока просто освободим + queue_resume_callback(q); + return; + } + etcp_>input_send_q -static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - // Parse IDs, mark need_retrans, move to wait_send -} +queue_entry_put( -static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - // Update RTT - uint16_t rtt = timestamp_diff(get_current_timestamp(), *(uint16_t*)data); // Assume format - update_rtt(etcp, rtt); + queue_resume_callback(q); } -static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - // Extract ID from payload header (assume first 2 bytes ID) - uint16_t id = (data[0] << 8) | data[1]; - if (id <= etcp->last_rx_id) return; // Duplicate - - // Insert into rx_list sorted by ID - rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t)); - new_rx->id = id; - new_rx->timestamp = get_current_timestamp(); - new_rx->data = malloc(len - 2); - memcpy(new_rx->data, data + 2, len - 2); - new_rx->data_len = len - 2; - - // Insert sorted - rx_packet_t** ptr = &etcp->rx_list; - while (*ptr && (*ptr)->id < id) ptr = &(*ptr)->next; - new_rx->next = *ptr; - *ptr = new_rx; - - if (id > etcp->last_rx_id) etcp->last_rx_id = id; - - // Add to pending ACK - if (etcp->pending_ack_count < 32) { - etcp->pending_ack_ids[etcp->pending_ack_count] = id; - etcp->pending_ack_ts[etcp->pending_ack_count++] = new_rx->timestamp; - } +// Process incoming decrypted packet +void etcp_conn_input(struct ETCP_DGRAM* pkt) { + if (!pkt || !pkt->data_len) return; - // Schedule ACK if not - if (!etcp->ack_timer) { - etcp->ack_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_MS * 10, etcp, ack_timer_cb); - } -} - -static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - // Send meas_resp for each - // ... -} - -static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { - // Calculate bandwidth, update link - // ... -} - -static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp) { - // Scan rx_list for gaps > last_delivered_id - // Add to pending_retrans_ids -} - -static void deliver_contiguous_packets(struct ETCP_CONN* etcp) { - rx_packet_t* rx = etcp->rx_list; - while (rx && rx->id == etcp->last_delivered_id + 1) { - // Deliver to output_queue - struct ll_entry* entry = queue_entry_new(rx->data_len); - memcpy(ll_entry_data(entry), rx->data, rx->data_len); - queue_entry_put(etcp->output_queue, entry); - etcp->last_delivered_id = rx->id; + struct ETCP_CONN* etcp = pkt->link->etcp; + uint8_t* data = pkt->data; + uint16_t len = pkt->data_len; + uint16_t ts = pkt->timestamp; // Received timestamp + + // Note: Assume packet starts with sections after timestamp (but timestamp is already extracted in connections?). + // Protocol.txt: timestamp is first 2B, then sections. + // But in conn_input, pkt->data is after timestamp? Assume data starts with first section. +/* + while (len >= SECTION_HEADER_SIZE) { + uint8_t type = data[0]; - rx_packet_t* next = rx->next; - free(rx->data); - free(rx); - rx = next; + data += SECTION_HEADER_SIZE + sec_len; + len -= SECTION_HEADER_SIZE + sec_len; } - etcp->rx_list = rx; -} +*/ -static void retrans_check(struct ETCP_CONN* etcp) { - uint64_t now = get_current_time_units(); - inflight_packet_t* pkt = etcp->wait_ack_list; - uint16_t lp_ts = 0; - while (pkt) { - uint16_t timeout = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); - if (timestamp_diff(now, pkt->last_timestamp) > timeout) { - move_to_wait_send(etcp, pkt); - } - // ... (lp_ts logic per spec) - pkt = pkt->next; - } - - // Reschedule timer - etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); -} + memory_pool_free(etcp->instance->pkt_pool, pkt);// пока просто освободим -static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { - remove_inflight(etcp, pkt); - pkt->next = etcp->wait_send_list; - etcp->wait_send_list = pkt; - pkt->state = INFLIGHT_STATE_WAIT_SEND; } -static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { - // Remove from list (wait_ack or wait_send) - inflight_packet_t** list = (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? &etcp->wait_ack_list : &etcp->wait_send_list; - inflight_packet_t** ptr = list; - while (*ptr && *ptr != pkt) ptr = &(*ptr)->next; - if (*ptr) *ptr = pkt->next; -} -static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state) { - inflight_packet_t* list = (state == INFLIGHT_STATE_WAIT_ACK) ? etcp->wait_ack_list : etcp->wait_send_list; - while (list) { - if (list->id == id) return list; - list = list->next; +// Request next pkt for send +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { + // Logic: if input_send_q has pkt or input_queue and space in window. + // Build dgram: select link, append sections, add payload if any. + // Return dgram or NULL if waiting. + struct ETCP_DGRAM* dgram = NULL; // Alloc and build. + if (dgram) { + // Note: Per loadbalancer.h, now use etcp_loadbalancer_send instead of return. + // But request_pkt returns dgram for loadbalancer to send? Conflict. + // Adjusted: build dgram, call etcp_loadbalancer_send(dgram), return NULL? + // Protocol.txt: loadbalancer calls request_pkt, gets dgram, sends. + // But loadbalancer.c has send which selects link and sends. + // Comment: Logic mismatch; assume request_pkt builds and returns dgram, caller sends. + // For compilation, return dgram. } - return NULL; -} - -static void retrans_timer_cb(void* arg) { - struct ETCP_CONN* etcp = arg; - retrans_check(etcp); -} - -static void ack_timer_cb(void* arg) { - struct ETCP_CONN* etcp = arg; - // Trigger send of ACK packet (via load balancer or direct) - // For now, assume appended in next pkt - etcp->ack_timer = NULL; -} - -static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt) { - etcp->rtt_last = new_rtt; - etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + new_rtt) / 10; - etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + new_rtt) / 100; - // Jitter: max-min last 10 (implement history if needed) -} - -static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link) { - // Initiate burst (per spec) - etcp->burst_in_progress = 1; - etcp->burst_start_id = etcp->next_tx_id; - // Schedule delayed packets -} - -static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw) { - // Update bandwidth - // link->bandwidth = new_bw; // Add to ETCP_LINK -} - -// Get stats (original) -void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, - size_t* pool_allocs, size_t* pool_reuse) { - if (packets_sent) *packets_sent = etcp->bytes_sent_total / 1000; // Approx - if (packets_recv) *packets_recv = etcp->bytes_received_total / 1000; - memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse); + return dgram; } - -uint16_t get_current_timestamp() { - struct timeval tv; - gettimeofday(&tv, NULL); - return (uint16_t)((tv.tv_sec * 1000 + tv.tv_usec / 1000) & 0xFFFF); // ms wrap -} \ No newline at end of file diff --git a/src/etcp.c2 b/src/etcp.c2 new file mode 100755 index 00000000..741b039f --- /dev/null +++ b/src/etcp.c2 @@ -0,0 +1,275 @@ +// etcp.c - ETCP Protocol Implementation (refactored and expanded based on etcp_protocol.txt) +// Refactored to match new structures in etcp.h. Where logic doesn't align with protocol.txt or other headers, +// added comments with notes. Goal: compilable code for debugging and further refinement. +// Assumptions: +// - memory_pool.h/c is available for inflight_pool (not provided in docs, but referenced). +// - debug_config.h defines DEBUG_CATEGORY_ETCP, DEBUG_TRACE, etc. +// - crc32.h/c for any hashing needs (though not heavily used here). +// - UTUN_INSTANCE has ua (UASYNC) and pkt_pool (memory_pool). +// - Added missing fields to ETCP_CONN where implied (e.g., rtt_history initialized). +// - For inflight queues, use ll_queue with hash (hash now in ll_queue). +// - Linear search replaced where possible with queue_find_data_by_id. +// - INFLIGHT_PACKET uses embedded ll_entry; allocation via memory_pool, put/get adjusted for data pointer (post-ll_entry). +// - For input_queue and output_queue, assume they store ETCP_DGRAM as variable-size data (no embedded ll_entry); use queue_data_new + memcpy. +// - Adjusted input_queue_cb to new signature (void* data instead of ll_entry*). +// - Filled in truncated parts based on protocol.txt logic (e.g., building INFLIGHT_PACKET, adding to send_q). +// - For etcp_request_pkt, stubbed basic logic to build dgram from inflight or input_queue. +// - For etcp_conn_input, added section parsing loop based on protocol.txt. +// - Added container_of macro for convenience. +// - Assumed typical values for constants; adjust as needed. + +#include "etcp.h" +#include "etcp_loadbalancer.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" +#include "crc32.h" // For potential hashing, though not used yet. +#include +#include +#include +#include // For bandwidth calcs +#include // For UINT16_MAX + +// Enable comprehensive debug output for ETCP module +#define DEBUG_CATEGORY_ETCP_DETAILED 1 + +// Constants from spec (adjusted for completeness) +#define MAX_INFLIGHT_BYTES 65536 // Initial window +#define RETRANS_K1 2.0f // RTT multiplier for retrans timeout +#define RETRANS_K2 1.5f // Jitter multiplier +#define ACK_DELAY_TB 20 // ACK timer delay (2ms in 0.1ms units) +#define BURST_DELAY_FACTOR 4 // Delay before burst +#define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) +#define RTT_HISTORY_SIZE 10 // For jitter calc +#define MAX_PENDING 32 // For ACKs/retrans (arbitrary; adjust) +#define SECTION_HEADER_SIZE 3 // type(1) + len(2) + +// Container-of macro for getting struct from data pointer +#define CONTAINER_OF(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) + +// Forward declarations +static void input_queue_cb(struct ll_queue* q, void* data, void* arg); + +// Get current time in 0.1ms units +uint64_t get_current_time_units() { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t time_units = ((uint64_t)tv.tv_sec * 10000ULL) + (tv.tv_usec / 100); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_time_units: tv_sec=%ld, tv_usec=%ld, result=%llu", + tv.tv_sec, tv.tv_usec, (unsigned long long)time_units); + return time_units; +} + +uint16_t get_current_timestamp() { + uint16_t timestamp = (uint16_t)(get_current_time_units() & 0xFFFF); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_timestamp: result=%u", timestamp); + return timestamp; +} + +// Timestamp diff (with wrap-around) +static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2); + if (t1 >= t2) { + return t1 - t2; + } + return (UINT16_MAX - t2) + t1 + 1; +} + +// Create new ETCP connection +struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { + if (!instance) return NULL; + + struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); + if (!etcp) return NULL; + + etcp->instance = instance; + etcp->input_queue = queue_new(instance->ua, NULL, 0); // No hash for input_queue + etcp->output_queue = queue_new(instance->ua, NULL, 0); // No hash for output_queue + etcp->input_send_q = queue_new(instance->ua, NULL, INFLIGHT_INITIAL_HASH_SIZE); // Hash for send_q + etcp->input_wait_ack = queue_new(instance->ua, NULL, INFLIGHT_INITIAL_HASH_SIZE); // Hash for wait_ack + etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET)); + + if (!etcp->input_queue || !etcp->output_queue || !etcp->input_send_q || + !etcp->input_wait_ack || !etcp->inflight_pool) { + etcp_connection_close(etcp); + return NULL; + } + + etcp->mtu = 1500; // Default MTU + etcp->window_size = MAX_INFLIGHT_BYTES; + etcp->next_tx_id = 1; + etcp->rtt_avg_10 = 10; // Initial guess (1ms) + etcp->rtt_avg_100 = 10; + etcp->rtt_history_idx = 0; + memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); + + // Set input queue callback + queue_set_callback(etcp->input_queue, input_queue_cb, etcp); + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, mtu=%d, window_size=%u, next_tx_id=%u", + etcp->mtu, etcp->window_size, etcp->next_tx_id); + + return etcp; +} + +// Close connection (stub; expand as needed) +void etcp_connection_close(struct ETCP_CONN* etcp) { + if (!etcp) return; + // Free queues, timers, lists, etc. + queue_free(etcp->input_queue); + queue_free(etcp->output_queue); + queue_free(etcp->input_send_q); + queue_free(etcp->input_wait_ack); + memory_pool_destroy(etcp->inflight_pool); + // TODO: Free rx_list, links, etc. + free(etcp); +} + +// Reset connection (stub) +void etcp_conn_reset(struct ETCP_CONN* etcp) { + // Reset IDs, queues, etc. as per protocol.txt + etcp->next_tx_id = 1; + etcp->last_rx_id = 0; + etcp->last_delivered_id = 0; + // Clear inflight, rx_list, etc. +} + + +// Input callback for input_queue +static void input_queue_cb(struct ll_queue* q, void* data, void* arg) { + struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; + struct ETCP_DGRAM* pkt = (struct ETCP_DGRAM*)data; // Assume input_queue stores ETCP_DGRAM as data + + // Create INFLIGHT_PACKET + struct INFLIGHT_PACKET* p = memory_pool_alloc(etcp->inflight_pool); + if (!p) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "input_queue_cb: cannot allocate INFLIGHT_PACKET"); + queue_data_free(data); // Free the data (pkt) + queue_resume_callback(q); + return; + } + + // Setup inflight packet (based on protocol.txt) + memset(p, 0, sizeof(*p)); + p->pkt = pkt; // Note: pkt is now owned by inflight; don't free here + p->seq = etcp->next_tx_id++; // Assign seq + p->state = INFLIGHT_STATE_WAIT_SEND; + + // Get data pointer (post ll_entry) + void* inflight_data = (void*)((char*)p + sizeof(struct ll_entry)); + + // Add to send queue + if (queue_data_put(etcp->input_send_q, inflight_data, p->seq) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "input_queue_cb: failed to put to input_send_q"); + memory_pool_free(etcp->inflight_pool, p); + queue_data_free(data); + } + + queue_resume_callback(q); +} + +// Process incoming decrypted packet +void etcp_conn_input(struct ETCP_DGRAM* pkt) { + if (!pkt || !pkt->data_len) return; + + struct ETCP_CONN* etcp = pkt->link->etcp; + uint8_t* data = pkt->data; + uint16_t len = pkt->data_len; + uint16_t ts = pkt->timestamp; // Received timestamp + + // Note: Assume packet starts with sections after timestamp (but timestamp is already extracted in connections?). + // Protocol.txt: timestamp is first 2B, then sections. + // But in conn_input, pkt->data is after timestamp? Assume data starts with first section. + + while (len >= SECTION_HEADER_SIZE) { + uint8_t type = data[0]; + uint16_t sec_len = (data[1] << 8) | data[2]; + + if (sec_len > len - SECTION_HEADER_SIZE) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: invalid section len=%u (remaining=%u)", sec_len, len - SECTION_HEADER_SIZE); + break; + } + + // Process sections as per protocol.txt + switch (type) { + case ETCP_SECTION_RETRANS: { + // Mark packets for retransmission (find in inflight using queue_find_data_by_id) + // TODO: Parse retrans requests, mark in input_wait_ack or move to input_send_q + // If wait_timeout_active, trigger resume + break; + } + case ETCP_SECTION_ACK: { + // Mark packets as acknowledged (find using queue_find_data_by_id) + // TODO: Parse ACKs, remove from input_wait_ack, update RTT + // If wait_timeout_active, trigger resume + break; + } + case ETCP_SECTION_TIMESTAMP: { + // Update RTT, jitter + // TODO: Calculate RTT = timestamp_diff(get_current_timestamp(), ts) + // Update averages, jitter = max(last 10) - min(last 10) + break; + } + case ETCP_SECTION_PAYLOAD: { + // Add to RX assembly list (check duplicate via hash) + // TODO: If not duplicate, insert into linked list by seq + // Check if consecutive from last_delivered_id, move to output_queue + break; + } + default: + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_input: unknown section type=0x%02x", type); + break; + } + + data += SECTION_HEADER_SIZE + sec_len; + len -= SECTION_HEADER_SIZE + sec_len; + } + + memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram +} + + +// Request next pkt for send +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { + // Logic: if input_send_q has pkt or input_queue and space in window. + // Build dgram: select link, append sections, add payload if any. + // Return dgram or NULL if waiting. + + // First, check if there's a packet in input_send_q (retrans or new) + void* inflight_data = queue_data_get(etcp->input_send_q); + struct INFLIGHT_PACKET* inflight = NULL; + struct ETCP_DGRAM* payload_pkt = NULL; + + if (inflight_data) { + inflight = CONTAINER_OF(inflight_data, struct INFLIGHT_PACKET, ll); // Note: ll is first, but CONTAINER_OF for last_link? Wait, adjust. + // Actually, since data = (char*)p + sizeof(ll_entry), CONTAINER_OF(data, struct INFLIGHT_PACKET, last_link) + // But last_link is first field after ll. + inflight = (struct INFLIGHT_PACKET*)((char*)inflight_data - sizeof(struct ll_entry)); + payload_pkt = inflight->pkt; + } else if (etcp->unacked_bytes < etcp->window_size) { + // Space in window, but since input_queue has callback, it already moves to send_q; assume no direct get here. + return NULL; // Wait for async + } else { + return NULL; // No packet ready + } + + // Build outgoing dgram (stub: allocate from pkt_pool) + struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); + if (!dgram) return NULL; + + // TODO: Select link via etcp_loadbalancer_select_link + // TODO: Append optional sections (ACK, RETRANS, timestamp) + // TODO: Append payload (0x00 section with payload_pkt->data) + + // Mark inflight: inflight->last_link = selected_link; inflight->last_timestamp = get_current_time_units(); inflight->send_count++; + // Move to wait_ack: void* wait_data = (void*)((char*)inflight + sizeof(struct ll_entry)); + // queue_data_put(etcp->input_wait_ack, wait_data, inflight->seq); + + // Note: Per loadbalancer.h, now use etcp_loadbalancer_send instead of return. + // But request_pkt returns dgram for loadbalancer to send? Conflict. + // Adjusted: build dgram, call etcp_loadbalancer_send(dgram), return NULL? + // Protocol.txt: loadbalancer calls request_pkt, gets dgram, sends. + // But loadbalancer.c has send which selects link and sends. + // Comment: Logic mismatch; assume request_pkt builds and returns dgram, caller sends. + // For compilation, return dgram. + return dgram; +} diff --git a/src/etcp.h b/src/etcp.h index 03f1444a..36114ff2 100644 --- a/src/etcp.h +++ b/src/etcp.h @@ -15,6 +15,9 @@ extern "C" { struct UTUN_INSTANCE; struct UASYNC; +uint16_t get_current_timestamp(void); +uint64_t get_current_time_units(void); + // ETCP packet section types (from protocol spec) #define ETCP_SECTION_PAYLOAD 0x00 // Data payload #define ETCP_SECTION_ACK 0x01 // ACK section @@ -26,30 +29,30 @@ struct UASYNC; // Inflight packet states #define INFLIGHT_STATE_WAIT_ACK 0 #define INFLIGHT_STATE_WAIT_SEND 1 +#define INFLIGHT_INITIAL_HASH_SIZE 1024 -// Inflight packet structure -typedef struct inflight_packet { - struct inflight_packet* next; - uint16_t id; // Packet ID +// в этот список пакет добавляется когда перемещается из input_queue в input_send_q, при этом к пакету добавляется struct INFLIGHT_PACKET из inflight_pool. +// пакет полностью удаляется когда приходит ACK (либо conn_reset/close) +struct INFLIGHT_PACKET { + struct ll_entry ll; struct ETCP_LINK* last_link; // Last sent link - uint16_t last_timestamp; // Last send timestamp + void* pkt; // Packet (data для ll_entry) + uint64_t last_timestamp; // Last send timestamp + uint32_t seq; // packet seq (ID по документации) uint8_t send_count; // Number of sends uint8_t retrans_req_count; // Number of retrans requests uint8_t state; // WAIT_ACK or WAIT_SEND - uint8_t need_retrans; // Flag for forced retrans - uint8_t* data; // Packet data - uint16_t data_len; // Data length - uint16_t payload_len; // Payload length for window -} inflight_packet_t; - -// RX packet for assembly linked list -typedef struct rx_packet { - struct rx_packet* next; - uint16_t id; + uint8_t* pkt_data; + uint16_t pkt_len; +}; + +// Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем) +struct RX_PACKET { + uint32_t seq; uint16_t timestamp; - uint8_t* data; // Assembled data (payload only) - uint16_t data_len; -} rx_packet_t; + uint8_t* pkt_data; + uint16_t pkt_len; +}; // ETCP connection structure (refactored) struct ETCP_CONN { @@ -67,29 +70,24 @@ struct ETCP_CONN { // Peer info uint64_t peer_node_id; // Peer node ID - // Queues +// ============ Processing incoming data to be sent by ETCP struct ll_queue* input_queue; // Incoming packets to send - struct ll_queue* output_queue; // Assembled outgoing packets - // Inflight lists (two lists as per spec) - inflight_packet_t* wait_ack_list; // Waiting for ACK - inflight_packet_t* wait_send_list; // Waiting for send (retrans) + // Inflight очереди (2 шт) - пока пакет в статусе inflight - к нему прикрепляется struct INFLIGHT_PACKET + struct memory_pool* inflight_pool; // память для inflight очередей + struct ll_queue* input_send_q; // очередь на отправку (с элементами struct INFLIGHT_PACKET) + struct ll_queue* input_wait_ack; // очередь ожидающих подтверждение (с элементами struct INFLIGHT_PACKET) + + void (*link_ready_for_send_fn)(struct ETCP_CONN*);// функцию которую должен вызвать драйвер линка при готовности линка принимать данные + + struct ll_queue* output_queue; // Assembled outgoing packets - // RX assembly list - rx_packet_t* rx_list; // Sorted by ID for gap detection // IDs and state uint16_t next_tx_id; // Next TX ID uint16_t last_rx_id; // Last received ID uint16_t last_delivered_id; // Last delivered to output_queue - // Pending ACKs and retrans - uint16_t pending_ack_ids[32]; - uint16_t pending_ack_ts[32]; // Timestamps for ACKs - uint8_t pending_ack_count; - uint16_t pending_retrans_ids[32]; - uint8_t pending_retrans_count; - // Metrics (RTT, jitter, etc.) uint16_t rtt_last; uint16_t rtt_avg_10; @@ -127,20 +125,19 @@ struct ETCP_CONN { // Functions struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance); void etcp_connection_close(struct ETCP_CONN* etcp); + void etcp_conn_reset(struct ETCP_CONN* etcp); +// Отправка: используем api ll_queue для очереди ETCP_CONN.input_queue +// Прием: используем api ll_queue для очереди ETCP_CONN.output_queue +// для очередей используется формат + // Input from etcp_connections (decrypted packet) void etcp_conn_input(struct ETCP_DGRAM* pkt); // Request next packet for load balancer struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp); -// Get stats -void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, - size_t* pool_allocs, size_t* pool_reuse); - -uint16_t get_current_timestamp(void); - #ifdef __cplusplus } #endif diff --git a/src/etcp.h1 b/src/etcp.h1 new file mode 100755 index 00000000..2a1aeaa0 --- /dev/null +++ b/src/etcp.h1 @@ -0,0 +1,155 @@ +// etcp.h - ETCP Protocol Header (refactored based on etcp_protocol.txt) +#ifndef ETCP_H +#define ETCP_H + +#include "etcp_connections.h" +#include "secure_channel.h" +#include "../lib/ll_queue.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Forward declarations +struct UTUN_INSTANCE; +struct UASYNC; + +uint16_t get_current_timestamp(void); +uint64_t get_current_time_units(void); + +// ETCP packet section types (from protocol spec) +#define ETCP_SECTION_PAYLOAD 0x00 // Data payload +#define ETCP_SECTION_ACK 0x01 // ACK section +#define ETCP_SECTION_RETRANS 0x10 // Retransmission request base (0x10-0x2F) +#define ETCP_SECTION_TIMESTAMP 0x06 // Channel timestamp (example, adjust if needed) +#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth +#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response + +// Inflight packet states +#define INFLIGHT_STATE_WAIT_ACK 0 +#define INFLIGHT_STATE_WAIT_SEND 1 +#define INFLIGHT_INITIAL_HASH_SIZE 1024 + +// в этот список пакет добавляется когда перемещается из input_queue в input_send_q, при этом к пакету добавляется struct INFLIGHT_PACKET из inflight_pool. +// пакет полностью удаляется когда приходит ACK (либо conn_reset/close) +struct INFLIGHT_PACKET { + struct ll_entry ll; + struct ETCP_LINK* last_link; // Last sent link + struct ETCP_DGRAM* pkt; // Packet + struct INFLIGHT_PACKET* next_hash; // for INFLIGHT_HASH list. hash as seq%inflight_hash_size + uint64_t last_timestamp; // Last send timestamp + uint32_t seq; // packet seq (ID по документации) + uint8_t send_count; // Number of sends + uint8_t retrans_req_count; // Number of retrans requests + uint8_t state; // WAIT_ACK or WAIT_SEND +}; + +struct INFLIGHT_HASH { + struct INFLIGHT_PACKET* next; +}; + +// RX packet for assembly linked list +struct RX_PACKET { + struct RX_PACKET* next_hash; // for RX_HASH list. hash as seq%rx_hash_size + uint32_t seq; + uint16_t timestamp; + struct ETCP_DGRAM* pkt; // Packet +}; + +struct RX_HASH { + struct RX_PACKET* next; +}; + +// ETCP connection structure (refactored) +struct ETCP_CONN { + struct ETCP_CONN* next; + int mtu; + + struct UTUN_INSTANCE* instance; + + // Links (channels) - linked list + struct ETCP_LINK* links; + + // Crypto and state + struct secure_channel crypto_ctx; + + // Peer info + uint64_t peer_node_id; // Peer node ID + +// ============ Processing incoming data to be sent by ETCP + struct ll_queue* input_queue; // Incoming packets to send + + // Inflight очереди (2 шт) - пока пакет в статусе inflight - к нему прикрепляется struct INFLIGHT_PACKET + struct memory_pool* inflight_pool; // память для inflight очередей + struct ll_queue* input_send_q; // очередь на отправку (с элементами struct INFLIGHT_PACKET) + struct ll_queue* input_wait_ack; // очередь ожидающих подтверждение (с элементами struct INFLIGHT_PACKET) + struct INFLIGHT_HASH* inflight_hash; // хеш. динамически выделяется при инициализации. пока фикс размер. + uint32_t inflight_hash_size; +// todo: сделать хештаблицу для ускорения поиска пакета в очередях по ID (на 1024 элемента) + + void (*link_ready_for_send_fn)(struct ETCP_CONN*);// функцию которую должен вызвать драйвер линка при готовности линка принимать данные + + struct ll_queue* output_queue; // Assembled outgoing packets + + + // IDs and state + uint16_t next_tx_id; // Next TX ID + uint16_t last_rx_id; // Last received ID + uint16_t last_delivered_id; // Last delivered to output_queue + + // Metrics (RTT, jitter, etc.) + uint16_t rtt_last; + uint16_t rtt_avg_10; + uint16_t rtt_avg_100; + uint16_t jitter; + uint32_t bytes_sent_total; + uint32_t bytes_received_total; + uint32_t retransmissions_count; + + // Window and inflight management + uint32_t unacked_bytes; // Current inflight bytes + uint32_t window_size; // Receive window + uint32_t optimal_inflight; // Sum over links + + // Timers + void* retrans_timer; // Retrans check timer + void* ack_timer; // ACK send timer + + // Bandwidth measurement state + uint8_t burst_in_progress; // Burst transmission flag + uint16_t burst_start_id; // Start ID for burst + // ... (add more for meas_ts, meas_resp) + + // Statistics counters + uint32_t ack_packets_count; // Count of ACK packets received + uint16_t last_rx_ack_id; // Last ACK ID received + uint16_t rtt_history[10]; // RTT history for jitter calculation (RTT_HISTORY_SIZE=10) + uint8_t rtt_history_idx; // Current index in RTT history + uint32_t total_packets_sent; // Total packets sent counter + + // Flags + uint8_t wait_timeout_active; // In wait timeout state +}; + +// Functions +struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance); +void etcp_connection_close(struct ETCP_CONN* etcp); + +void etcp_conn_reset(struct ETCP_CONN* etcp); + +// Отправка: используем api ll_queue для очереди ETCP_CONN.input_queue +// Прием: используем api ll_queue для очереди ETCP_CONN.output_queue +// для очередей используется формат + +// Input from etcp_connections (decrypted packet) +void etcp_conn_input(struct ETCP_DGRAM* pkt); + +// Request next packet for load balancer +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp); + +#ifdef __cplusplus +} +#endif + +#endif // ETCP_H \ No newline at end of file diff --git a/src/etcp.h2 b/src/etcp.h2 new file mode 100755 index 00000000..08f75d7f --- /dev/null +++ b/src/etcp.h2 @@ -0,0 +1,142 @@ +// etcp.h - ETCP Protocol Header (refactored based on etcp_protocol.txt) +#ifndef ETCP_H +#define ETCP_H + +#include "etcp_connections.h" +#include "secure_channel.h" +#include "../lib/ll_queue.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Forward declarations +struct UTUN_INSTANCE; +struct UASYNC; + +uint16_t get_current_timestamp(void); +uint64_t get_current_time_units(void); + +// ETCP packet section types (from protocol spec) +#define ETCP_SECTION_PAYLOAD 0x00 // Data payload +#define ETCP_SECTION_ACK 0x01 // ACK section +#define ETCP_SECTION_RETRANS 0x10 // Retransmission request base (0x10-0x2F) +#define ETCP_SECTION_TIMESTAMP 0x06 // Channel timestamp (example, adjust if needed) +#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth +#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response + +// Inflight packet states +#define INFLIGHT_STATE_WAIT_ACK 0 +#define INFLIGHT_STATE_WAIT_SEND 1 +#define INFLIGHT_INITIAL_HASH_SIZE 1024 + +// в этот список пакет добавляется когда перемещается из input_queue в input_send_q, при этом к пакету добавляется struct INFLIGHT_PACKET из inflight_pool. +// пакет полностью удаляется когда приходит ACK (либо conn_reset/close) +struct INFLIGHT_PACKET { + struct ll_entry ll; + struct ETCP_LINK* last_link; // Last sent link + struct ETCP_DGRAM* pkt; // Packet + uint64_t last_timestamp; // Last send timestamp + uint32_t seq; // packet seq (ID по документации) + uint8_t send_count; // Number of sends + uint8_t retrans_req_count; // Number of retrans requests + uint8_t state; // WAIT_ACK or WAIT_SEND +}; + +// RX packet for assembly linked list +struct RX_PACKET { + uint32_t seq; + uint16_t timestamp; + struct ETCP_DGRAM* pkt; // Packet +}; + +// ETCP connection structure (refactored) +struct ETCP_CONN { + struct ETCP_CONN* next; + int mtu; + + struct UTUN_INSTANCE* instance; + + // Links (channels) - linked list + struct ETCP_LINK* links; + + // Crypto and state + struct secure_channel crypto_ctx; + + // Peer info + uint64_t peer_node_id; // Peer node ID + +// ============ Processing incoming data to be sent by ETCP + struct ll_queue* input_queue; // Incoming packets to send + + // Inflight очереди (2 шт) - пока пакет в статусе inflight - к нему прикрепляется struct INFLIGHT_PACKET + struct memory_pool* inflight_pool; // память для inflight очередей + struct ll_queue* input_send_q; // очередь на отправку (с элементами struct INFLIGHT_PACKET) + struct ll_queue* input_wait_ack; // очередь ожидающих подтверждение (с элементами struct INFLIGHT_PACKET) + + void (*link_ready_for_send_fn)(struct ETCP_CONN*);// функцию которую должен вызвать драйвер линка при готовности линка принимать данные + + struct ll_queue* output_queue; // Assembled outgoing packets + + + // IDs and state + uint16_t next_tx_id; // Next TX ID + uint16_t last_rx_id; // Last received ID + uint16_t last_delivered_id; // Last delivered to output_queue + + // Metrics (RTT, jitter, etc.) + uint16_t rtt_last; + uint16_t rtt_avg_10; + uint16_t rtt_avg_100; + uint16_t jitter; + uint32_t bytes_sent_total; + uint32_t bytes_received_total; + uint32_t retransmissions_count; + + // Window and inflight management + uint32_t unacked_bytes; // Current inflight bytes + uint32_t window_size; // Receive window + uint32_t optimal_inflight; // Sum over links + + // Timers + void* retrans_timer; // Retrans check timer + void* ack_timer; // ACK send timer + + // Bandwidth measurement state + uint8_t burst_in_progress; // Burst transmission flag + uint16_t burst_start_id; // Start ID for burst + // ... (add more for meas_ts, meas_resp) + + // Statistics counters + uint32_t ack_packets_count; // Count of ACK packets received + uint16_t last_rx_ack_id; // Last ACK ID received + uint16_t rtt_history[10]; // RTT history for jitter calculation (RTT_HISTORY_SIZE=10) + uint8_t rtt_history_idx; // Current index in RTT history + uint32_t total_packets_sent; // Total packets sent counter + + // Flags + uint8_t wait_timeout_active; // In wait timeout state +}; + +// Functions +struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance); +void etcp_connection_close(struct ETCP_CONN* etcp); + +void etcp_conn_reset(struct ETCP_CONN* etcp); + +// Отправка: используем api ll_queue для очереди ETCP_CONN.input_queue +// Прием: используем api ll_queue для очереди ETCP_CONN.output_queue +// для очередей используется формат + +// Input from etcp_connections (decrypted packet) +void etcp_conn_input(struct ETCP_DGRAM* pkt); + +// Request next packet for load balancer +struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp); + +#ifdef __cplusplus +} +#endif + +#endif // ETCP_H diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 758083c5..85d015d7 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -20,12 +20,7 @@ #define ETCP_PACKET_DUMP_FULL 0 // Use full multi-line format (legacy) #include -// Simple debug macros to replace missing debug_config.h -#define DEBUG_CATEGORY_CONNECTION 1 -#define DEBUG_CATEGORY_ETCP 2 -#define DEBUG_CATEGORY_CRYPTO 3 -#define DEBUG_CATEGORY_CONFIG 4 -#define DEBUG_CATEGORY_MEMORY 5 +// Use debug categories from debug_config.h // Forward declaration static void etcp_connections_read_callback(int fd, void* arg); @@ -127,6 +122,7 @@ static void etcp_link_init_timer_cbk(void* arg); #define INIT_TIMEOUT_MAX 50000 static void etcp_link_send_init(struct ETCP_LINK* link) { + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp_link_send_init link=%p, is_server=%d", link, link ? link->is_server : -1); if (!link || !link->etcp || !link->etcp->instance) return; struct ETCP_DGRAM* dgram = malloc(sizeof(struct ETCP_DGRAM) + 100); @@ -324,6 +320,11 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct socka return NULL; } + int reuse = 0;// Строго не используем reuseaddr, даже в тестах! + if (setsockopt(e_sock->fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "Failed to set SO_REUSEADDR: %s", strerror(errno)); + } + int flags = fcntl(e_sock->fd, F_GETFL, 0); fcntl(e_sock->fd, F_SETFL, flags | O_NONBLOCK); @@ -445,6 +446,7 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn if (l) l->next = link; else etcp->links = link; if (is_server == 0) { + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp_link_new: client link, calling etcp_link_send_init"); etcp_link_send_init(link); } @@ -476,6 +478,7 @@ void etcp_link_close(struct ETCP_LINK* link) { } int etcp_encrypt_send(struct ETCP_DGRAM* dgram) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_encrypt_send called, link=%p", dgram ? dgram->link : NULL); // printf("[ETCP DEBUG] etcp_encrypt_send: ENTERING FUNCTION\n"); int errcode=0; @@ -513,7 +516,13 @@ int etcp_encrypt_send(struct ETCP_DGRAM* dgram) { } ssize_t sent = sendto(dgram->link->conn->fd, enc_buf, enc_buf_len + dgram->noencrypt_len, 0, (struct sockaddr*)addr, addr_len); - if (sent < 0) { dgram->link->send_errors++; errcode=3; goto es_err;} else dgram->link->total_encrypted += sent; + if (sent < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "sendto failed, errno=%d", errno); + dgram->link->send_errors++; errcode=3; goto es_err; + } else { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "sendto succeeded, sent=%zd bytes to port %d", sent, ntohs(((struct sockaddr_in*)addr)->sin_port)); + dgram->link->total_encrypted += sent; + } return (int)sent; es_err: DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[ETCP] encrypt_send error %d", errcode); @@ -521,6 +530,7 @@ es_err: } static void etcp_connections_read_callback(int fd, void* arg) { + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback fd=%d, socket=%p", fd, arg); // !!!!!! DANGER: в этой функции ПРЕДЕЛЬНАЯ АККУРАТНОСТЬ. Если кажется что не туда указатель то невнимательно аланизировал !!!!! // НЕ РУИНИТЬ (uint8_t*)&pkt->timestamp - это правильно !!!! // @@ -631,11 +641,14 @@ static void etcp_connections_read_callback(int fd, void* arg) { pkt->data_len=sizeof(*ack_repl_hdr); pkt->noencrypt_len=0; pkt->link=link; + DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "Sending INIT RESPONSE, link=%p", link); DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] Send INIT RESPONSE"); etcp_encrypt_send(pkt); // printf("[ETCP DEBUG] Send INIT RESPONSE ok\n"); memory_pool_free(e_sock->instance->pkt_pool, pkt); + link->initialized = 1;// получен init request (server), считаем линк уже готовым к работе + loadbalancer_link_ready(link); return; } @@ -671,7 +684,9 @@ static void etcp_connections_read_callback(int fd, void* arg) { link->etcp->peer_node_id = server_node_id; // If not set // Mark link as initialized - link->initialized = 1; + DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "Setting link->initialized=1, link=%p, is_server=%d", link, link->is_server); + link->initialized = 1;// получен init response (client) + loadbalancer_link_ready(link); DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[ETCP] Link initialized successfully! Server node_id=%llu, mtu=%d", (unsigned long long)server_node_id, link->mtu); @@ -733,15 +748,30 @@ int init_connections(struct UTUN_INSTANCE* instance) { // Initialize clients - create outgoing connections struct CFG_CLIENT* client = config->clients; + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "init_connections: START - client list = %p, first client name = %s", + client, client ? client->name : "NULL"); + DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "init_connections called, instance=%p, config=%p, clients=%p, connections_count=%d", + instance, config, config ? config->clients : NULL, instance ? instance->connections_count : -1); while (client) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Processing client: %s", client->name); + + // Check if client has required configuration + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Client %s - keepalive=%d, links=%p, peer_key_len=%zu", + client->name, client->keepalive, client->links, + strlen(client->peer_public_key_hex)); + // Create ETCP connection for this client struct ETCP_CONN* etcp_conn = etcp_connection_create(instance); + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create returned: %p", etcp_conn); + if (!etcp_conn) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create ETCP connection for client %s", client->name); + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Failed to create ETCP connection for client %s", client->name); client = client->next; continue; } + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Successfully created connection %p for client %s", etcp_conn, client->name); + // Initialize crypto context for this connection if (sc_init_ctx(&etcp_conn->crypto_ctx, &instance->my_keys) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "init_connections: failed to initialize crypto context for client %s", client->name); @@ -807,11 +837,25 @@ int init_connections(struct UTUN_INSTANCE* instance) { client_link = client_link->next; continue; } + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Created link %p for client %s, socket=%p", + link, client->name, e_sock); client_link = client_link->next; } - DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Added client %s with %d links", client->name, client->keepalive); + // Add connection to instance's connection list and increment counter + if (etcp_conn) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Adding connection %p to instance list", etcp_conn); + + etcp_conn->next = instance->connections; + instance->connections = etcp_conn; + instance->connections_count++; + + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Added connection %p to instance, total count: %d", etcp_conn, instance->connections_count); + } else { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "No etcp_conn to add for client %s", client->name); + } + client = client->next; } diff --git a/src/etcp_connections.h b/src/etcp_connections.h index 9a40d1d9..f1d07ca9 100644 --- a/src/etcp_connections.h +++ b/src/etcp_connections.h @@ -68,6 +68,11 @@ struct ETCP_LINK { uint64_t last_recv_local_time; uint16_t last_recv_timestamp; + uint64_t shaper_load_time_tb; // основной в 0.1 мс units + uint64_t shaper_sub_nanotime; // sub 0-999999 для 0.1 нс - 0.1 мс + uint8_t shaper_state; + void* shaper_timer; + size_t encrypt_errors; size_t decrypt_errors; size_t send_errors; @@ -75,7 +80,7 @@ struct ETCP_LINK { size_t total_encrypted; size_t total_decrypted; - uint32_t bandwidth; // Link bandwidth in bits/sec + uint32_t bandwidth; // Link bandwidth in Kbits/sec }; // INITIALIZATION (создаёт listen-сокеты и подключения из конфига) diff --git a/src/etcp_loadbalancer.c b/src/etcp_loadbalancer.c index 7e0e16c5..a905a75f 100755 --- a/src/etcp_loadbalancer.c +++ b/src/etcp_loadbalancer.c @@ -1,99 +1,78 @@ -// etcp_loadbalancer.c - Load Balancer Implementation (based on etcp_protocol.txt) +// etcp_loadbalancer.c - Load Balancer Implementation (updated to match etcp_loadbalancer.h) +// Changes: +// - Replaced etcp_loadbalancer_update_after_send with etcp_loadbalancer_send, which now handles link selection, encryption/send, and shaper update. +// - Added loadbalancer_link_ready to notify ETCP_CONN when a link becomes ready (assumes ETCP_CONN has a resume_fn callback; implement in etcp.h/c if needed). +// - Updated constants to match .h (TIMEBASE_NS, SUB_MODULO, etc.). +// - Refined shaper logic for better precision and added normalization. +// - Removed duplicate update_after_send definitions; consolidated into send. +// - Added debug traces for new functions. +// - Assumed ETCP_CONN has a 'resume_fn' field: void (*resume_fn)(struct ETCP_CONN*); // Add to etcp.h struct ETCP_CONN. + #include "etcp_loadbalancer.h" #include "../lib/debug_config.h" #include "../lib/u_async.h" #include +#include // For UINT64_MAX +#include // For timespec +#include // For rounding // Enable comprehensive debug output for loadbalancer module #define DEBUG_CATEGORY_LOADBALANCER 1 +#define ALLOWED_DELTA 5 // 0.5ms allowed gap for time correction -// Forward declaration -static void init_timeout_cb(void* arg); - -// Constants -#define TIMEBASE_NS 100000 // 0.1ms = 100us = 100000ns -#define DELTA_TIME_NS 10000 // Example delta +// Forward declarations +static void shaper_timer_cb(void* arg); // Shaper wait callback -// Internal -static uint64_t get_current_nanotime() { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - uint64_t nanotime = (uint64_t)ts.tv_sec * 1000000000ULL + ts.tv_nsec; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_nanotime: tv_sec=%ld, tv_nsec=%ld, result=%llu", - ts.tv_sec, ts.tv_nsec, (unsigned long long)nanotime); - return nanotime; -} - -// Select link for transmission (per spec) +// Select link for transmission struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) { DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: etcp=%p", etcp); if (!etcp || !etcp->links) { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: invalid parameters (etcp=%p, links=%p)", - etcp, etcp ? etcp->links : NULL); + etcp, etcp ? etcp->links : NULL); return NULL; } struct ETCP_LINK* best = NULL; - uint64_t min_load_time = UINT64_MAX; - uint64_t now_ns = get_current_nanotime(); - uint64_t now_tb = now_ns / (TIMEBASE_NS / 10); // To 0.1ms units + uint64_t min_load_tb = UINT64_MAX; + uint64_t now_tb=get_current_time_units(); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: current time=%llu tb, scanning %d links", - (unsigned long long)now_tb, etcp->links ? 1 : 0); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: current tb=%llu, scanning links", (unsigned long long)now_tb); struct ETCP_LINK* link = etcp->links; int link_index = 0; while (link) { link_index++; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: evaluating link %d (%p) - initialized=%u, is_server=%u, bandwidth=%u", - link_index, link, link->initialized, link->is_server, link->bandwidth); + + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d (%p) - initialized=%u, state=%u, load_tb=%llu, sub=%llu", + link_index, link, link->initialized, link->shaper_timer==NULL?1:0, + (unsigned long long)link->shaper_load_time_tb, (unsigned long long)link->shaper_sub_nanotime); if (!link->initialized) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d not initialized, checking if client connection needed", - link_index); - // Initiate connection if client - if (!link->is_server && !link->init_timer) { - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: initiating client connection for link %d", link_index); - // Send INIT (via etcp_encrypt_send with special dgram) - // Set timer - link->init_timer = uasync_set_timeout(etcp->instance->ua, link->init_timeout, link, init_timeout_cb); // Define cb - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: set init timer for link %d", link_index); - } link = link->next; continue; } - // Update load time if inactive - if (link->last_activity < now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10))) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d inactive, updating last_activity from %llu to %llu", - link_index, (unsigned long long)link->last_activity, - (unsigned long long)(now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10)))); - link->last_activity = now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10)); - } - - // Check if can send (load time < now) - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d last_activity=%llu, now_tb=%llu", - link_index, (unsigned long long)link->last_activity, (unsigned long long)now_tb); + if (link->shaper_load_time_tb < now_tb-ALLOWED_DELTA) link->shaper_load_time_tb = now_tb-ALLOWED_DELTA; - if (link->last_activity < now_tb) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d can send (load_time < now)", link_index); - if (link->last_activity < min_load_time) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d is better candidate (activity=%llu < %llu)", - link_index, (unsigned long long)link->last_activity, (unsigned long long)min_load_time); - min_load_time = link->last_activity; + // Check if ready (load < now + burst allowance) + uint64_t effective_load_ns = link->shaper_load_time_tb * TIMEBASE_NS + (link->shaper_sub_nanotime / 10); // To ns + uint64_t now_ns = now_tb * TIMEBASE_NS; + int64_t delta_t=(int64_t)(now_ns - effective_load_ns);// uint64 может переполняться. чтобы обеспечить правильную цикличность. + if (delta_t > SHAPER_BURST_DELAY_TB) { + // Can send (within burst window) + if (link->shaper_load_time_tb < min_load_tb || + (link->shaper_load_time_tb == min_load_tb && link->shaper_sub_nanotime < best->shaper_sub_nanotime)) { + min_load_tb = link->shaper_load_time_tb; best = link; } - } else { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d cannot send yet (load_time >= now)", link_index); } - link = link->next; } if (best) { - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: selected link %p (activity=%llu)", - best, (unsigned long long)min_load_time); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: selected link %p (load_tb=%llu)", + best, (unsigned long long)min_load_tb); } else { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: no suitable link found"); } @@ -101,64 +80,102 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) { return best; } -// Init timeout callback (placeholder) -static void init_timeout_cb(void* arg) { - struct ETCP_LINK* link = (struct ETCP_LINK*)arg; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_timeout_cb: link=%p, retry_count=%u, timeout=%u", - link, link->init_retry_count, link->init_timeout); - - // Resend INIT, increment retry - link->init_retry_count++; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_timeout_cb: incremented retry_count to %u", link->init_retry_count); +// New: Send dgram (select link, encrypt/send, update shaper) +void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) { + if (!dgram) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: called with NULL dgram"); + return; + } - if (link->init_retry_count > 5) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "init_timeout_cb: max retries exceeded (%u > 5), closing link", link->init_retry_count); - // Fail - etcp_link_close(link); + struct ETCP_CONN* etcp = dgram->link ? dgram->link->etcp : NULL; + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: no ETCP_CONN associated with dgram"); return; } - // Resend... - link->init_timeout *= 2; // Backoff - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_timeout_cb: doubled timeout to %u ms", link->init_timeout); - link->init_timer = uasync_set_timeout(link->etcp->instance->ua, link->init_timeout, link, init_timeout_cb); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_timeout_cb: rescheduled timer"); -} - -// Bandwidth limit update (called after send) -void etcp_loadbalancer_update_after_send(struct ETCP_LINK* link, size_t pkt_size) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: link=%p, pkt_size=%zu", link, pkt_size); + // Select link if not already set + if (!dgram->link) { + dgram->link = etcp_loadbalancer_select_link(etcp); + if (!dgram->link) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: no link available, dropping dgram"); + free(dgram); // Assume free; adjust if pooled + return; + } + } + + struct ETCP_LINK* link = dgram->link; + size_t pkt_size = dgram->data_len + sizeof(uint16_t); // Include timestamp + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: sending dgram on link=%p, size=%zu", link, pkt_size); - if (!link) { - DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: called with NULL link"); + // Encrypt and send (from etcp_connections.c) + int send_result = etcp_encrypt_send(dgram); + if (send_result != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: encrypt/send failed (%d)", send_result); return; } + // Update shaper after successful send if (link->bandwidth == 0) { - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: link bandwidth is 0, skipping update"); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: unlimited bandwidth, skipping shaper update"); return; } - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: link bandwidth=%u bits/sec, packet size=%zu bytes", - link->bandwidth, pkt_size); - - // Time to transmit (ns) - double byte_time_ns = 1000000000.0 / (link->bandwidth * 8.0); // bits/sec to byte/ns - uint64_t tx_time_ns = (uint64_t)(pkt_size * byte_time_ns); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: byte_time_ns=%.2f, tx_time_ns=%llu", - byte_time_ns, (unsigned long long)tx_time_ns); - - // To timebase (0.1ms units) - uint64_t tx_time_tb = tx_time_ns / (TIMEBASE_NS / 10); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: tx_time_tb=%llu", (unsigned long long)tx_time_tb); - - // Update last_activity - uint64_t now_tb = get_current_nanotime() / (TIMEBASE_NS / 10); - uint64_t old_activity = link->last_activity; - link->last_activity += tx_time_tb; - if (link->last_activity < now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10))) { - link->last_activity = now_tb - (DELTA_TIME_NS / (TIMEBASE_NS / 10)); + // Time to transmit (ns per byte = 8e9 / bw for bits/sec) + double byte_time_ns = 8000000000.0 / (double)link->bandwidth; + uint64_t tx_time_ns = (uint64_t)(pkt_size * byte_time_ns + 0.5); // Round nearest + + // To sub_nanotime (*10 for 0.1ns) + uint64_t tx_sub = tx_time_ns * 10; + + // Add to load + link->shaper_sub_nanotime += tx_sub; + uint64_t carry=link->shaper_sub_nanotime / SUB_MODULO; + link->shaper_sub_nanotime -= carry * SUB_MODULO; + link->shaper_load_time_tb += carry; + + // Check if exceeds burst -> schedule timer + uint64_t now_tb=get_current_time_units(); + if (link->shaper_load_time_tb >= now_tb + SHAPER_BURST_DELAY_TB) { + uint64_t wait_tb = link->shaper_load_time_tb - now_tb; + link->shaper_timer = uasync_set_timeout(link->etcp->instance->ua, wait_tb, link, shaper_timer_cb); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: scheduled shaper timer (wait_tb=%llu)", (unsigned long long)wait_tb); + } + + // Inactivity correction + if (link->shaper_load_time_tb < now_tb-ALLOWED_DELTA) link->shaper_load_time_tb = now_tb-ALLOWED_DELTA; + + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: updated load_tb=%llu, sub=%llu, state=%u", + (unsigned long long)link->shaper_load_time_tb, (unsigned long long)link->shaper_sub_nanotime, link->shaper_timer==NULL?1:0); +} + +// New: Notify when link is ready (called from timer or external) +void loadbalancer_link_ready(struct ETCP_LINK* link) { + if (!link || !link->etcp) { + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: invalid link (%p)", link); + return; } - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_update_after_send: updated last_activity from %llu to %llu", - (unsigned long long)old_activity, (unsigned long long)link->last_activity); -} \ No newline at end of file + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: link=%p now ready, notifying ETCP_CONN", link); + + // Call ETCP_CONN resume (assumes link_ready_for_send_fn in ETCP_CONN; add to etcp.h: void (*link_ready_for_send_fn)(struct ETCP_CONN*);) + if (link->etcp->link_ready_for_send_fn) { + link->etcp->link_ready_for_send_fn(link->etcp); + } else { + // Fallback: request next packet (implement etcp_request_pkt in etcp.c if needed) + // etcp_request_pkt(link->etcp); + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: no link_ready_for_send_fn set, skipping notify"); + } +} + +// Shaper timer callback +static void shaper_timer_cb(void* arg) { + struct ETCP_LINK* link = (struct ETCP_LINK*)arg; + if (!link) return; + + link->shaper_timer = NULL; + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "shaper_timer_cb: link=%p now ready", link); + + // Notify loadbalancer that link is ready + loadbalancer_link_ready(link); +} diff --git a/src/etcp_loadbalancer.h b/src/etcp_loadbalancer.h index 7e46d309..f83252fb 100755 --- a/src/etcp_loadbalancer.h +++ b/src/etcp_loadbalancer.h @@ -8,10 +8,24 @@ extern "C" { #endif -// Functions +// Constants (moved here for visibility) +#define TIMEBASE_NS 100000ULL // 0.1ms base +#define SUB_MODULO 1000000ULL // Sub-nanotime modulo +#define DELTA_TIME_NS 10000ULL // Delta for inactivity +#define SHAPER_BURST_DELAY_TB 10ULL // 10 = 1ms burst allowance + +// при формировании пакета - выбрать линк для передачи +// должен либо возвращать линк который готов забрать пакет, либо возвратить NULL если все линки заняты. struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp); -// Add more as needed (e.g., update bandwidth) +// Когда линк снова ready (timer или link_ready) - loadbalancer должен вызвать ETCP_CONN->packet_request_fn(etcp) + +//void etcp_loadbalancer_update_after_send(struct ETCP_LINK* link, size_t pkt_size); - это надо заменить на: +void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram); + +// сообщаем в loadbalancer о готовности линка +void loadbalancer_link_ready(struct ETCP_LINK* link); + #ifdef __cplusplus } diff --git a/src/pkt_normalizer.c b/src/pkt_normalizer.c index cfc1cfc2..ec84081e 100644 --- a/src/pkt_normalizer.c +++ b/src/pkt_normalizer.c @@ -4,8 +4,8 @@ #include #include #include -static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* arg); -static void unpacker_handler(struct ll_queue* q, struct ll_entry* unused, void* arg); +static void packer_handler(struct ll_queue* q, void* data, void* arg); +static void unpacker_handler(struct ll_queue* q, void* data, void* arg); static void send_buf(struct pn_struct* pn); static int get_header(uint8_t* header, size_t L); /* Calculate fragment size from mtu */ @@ -13,12 +13,12 @@ struct pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer, int mtu) { struct pn_struct* pn = malloc(sizeof(struct pn_struct)); if (!pn) return NULL; pn->ua = ua; - pn->input = queue_new(ua, NULL); // No memory pool for now + pn->input = queue_new(ua, 0); // No memory pool for now, no hash table if (!pn->input) { free(pn); return NULL; } - pn->output = queue_new(ua, NULL); // No memory pool for now + pn->output = queue_new(ua, 0); // No memory pool for now, no hash table if (!pn->output) { queue_free(pn->input); free(pn); @@ -110,25 +110,29 @@ static void reset_fragment_state(struct pn_struct* pn) { static void send_buf(struct pn_struct* pn) { if (pn->u.packer.len == 0) return; size_t payload_len = pn->u.packer.len; - struct ll_entry* out = queue_entry_new(2 + payload_len); + uint8_t* out = queue_data_new(2 + payload_len); // Новый API if (!out) return; - uint8_t* d = ll_entry_data(out); - *(uint16_t*)d = (uint16_t)payload_len; - memcpy(d + 2, pn->u.packer.buf, payload_len); - queue_entry_put(pn->output, out); + *(uint16_t*)out = (uint16_t)payload_len; + memcpy(out + 2, pn->u.packer.buf, payload_len); + queue_data_put(pn->output, out, 0); // Новый API, ID=0 для внутренних пакетов pn->u.packer.len = 0; } -static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* arg) { - (void)unused; +static void packer_handler(struct ll_queue* q, void* data, void* arg) { struct pn_struct* pn = arg; size_t max = (size_t)1400; - struct ll_entry* entry = queue_entry_get(q); - if (!entry) { + + // Получить данные из очереди + void* pkt_data = queue_data_get(q); + if (!pkt_data) { queue_resume_callback(q); return; } - size_t L = ll_entry_size(entry); - uint8_t* data = ll_entry_data(entry); + + // Для определения размера нужно знать структуру, но в новом API размер в самих данных + // В pkt_normalizer все данные имеют 2-байтовый заголовок с размером + uint16_t* size_ptr = (uint16_t*)pkt_data; + size_t L = *size_ptr; + uint8_t* pkt_payload = (uint8_t*)pkt_data + 2; uint8_t header[2]; int hsize = get_header(header, L); size_t needed = (size_t)hsize + L; @@ -143,7 +147,7 @@ static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* ar while (remaining > 0) { size_t chunk; size_t payload_len; - struct ll_entry* fout; + uint8_t* fout; uint8_t* fd; uint8_t frag_header[2]; int frag_hsize; @@ -151,11 +155,11 @@ static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* ar // Первый фрагмент: FF + общая длина (2 байта) chunk = remaining > (max - 5) ? (max - 5) : remaining; // 2+1+2+chunk <= max payload_len = 1 + 2 + chunk; // FF + total_len + data - fout = queue_entry_new(2 + payload_len); + fout = queue_data_new(2 + payload_len); if (!fout) { break; } - fd = ll_entry_data(fout); + fd = fout; *(uint16_t*)fd = (uint16_t)payload_len; fd += 2; *fd++ = 0xFF; @@ -171,11 +175,11 @@ static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* ar // Успешно: обычный блок payload_len = frag_hsize + remaining; chunk = remaining; - fout = queue_entry_new(2 + payload_len); + fout = queue_data_new(2 + payload_len); if (!fout) { break; } - fd = ll_entry_data(fout); + fd = fout; *(uint16_t*)fd = (uint16_t)payload_len; fd += 2; memcpy(fd, frag_header, frag_hsize); @@ -208,9 +212,9 @@ static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* ar // Отправляем как FE (нарушение спецификации, но это крайний случай) chunk = remaining > (max - 3) ? (max - 3) : remaining; payload_len = 1 + chunk; // FE + data - fout = queue_entry_new(2 + payload_len); + fout = queue_data_new(2 + payload_len); if (!fout) break; - fd = ll_entry_data(fout); + fd = (fout); *(uint16_t*)fd = (uint16_t)payload_len; fd += 2; *fd++ = 0xFE; @@ -218,14 +222,14 @@ static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* ar // Отправляем FE фрагмент chunk = fe_data_size; payload_len = 1 + chunk; // FE + data - fout = queue_entry_new(2 + payload_len); + fout = queue_data_new(2 + payload_len); if (!fout) break; - fd = ll_entry_data(fout); + fd = (fout); *(uint16_t*)fd = (uint16_t)payload_len; fd += 2; *fd++ = 0xFE; memcpy(fd, data + pos, chunk); - queue_entry_put(pn->output, fout); + queue_data_put(pn->output, fout, 0); pos += chunk; remaining -= chunk; fragment_count++; @@ -238,18 +242,18 @@ static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* ar // Промежуточный фрагмент, отправляем как FE chunk = remaining > (max - 3) ? (max - 3) : remaining; payload_len = 1 + chunk; // FE + data - fout = queue_entry_new(2 + payload_len); + fout = queue_data_new(2 + payload_len); if (!fout) { break; } - fd = ll_entry_data(fout); + fd = fout; *(uint16_t*)fd = (uint16_t)payload_len; fd += 2; *fd++ = 0xFE; } } memcpy(fd, data + pos, chunk); - queue_entry_put(pn->output, fout); + queue_data_put(pn->output, fout, 0); pos += chunk; remaining -= chunk; fragment_count++; @@ -264,7 +268,7 @@ static void packer_handler(struct ll_queue* q, struct ll_entry* unused, void* ar memcpy(p + hsize, data, L); pn->u.packer.len += needed; } - queue_entry_free(entry); + queue_data_free(pkt_data); if (pn->u.packer.len > 0) { send_buf(pn); } @@ -338,9 +342,9 @@ int pkt_normalizer_send_service(struct pn_struct* pn, uint8_t type, const void* } if (chunk == 0) break; size_t payload_len = 1 + chunk + (pos == 0 ? 1 : 0); // +1 байт типа для первого пакета - struct ll_entry* entry = queue_entry_new(2 + payload_len); + struct ll_entry* entry = queue_data_new(2 + payload_len); if (!entry) return -1; - uint8_t* d = ll_entry_data(entry); + uint8_t* d = (entry); *(uint16_t*)d = (uint16_t)payload_len; d += 2; *d++ = service_header; @@ -348,7 +352,7 @@ int pkt_normalizer_send_service(struct pn_struct* pn, uint8_t type, const void* *d++ = type; } memcpy(d, (const uint8_t*)data + pos, chunk); - queue_entry_put(pn->output, entry); + queue_data_put(pn->output, entry, 0); pos += chunk; total_service_len -= chunk + (pos == chunk ? 2 : 1); // корректно вычитаем заголовки } @@ -375,16 +379,15 @@ void pkt_normalizer_flush(struct pn_struct* pn) { send_buf(pn); } } -static void unpacker_handler(struct ll_queue* q, struct ll_entry* unused, void* arg) { - (void)unused; +static void unpacker_handler(struct ll_queue* q, void* data, void* arg) { + (void)data; struct pn_struct* pn = arg; while (queue_entry_count(q) > 0) { - struct ll_entry* entry = queue_entry_get(q); - uint8_t* data = ll_entry_data(entry); - size_t total = ll_entry_size(entry); + uint8_t* entry = queue_data_get(q); + uint8_t* data = entry; uint16_t payload_len = *(uint16_t*)data; - if (total != 2 + (size_t)payload_len) { - queue_entry_free(entry); + if (entry != NULL && *(uint16_t*)entry != 2 + (size_t)payload_len) { + queue_data_free(entry); continue; } uint8_t* cg = data + 2; @@ -424,10 +427,10 @@ static void unpacker_handler(struct ll_queue* q, struct ll_entry* unused, void* /* Проверить, не собрали ли уже весь пакет */ if (pn->u.unpacker.len >= pn->u.unpacker.total_len) { if (pn->u.unpacker.len == pn->u.unpacker.total_len) { - struct ll_entry* out = queue_entry_new(pn->u.unpacker.total_len); + uint8_t* out = queue_data_new(pn->u.unpacker.total_len); if (out) { - memcpy(ll_entry_data(out), pn->u.unpacker.buf, pn->u.unpacker.total_len); - queue_entry_put(pn->output, out); + memcpy(out, pn->u.unpacker.buf, pn->u.unpacker.total_len); + queue_data_put(pn->output, out, 0); } } else { /* Слишком много данных - ошибка */ @@ -458,10 +461,10 @@ static void unpacker_handler(struct ll_queue* q, struct ll_entry* unused, void* /* Проверить, не собрали ли уже весь пакет */ if (pn->u.unpacker.len >= pn->u.unpacker.total_len) { if (pn->u.unpacker.len == pn->u.unpacker.total_len) { - struct ll_entry* out = queue_entry_new(pn->u.unpacker.total_len); + uint8_t* out = queue_data_new(pn->u.unpacker.total_len); if (out) { - memcpy(ll_entry_data(out), pn->u.unpacker.buf, pn->u.unpacker.total_len); - queue_entry_put(pn->output, out); + memcpy(out, pn->u.unpacker.buf, pn->u.unpacker.total_len); + queue_data_put(pn->output, out, 0); } } else { /* Слишком много данных - ошибка */ @@ -576,10 +579,10 @@ static void unpacker_handler(struct ll_queue* q, struct ll_entry* unused, void* /* Проверить, собрали ли весь пакет */ if (pn->u.unpacker.len >= pn->u.unpacker.total_len) { if (pn->u.unpacker.len == pn->u.unpacker.total_len) { - struct ll_entry* out = queue_entry_new(pn->u.unpacker.total_len); + uint8_t* out = queue_data_new(pn->u.unpacker.total_len); if (out) { - memcpy(ll_entry_data(out), pn->u.unpacker.buf, pn->u.unpacker.total_len); - queue_entry_put(pn->output, out); + memcpy(out, pn->u.unpacker.buf, pn->u.unpacker.total_len); + queue_data_put(pn->output, out, 0); } } else { /* Слишком много данных - ошибка */ @@ -589,16 +592,16 @@ static void unpacker_handler(struct ll_queue* q, struct ll_entry* unused, void* } } else { /* Обычная запись (не часть фрагмента) */ - struct ll_entry* out = queue_entry_new(L); + uint8_t* out = queue_data_new(L); if (out) { - memcpy(ll_entry_data(out), cg + cg_pos, L); - queue_entry_put(pn->output, out); + memcpy(out, cg + cg_pos, L); + queue_data_put(pn->output, out, 0); } cg_pos += L; } } err: - queue_entry_free(entry); + queue_data_free(entry); } queue_resume_callback(q); } diff --git a/src/utun.c b/src/utun.c index 13aa09c7..d632c778 100644 --- a/src/utun.c +++ b/src/utun.c @@ -269,6 +269,22 @@ int main(int argc, char *argv[]) { return 1; } + // Apply debug configuration from config file (CLI arguments have priority) + int cli_override_level = (args.debug_config != NULL); + int cli_override_categories = (args.debug_config != NULL); + + debug_apply_config_values( + instance->config->global.log_file, + instance->config->global.debug_level, + instance->config->global.debug_categories, + instance->config->global.enable_timestamp, + instance->config->global.enable_function_names, + instance->config->global.enable_file_lines, + instance->config->global.enable_colors, + cli_override_level, + cli_override_categories + ); + // Print config for debugging if (args.foreground) { print_config(instance->config); diff --git a/src/utun_instance.c b/src/utun_instance.c index 88a3e170..0269c9f5 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -79,7 +79,7 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config } - instance->pkt_pool=memory_pool_init(PACKET_DATA_SIZE+100); + instance->pkt_pool=memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE); // Initialize TUN device only if enabled if (g_tun_init_enabled) { @@ -194,11 +194,14 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { instance->etcp_sockets = NULL; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] ETCP sockets cleanup complete"); + // Safe cleanup of ETCP connections with NULL pointer checks struct ETCP_CONN* conn = instance->connections; while (conn) { struct ETCP_CONN* next = conn->next; DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Closing connection %p", conn); - etcp_connection_close(conn); // Закрыть соединение + if (conn) { // Дополнительная проверка на случай поврежденного списка + etcp_connection_close(conn); // Закрыть соединение (с проверкой NULL внутри) + } conn = next; } instance->connections = NULL; @@ -241,7 +244,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { // FINALLY destroy uasync (after all resources are cleaned up) if (instance->ua) { DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Destroying uasync instance"); - uasync_destroy(instance->ua); + uasync_destroy(instance->ua, 0); instance->ua = NULL; } @@ -285,10 +288,24 @@ int utun_instance_init(struct UTUN_INSTANCE *instance) { } // Initialize connections - if (init_connections(instance) < 0) { + // Добавляем прямой вывод для отладки проблемы double free + fprintf(stderr, "DEBUG: utun_instance_init() calling init_connections() for instance %p\n", instance); + fflush(stderr); + + int conn_result = init_connections(instance); + + fprintf(stderr, "DEBUG: init_connections() returned: %d\n", conn_result); + fflush(stderr); + + if (conn_result < 0) { + fprintf(stderr, "DEBUG: Failed to initialize connections, returning error\n"); + fflush(stderr); return -1; } + fprintf(stderr, "DEBUG: Connections initialized successfully, count=%d\n", instance->connections_count); + fflush(stderr); + return 0; } diff --git a/src/utun_instance.h b/src/utun_instance.h index 04ef703e..d3af8245 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -36,13 +36,14 @@ struct UTUN_INSTANCE { int running; FILE *log_fp; - struct memory_pool* pkt_pool; // Routing struct routing_table *routing_table; - // Connections + // Connections (список всех подключений для instance) struct ETCP_CONN* connections;// linked-list int connections_count; // Number of connections + + struct memory_pool* pkt_pool; // Active sockets struct ETCP_SOCKET* etcp_sockets;// linked-list diff --git a/tests/Makefile.am b/tests/Makefile.am index 429d01de..430e52ef 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,57 +1,96 @@ -# Tests Makefile.am for utun - cleaned and working version +# Tests Makefile.am for utun - all tests with new ll_queue library -# Essential working tests +# All available tests with new ll_queue library check_PROGRAMS = test_etcp_crypto$(EXEEXT) \ test_crypto$(EXEEXT) \ test_etcp_two_instances$(EXEEXT) \ - test_etcp_traffic_flow$(EXEEXT) \ test_etcp_simple_traffic$(EXEEXT) \ test_etcp_minimal$(EXEEXT) \ - test_ll_queue_pos$(EXEEXT) \ - test_ll_queue_unified$(EXEEXT) + test_ll_queue$(EXEEXT) \ + test_ecc_encrypt$(EXEEXT) \ + test_intensive_memory_pool$(EXEEXT) \ + test_memory_pool_and_config$(EXEEXT) \ + test_packet_dump$(EXEEXT) \ + test_u_async_comprehensive$(EXEEXT) \ + test_u_async_performance$(EXEEXT) \ + test_debug_new$(EXEEXT) \ + test_debug_categories$(EXEEXT) \ + test_config_debug$(EXEEXT) # Basic includes AM_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source -# ETCP crypto test - main functionality +# ETCP tests test_etcp_crypto_SOURCES = test_etcp_crypto.c test_etcp_crypto_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_etcp_crypto_LDADD = $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-crc32.o $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ecc.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dh.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dsa.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_platform_specific.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto -# Basic crypto test -test_crypto_SOURCES = test_crypto.c -test_crypto_CFLAGS = -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source -test_crypto_LDADD = $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o -lpthread -lcrypto - -# ETCP two instances test - comprehensive connection test test_etcp_two_instances_SOURCES = test_etcp_two_instances.c test_etcp_two_instances_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_etcp_two_instances_LDADD = $(top_builddir)/src/utun-config_parser.o $(top_builddir)/src/utun-config_updater.o $(top_builddir)/src/utun-crc32.o $(top_builddir)/src/utun-etcp.o $(top_builddir)/src/utun-etcp_connections.o $(top_builddir)/src/utun-etcp_loadbalancer.o $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-tun_if.o $(top_builddir)/src/utun-utun_instance.o $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ecc.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dh.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dsa.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_platform_specific.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto -# ETCP simple traffic test - TUN-less traffic analysis test_etcp_simple_traffic_SOURCES = test_etcp_simple_traffic.c test_etcp_simple_traffic_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_etcp_simple_traffic_LDADD = $(top_builddir)/src/utun-config_parser.o $(top_builddir)/src/utun-config_updater.o $(top_builddir)/src/utun-crc32.o $(top_builddir)/src/utun-etcp.o $(top_builddir)/src/utun-etcp_connections.o $(top_builddir)/src/utun-etcp_loadbalancer.o $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-tun_if.o $(top_builddir)/src/utun-utun_instance.o $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ecc.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dh.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dsa.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_platform_specific.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto -# ETCP minimal test - basic packet analysis test_etcp_minimal_SOURCES = test_etcp_minimal.c test_etcp_minimal_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_etcp_minimal_LDADD = $(top_builddir)/src/utun-etcp.o $(top_builddir)/src/utun-etcp_connections.o $(top_builddir)/src/utun-etcp_loadbalancer.o $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-crc32.o $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ecc.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dh.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dsa.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_platform_specific.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto -# ETCP traffic flow test - TUN-less traffic analysis -test_etcp_traffic_flow_SOURCES = test_etcp_traffic_flow.c -test_etcp_traffic_flow_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source -test_etcp_traffic_flow_LDADD = $(top_builddir)/src/utun-config_parser.o $(top_builddir)/src/utun-config_updater.o $(top_builddir)/src/utun-crc32.o $(top_builddir)/src/utun-etcp.o $(top_builddir)/src/utun-etcp_connections.o $(top_builddir)/src/utun-etcp_loadbalancer.o $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-tun_if.o $(top_builddir)/src/utun-utun_instance.o $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ecc.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dh.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dsa.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_platform_specific.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto +# Basic crypto test +test_crypto_SOURCES = test_crypto.c +test_crypto_CFLAGS = -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source +test_crypto_LDADD = $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o -lpthread -lcrypto + +# New comprehensive ll_queue test with current API +test_ll_queue_SOURCES = test_ll_queue.c +test_ll_queue_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_ll_queue_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto + +# ECC encryption test +test_ecc_encrypt_SOURCES = test_ecc_encrypt.c +test_ecc_encrypt_CFLAGS = -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source +test_ecc_encrypt_LDADD = $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-crc32.o $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ecc.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dh.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_dsa.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-ecc_platform_specific.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto + +# Intensive memory pool test +test_intensive_memory_pool_SOURCES = test_intensive_memory_pool.c +test_intensive_memory_pool_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_intensive_memory_pool_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto + +# Memory pool and config test +test_memory_pool_and_config_SOURCES = test_memory_pool_and_config.c +test_memory_pool_and_config_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_memory_pool_and_config_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto + +# Packet dump test +test_packet_dump_SOURCES = test_packet_dump.c +test_packet_dump_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_packet_dump_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto + +# UASYNC comprehensive test +test_u_async_comprehensive_SOURCES = test_u_async_comprehensive.c +test_u_async_comprehensive_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_u_async_comprehensive_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto + +# UASYNC performance test +test_u_async_performance_SOURCES = test_u_async_performance.c +test_u_async_performance_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_u_async_performance_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto + +# Debug system test +test_debug_new_SOURCES = test_debug_new.c +test_debug_new_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_debug_new_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto -# LL queue position test -test_ll_queue_pos_SOURCES = test_ll_queue_pos.c -test_ll_queue_pos_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -test_ll_queue_pos_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto +# Debug categories test +test_debug_categories_SOURCES = test_debug_categories.c +test_debug_categories_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_debug_categories_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto -# Unified comprehensive ll_queue test -test_ll_queue_unified_SOURCES = test_ll_queue_unified.c -test_ll_queue_unified_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -test_ll_queue_unified_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto +# Config debug test +test_config_debug_SOURCES = test_config_debug.c +test_config_debug_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_config_debug_LDADD = $(top_builddir)/src/utun-config_parser.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto # Register tests TESTS = $(check_PROGRAMS) \ No newline at end of file diff --git a/tests/buffer_overflow_fix.patch b/tests/buffer_overflow_fix.patch new file mode 100644 index 00000000..c1aa1f7b --- /dev/null +++ b/tests/buffer_overflow_fix.patch @@ -0,0 +1,38 @@ +--- test_etcp_simple_traffic.c.backup ++++ test_etcp_simple_traffic.c +@@ -172,19 +172,25 @@ + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "SIMULATE RX: inst=%s, len=%u", + inst->is_server ? "server" : "client", len); + +- // Create a mock ETCP_DGRAM for processing +- struct ETCP_DGRAM mock_dgram; +- memset(&mock_dgram, 0, sizeof(mock_dgram)); +- mock_dgram.link = NULL; // Will be set by connection processing +- mock_dgram.data_len = len > 1500 ? 1500 : len; +- mock_dgram.timestamp = get_current_timestamp(); +- if (mock_dgram.data_len > 0) { +- memcpy(mock_dgram.data, data, mock_dgram.data_len); ++ // Create a mock ETCP_DGRAM for processing with proper allocation ++ size_t dgram_size = sizeof(struct ETCP_DGRAM) + (len > 1500 ? 1500 : len); ++ struct ETCP_DGRAM* mock_dgram = malloc(dgram_size); ++ if (!mock_dgram) { ++ DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to allocate mock dgram"); ++ return; ++ } ++ memset(mock_dgram, 0, dgram_size); ++ mock_dgram->link = NULL; // Will be set by connection processing ++ mock_dgram->data_len = len > 1500 ? 1500 : len; ++ mock_dgram->timestamp = get_current_timestamp(); ++ if (mock_dgram->data_len > 0) { ++ memcpy(mock_dgram->data, data, mock_dgram->data_len); + } + + // Process through ETCP input +- etcp_conn_input(&mock_dgram); ++ etcp_conn_input(mock_dgram); ++ ++ // Free the allocated memory ++ free(mock_dgram); + } + + // Simulate packet transmission for analysis diff --git a/tests/debug_full_test b/tests/debug_full_test new file mode 100755 index 0000000000000000000000000000000000000000..ea226291627649b0dd3a7f5eaf362817e442b303 GIT binary patch literal 45456 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_async.h" +#include "timeout_heap.h" + +/* Test statistics */ +static struct { + int tests_run; + int tests_passed; + int tests_failed; + + /* Timer statistics */ + int timer_callbacks; + int timer_cancellations; + int immediate_timeouts; + + /* Socket statistics */ + int socket_events; + int socket_errors; + + /* Error statistics */ + int memory_allocation_errors; + int invalid_parameter_errors; + int race_condition_errors; +} test_stats = {0}; + +/* Test result tracking */ +#define TEST_START(name) do { \ + printf("TEST: %s... \n", name); \ + printf(" immediate_timeouts before: %d\n", test_stats.immediate_timeouts); \ + test_stats.tests_run++; \ +} while(0) + +#define TEST_PASS() do { \ + printf(" immediate_timeouts after: %d\n", test_stats.immediate_timeouts); \ + printf("PASS\n"); \ + test_stats.tests_passed++; \ +} while(0) + +#define TEST_FAIL(msg) do { \ + printf(" immediate_timeouts after: %d\n", test_stats.immediate_timeouts); \ + printf("FAIL: %s\n", msg); \ + test_stats.tests_failed++; \ +} while(0) + +#define ASSERT_EQ(a, b, msg) do { \ + if ((a) != (b)) { \ + TEST_FAIL(msg); \ + printf(" Expected: %ld, Got: %ld\n", (long)(b), (long)(a)); \ + return; \ + } \ +} while(0) + +#define ASSERT_NOT_NULL(ptr, msg) do { \ + if ((ptr) == NULL) { \ + TEST_FAIL(msg); \ + return; \ + } \ +} while(0) + +/* Test context for callbacks */ +typedef struct { + int callback_count; + int expected_count; + int callback_arg; + int timeout_ms; + uasync_t* ua; + void* timer_id; +} test_context_t; + +/* Timer callback for testing */ +static void test_timer_callback(void* arg) { + test_context_t* ctx = (test_context_t*)arg; + ctx->callback_count++; + test_stats.timer_callbacks++; + + if (ctx->timeout_ms == 0) { + test_stats.immediate_timeouts++; + printf(" IMMEDIATE TIMEOUT: immediate_timeouts now = %d\n", test_stats.immediate_timeouts); + } +} + +/* Test 1: Basic timer functionality */ +static void test_basic_timers(void) { + TEST_START("Basic timer functionality"); + + uasync_t* ua = uasync_create(); + ASSERT_NOT_NULL(ua, "Failed to create uasync instance"); + + test_context_t ctx = {0}; + ctx.expected_count = 3; + ctx.timeout_ms = 10; // Non-zero to avoid immediate timeout counting + + /* Set multiple timers with different timeouts */ + void* timer1 = uasync_set_timeout(ua, 10, &ctx, test_timer_callback); /* 1ms */ + void* timer2 = uasync_set_timeout(ua, 20, &ctx, test_timer_callback); /* 2ms */ + void* timer3 = uasync_set_timeout(ua, 30, &ctx, test_timer_callback); /* 3ms */ + + ASSERT_NOT_NULL(timer1, "Failed to set timer 1"); + ASSERT_NOT_NULL(timer2, "Failed to set timer 2"); + ASSERT_NOT_NULL(timer3, "Failed to set timer 3"); + + /* Poll and verify timers fire in order */ + int poll_count = 0; + while (ctx.callback_count < ctx.expected_count && poll_count < 100) { + uasync_poll(ua, 10); /* 1ms poll */ + poll_count++; + } + + ASSERT_EQ(ctx.callback_count, ctx.expected_count, "Not all timers fired"); + + /* Cleanup */ + uasync_destroy(ua, 0); + TEST_PASS(); +} + +/* Test 2: Timer cancellation race conditions */ +static void test_timer_cancellation_races(void) { + TEST_START("Timer cancellation race conditions"); + + uasync_t* ua = uasync_create(); + ASSERT_NOT_NULL(ua, "Failed to create uasync instance"); + + test_context_t ctx = {0}; + ctx.expected_count = 2; + ctx.timeout_ms = 5; // Non-zero to avoid immediate timeout counting + + /* Create timers that will be cancelled at different stages */ + void* timer1 = uasync_set_timeout(ua, 5, &ctx, test_timer_callback); /* 0.5ms */ + void* timer2 = uasync_set_timeout(ua, 50, &ctx, test_timer_callback); /* 5ms */ + void* timer3 = uasync_set_timeout(ua, 100, &ctx, test_timer_callback); /* 10ms */ + + ASSERT_NOT_NULL(timer1, "Failed to set timer 1"); + ASSERT_NOT_NULL(timer2, "Failed to set timer 2"); + ASSERT_NOT_NULL(timer3, "Failed to set timer 3"); + + /* Cancel timer1 immediately (before it fires) */ + err_t cancel_result = uasync_cancel_timeout(ua, timer1); + ASSERT_EQ(cancel_result, ERR_OK, "Failed to cancel timer 1"); + test_stats.timer_cancellations++; + + /* Poll briefly - timer1 should not fire, others should */ + uasync_poll(ua, 10); /* 1ms */ + + /* Cancel timer2 while it might be firing */ + cancel_result = uasync_cancel_timeout(ua, timer2); + /* Result could be ERR_OK or ERR_FAIL depending on timing */ + + /* Continue polling */ + int poll_count = 0; + while (ctx.callback_count < 2 && poll_count < 50) { + uasync_poll(ua, 10); + poll_count++; + } + + /* Verify we got expected callbacks (timer3 + possibly timer2) */ + if (ctx.callback_count < 1 || ctx.callback_count > 2) { + TEST_FAIL("Wrong number of timers fired"); + return; + } + + /* Cleanup remaining timer */ + if (timer3) { + uasync_cancel_timeout(ua, timer3); + timer3 = NULL; + } + + uasync_destroy(ua, 0); + TEST_PASS(); +} + +/* Test 3: Immediate timeout handling */ +static void test_immediate_timeouts(void) { + TEST_START("Immediate timeout handling"); + + uasync_t* ua = uasync_create(); + ASSERT_NOT_NULL(ua, "Failed to create uasync instance"); + + test_context_t ctx = {0}; + ctx.expected_count = 5; + ctx.timeout_ms = 0; // This should trigger immediate timeout counting + + /* Set multiple immediate timeouts (0ms) */ + for (int i = 0; i < 5; i++) { + void* timer = uasync_set_timeout(ua, 0, &ctx, test_timer_callback); + ASSERT_NOT_NULL(timer, "Failed to set immediate timer"); + } + + /* Immediate timeouts should fire during next poll */ + ASSERT_EQ(ctx.callback_count, 0, "Callbacks fired too early"); + + printf(" About to call uasync_poll, immediate_timeouts = %d\n", test_stats.immediate_timeouts); + uasync_poll(ua, 1); /* Minimal poll */ + printf(" After uasync_poll, immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + ASSERT_EQ(ctx.callback_count, ctx.expected_count, "Immediate timeouts didn't fire correctly"); + ASSERT_EQ(test_stats.immediate_timeouts, 5, "Immediate timeout counter incorrect"); + + uasync_destroy(ua, 0); + TEST_PASS(); +} + +int main(void) { + printf("=== lib Comprehensive Unit Tests ===\n"); + printf("Testing race conditions, memory management, and error handling\n\n"); + printf("Initial immediate_timeouts: %d\n", test_stats.immediate_timeouts); + + /* Run all tests */ + test_basic_timers(); + printf("After basic_timers: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + test_timer_cancellation_races(); + printf("After timer_cancellation_races: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + test_immediate_timeouts(); + printf("After immediate_timeouts: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + /* Print final statistics */ + printf("\n=== Final Statistics ===\n"); + printf("Timer callbacks: %d\n", test_stats.timer_callbacks); + printf("Immediate timeouts: %d\n", test_stats.immediate_timeouts); + + return (test_stats.tests_failed > 0) ? 1 : 0; +} diff --git a/tests/debug_test b/tests/debug_test index 788f6c796a59c54435fa1301661a7f2784d430c5..d12e6750c079cc1977d1f21dc183625ed7a19663 100755 GIT binary patch literal 75904 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 literal 31024 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 u(qF}290ystBWXR`KQEGBBLdr_ + +/* Test statistics */ +static struct { + int timer_callbacks; + int immediate_timeouts; +} test_stats = {0}; + +typedef struct { + int callback_count; + int timeout_ms; +} test_context_t; + +/* Timer callback for testing */ +static void test_timer_callback(void* arg) { + test_context_t* ctx = (test_context_t*)arg; + ctx->callback_count++; + test_stats.timer_callbacks++; + + printf("Callback fired: ctx->timeout_ms=%d, immediate_timeouts before=%d", ctx->timeout_ms, test_stats.immediate_timeouts); + + if (ctx->timeout_ms == 0) { + test_stats.immediate_timeouts++; + printf(" -> INCREMENTED to %d\n", test_stats.immediate_timeouts); + } else { + printf(" -> NOT incremented\n"); + } +} + +int main() { + printf("=== Test 1: Basic timers (non-zero timeouts) ===\n"); + printf("Before test 1: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + uasync_t* ua1 = uasync_create(); + test_context_t ctx1 = {.timeout_ms = 10}; + + for (int i = 0; i < 3; i++) { + uasync_set_timeout(ua1, 10 + i*10, &ctx1, test_timer_callback); + } + + int polls = 0; + while (ctx1.callback_count < 3 && polls < 10) { + uasync_poll(ua1, 10); + polls++; + } + + printf("After test 1: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + uasync_destroy(ua1); + + printf("\n=== Test 2: Immediate timeouts (zero timeouts) ===\n"); + printf("Before test 2: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + uasync_t* ua2 = uasync_create(); + test_context_t ctx2 = {.timeout_ms = 0}; + + for (int i = 0; i < 5; i++) { + uasync_set_timeout(ua2, 0, &ctx2, test_timer_callback); + } + + printf("Before poll: ctx2.callback_count = %d, immediate_timeouts = %d\n", ctx2.callback_count, test_stats.immediate_timeouts); + + uasync_poll(ua2, 1); + + printf("After poll: ctx2.callback_count = %d, immediate_timeouts = %d\n", ctx2.callback_count, test_stats.immediate_timeouts); + + uasync_destroy(ua2); + + printf("\n=== Test 3: Another immediate timeout test ===\n"); + printf("Before test 3: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + uasync_t* ua3 = uasync_create(); + test_context_t ctx3 = {.timeout_ms = 0}; + + for (int i = 0; i < 2; i++) { + uasync_set_timeout(ua3, 0, &ctx3, test_timer_callback); + } + + uasync_poll(ua3, 1); + + printf("After test 3: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + uasync_destroy(ua3); + + return 0; +} diff --git a/tests/key_fix.patch b/tests/key_fix.patch new file mode 100644 index 00000000..cb0892d1 --- /dev/null +++ b/tests/key_fix.patch @@ -0,0 +1,24 @@ +--- test_etcp_simple_traffic.c.backup ++++ test_etcp_simple_traffic.c +@@ -243,12 +243,19 @@ + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Parsing crypto keys: priv_len=%zu, pub_len=%zu", + strlen(priv_key_hex), strlen(pub_key_hex)); + ++ // Validate key string lengths to prevent buffer overflow ++ if (strlen(priv_key_hex) != 64 || strlen(pub_key_hex) != 128) { ++ DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Invalid key string lengths: priv=%zu, pub=%zu", ++ strlen(priv_key_hex), strlen(pub_key_hex)); ++ return NULL; ++ } ++ + // Parse keys + for (int i = 0; i < 32; i++) { + if (sscanf(&priv_key_hex[i*2], "%2hhx", &inst->instance->my_keys.private_key[i]) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse private key byte %d", i); + } + } + for (int i = 0; i < 64; i++) { + if (sscanf(&pub_key_hex[i*2], "%2hhx", &inst->instance->my_keys.public_key[i]) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to parse public key byte %d", i); + } + } diff --git a/tests/test_config_debug b/tests/test_config_debug new file mode 100755 index 0000000000000000000000000000000000000000..41dc6f364477cbffebf1343f666f4682746929b8 GIT binary patch literal 95808 zcmeFa33!y%*#>;xcb3ddl9>b&2>Sp5g90WjvO@w1Of+EF6a@?r5=0?M!$iO}7=lb= zDp<9+Q>`s(Rm3f#Vh{>gZ4Fc{bw!IhBQ8`eqEz$W_c`aCnMA1l`hVYb{r`3O!aL_Y z>v_&|_GKp56ckN14MSLuO^g?W7IaaVTurE57URhkBSo^v6{m^*!U?(^uf^9sJZOT5 z#|m8v6gGsE%V6XrVRwZVR%oTpkaDMb7Wfp!3Pp;7Dd%8R!|lVjM<_lkwCK_FiWOb7 z3&Wih-U{R8=$lQ;6n)c`ZNpVM-^EsDv|V%;<+fWcV%U!JH+K2f{Ojse);4|}s;Nt%#y<#m_RigB?54-F9S)rBQbjZ;!|LLRMDQ4*O zPE`)=^Q)5=XD(a1=&WI9E?Yck+0u%dRfATIJZsQd!v?RY96UstK)d{SS*NBqy_e0IeMy(1=_AQQyD38+KGdh1P!v8L zG4e=%7;6Ben8Xc~971IExW4=TvOg~$^+ki#d^9B%*rA|942;Gmo&tXsI8MfY;1u{C zr_gic6#V6<;HN)MmhPXx?+2~@Gwu|6hMxj|3lyA8f5s{Br=3Di!YTMyoB}`g6ncO@ zsXb#(fggMdJte2WM}YT3SNmr%5GV6P=oI+rr_jIh6!<@#LeCYaz;8PRe!wa4FP;Lw z;S~65Pl2Cv3j5za1^;pI`$22}Ob6m*{+xdb{DY^!-+2mr{weS;0G}$l3V$skc&vWj z3p@KLRGZK50FghpR-r9^QNE-sxI(O`sH$FC5iA!gg4GqJRjWlw$%^2@(o0K9FTS*- zeBsh%KqwAkM0s^t8CipsHOrPsXyM|;P*7eepe`B*$KskQv9!FTV&U?#VCB+^#bv7~ zS6zB>wJ0xNR|aOrYsR_VeOWu?ojz*-(>bV&*N`cmerI#{xN;Zi2ExU9NbEUhRDmQ(~QDNwp< zVM+PYiiOLTUS1}U>cVAFr7#&FEh#TuR=J`~lvmZjb?}sSo-}89*>d`?d}a00pfW}p zf^;e>nT>_PrIi&b$*Le?w28ybSz=02;lxQLLk16x#&U*5qp{e~xai z6~(FXwx}F6SW*d5Db_p#quCnNQES}pxO8c<5DB6|<8In`3x+$VxLLz#lI{?jHT<}w z+rFD*R?W-?H#%8uD?~>QDv3f)8G!5FCr; zc&zD%;_yf~`h?@~dg>@?N8|9BQEnlQ$KlV2!&~i}^AC>w)?>9};;s1rX# z3nZc`4j(_yXpX~Ma~cZli^JQc2KCVrhnMqgO*HHhIjyvJfjcr*^5 z7>7R|hfj*b3+*?iiHa0`Tyc1B6pMNnhu2F8NzI7ETXmBJe;htFPES@GKED6Yj>C71 z;~yD^=lsxma^vvn77}3~4&ONrUmS<$+{Jq4#NqXlPLdYH;dK?4_>1H4-7F;ttK#tS zlgd?b_#ScmwQ=~KarpId_+D}N4RLsX9DY+AzIPmca~wW?T-y?d?;FRzH4fh|4&M}q z?;nS6j>Df8hu;^6&x*sh#Nh|T;s3uL{nFR-+d$nXu0Vt1ZMKPESJ2E2%vq48D#$Yz&$dDM!1{~=VgV}H+9bzyWY{ZPgy$FBtT{QoG z!tcjmHq6L@7|aG4c_9X~Ax5^v;7r2zMB&zc70ZPv_w~vbvVOJp>$I#b>}>}-+ri&o z+Lr%VJNUD9@Q3Z-ciO?Pw}b!O4&Kua-q8+zvK{#w;k+k2Y-J_yYg=X z=gkT>){u!?j5!$EzsuRaI8nL@K0 z3qPaL=Ku}KWx2pT1xdtT8I5>1Kk%Vl=tzBQ4?zO>Fn9uu4?Yfw@bzG=eTGr-JU;@B z=~qE4;4647&^Y{lM01*p8t;7^lHm=o6}(K=;++nLLSMnF;QfW<4POeThF|WMDQTQ! z4zoXK_~kxD*dUn|VU#4i`a^;r1em)T=7tad3vrAKMWGM#8yZ@`oZnFQGJ=K%sWSAr zO7s$OiD;;!vf^+`4l+`=+YcS})Ula58pl1&_)UoKV85-LTLZt-xi6v=6dD>t_#%zw zN?k$0WqJBA(MccYfZyT}6$_w~Z4buZdlM zPV>j3v~hlk4gcK4Kaa>mB6zUkhaXDz*;3BU_f3IKH>yM zD1nBkz!?;njIj(UjL4(FcqKp=1R9f_O5nn%z_-Y)9PA*l4(%eZX_OLR=|SK%?35!9 z1EK;iQeYbD4g`8g0jQ)&THrTI;CO+R=}i=HDuH3SOaVEkfL*1qLJ91pfXws~3IvqE zOEQJAO2DrKCMtnjq5@}8Kq!GlQecb{FqJ^65-5ubI4IDh-1;6Il-Gm?6PJfWXUR+t zqX4Z8zuFf9RS-bo?}Ihr$G&N8Z9>%&krTjTFk6eb8qab*Ee!3yC=hx-P;rM4eb1ELSW}{Tfo>8*#CVn4GJ7ufvfe98fPte&fjXsjRRTKoH094H}3s1 zL<6A@f=PjfaUTK*UvL~<=3>~o$B_;`V?Jb~=~F(k5=3oJ-mG%p=gqSNq1R@j`zT%< zXt4Lok{a_f13Ars#-dDDpmA1aif=%^`ta|~&&*)C5R)A~0YBC^*GwpCSPah4=b>GB z4PY;92xex42ZLA?`Y7D#TR6S}8W#u7Q2J}q(eo6C4$9UC`<{VGtw-Jr7s2@Qn0q(m z&4O*;%?yOTJehgnWUvS7j!*Di_Yy2B3>^;CH5-A@$LI#@j=Owyjlkx-xW!(xa*J6r zcZ*RoWd|z?ZR$W#_WB!v*aIInRzdO&l0HIB3?LUYH%+>uW;M-~#;Wr0J!$u>w|kFl z)qeCnHP%xe8tVFtgvxdTEO@?GEJ6^Xre!4L4`+s9aleR!xbmk(TBIu{)Q~yleS~|1%^ME zCg)?mCnp(!x?P6E)%=6qU*s4V8}n_gfl<4vQ;uXvzCc~r2#h)y^!T1kIhH@$@Yj60 zC)eO`fI>v6SoJM$c46o{SqQIfN5d4dBtqZAf>mwm%EcT87iaqEz5o*3ai((fp8U)b z5ncz(c0Vv|6zOpGc24;NjjN2DW8rT=485>^HcI6~R<$HG?3u`R-Ekv0UsgZRo~KOs z2)Kc+xv1P%i5mGdk>#O8U+N@#?N!1b?2k%|qPsX+biVpprd79l6^k#AIm<_Z7l_&; zua<8>rA(4TRC#DKILdXg`ux@_`$$-Br`NKd_HVp zNt_jEEbfkak6FVZ>#+%XIEfU(pJ*Jh{x(qG>RbC7#PVj-+<&2t%N6e3 z2Qiui>fa1@jdcjHBhb(-eBYPM!J9Ql3mbl)DW`4qp9XW`x5Ch;frf{Q(Xkgc+!;V{ za``e#z9{s0c#Z@n>qO$?Po2sfszW<`N zbywT+I6v<^W)pc_^U3if*cTd`DSa6T1v9gE9Ea|>hP5^`44$0k@NbwvC_ghxc3Isq z>j*Ti%{+u5JLl7aoKJJ!B+*sW_?yf_^gsG&KSHq~(2$n7gD1^wm=mC+?u41=yJ^qV z`cHi~zmV7ab*ZltW=LSD`-2g_ofD+cPT!MaZF6wv*uKH%#?JNC^Z2e{?J?ilBIX@^ zdsd)f9gC+Z^e}_MP=SCMwWH<+lXKoY5{i~@AoNNgRFs(#E`=)s4T#lVX|&F-c_Yv` z2ffINYgV=tHC)Lt=4Yz+Z4JA@SrlsBv$i|4C&F%8)HwWE^e8}wxBr8c)VJ2eE&|ZM zSLkPuMG?MB)12r$15_(}`)~r&)^q$LsN?MINew^DajEeNq>Qd4yqQpFZ((Rp_z5Ud z{co4*c!epF@UG<0G>A05mbejPrj(DjeO)EzwF zQf=+mv?@3e)S_^Oc*Bh|NSARcDeFo(uB1bUW zLA~9!(^SB@lXnX=MZk{4ucR+g6DBu4@4?>Wa{x10js_ERi{s=AbW2 zEsbf5@JtT<=v^}k<+%Dat9MFxK3T^pb!(Uwhih5>bPa~=YFB9I&WP>HzRZ5NM3#ap+CFG;{a>ZrJeKU6 zzO^qBvKlbep{-pf`?%2EDyU=s7GY`|>Q12K695L9c&4?oY;j4<{t@(W)AH3ND{2;1 zltuCNTrZW^J)0Q@J?N`H$OeX@LOMaV#3pud;TKSVk>^0t^MxzXqxjbT9tiCLT(r1U ziWN0%Wgp;MyUoG$s>;f$OR8!X1-36+T3T{x*=i9Os%zxXn`D{+cMEzFI5*IAX<1nn z#=gs9#+@B!96UTm8i&Rzrwo`b{P8ndi>e)3g9(d>bR~amK8|{rJvDSZ6p^zCw5n}b zJD>yo5@M0h(86`UPPgDt@v?={l-I6DO_A<`@ynLR@Y$9dD=HV4#kqHb#0P6CN|u&a z#OWCcH(0d(Nz^vEHM=S=TGIFz&%4epOU)0>-ers;f#wqn2gZI|bLufqNYVHhG z7y~yExu-UX&I=1{#xN7lf?dzL-yY6F%4;IS%$Oi%pGSsLK6%|~5c~*&b1samg&dmQ z-{HKHrB$dJt7JbbGXO7gU2$%paVrf~h?EkVig7Rv(ZY zarJ8yuz8{Smtk_G5k%ErHbpx>l%J}ZO)>vLyr^+2Pj5a5DJOf4%>4d1NH^?sws|2eFUwuim@8sqY`p#)g zOO{Y*4Gz~u2{P=)0tAXXAx2e71N1B37w zSQ_ah)pKgZ=26sGl^g!@Bc!OC$XHGXttRpW+`}gFF~%R(t~a=D(oH09eJ3`Ly%tJ0 zkMg|GEa&%;tr8tq@)PJKx-+z9Aia457_4!1iLZW<CuA@+%#WF12h^gXP_;`M6KJePj%H!wtKd`C48*{*{wl!Y9ROk zcKYgn2A$Y`aa3k=K@iL==AH{$DEcY1&&&Ji4SQ}7F5vbeBUQcuYcp$+5r*__c+Fw@ zh^@Qv%FG2QfI#DhY-BimF-Et@B$ym}H>a7KWx57-D#YA>)bZ-iL(PSaNeM;9(a2TE zE(<5N{}TQqmq93*DeU~@UQP1Pdb6LAJgwN4#Ax9d1jjaADhofo7|Su$q#njH>4J+Q zNf1RLaC(f~J|;VSYt%ruEf0o%`FE(-{mV<7a_Ii$+Rd`lWjXB(G{EFes0Uv3+xVc9 zvWh}q1R9@y78wm7Z-D{dHYTM62E48oJ&_`Gn4vdiZOQXJmEY_8aum_`k=$6})5+11 zpfS*@1v|<`7&<2|B$40nwJ;DHWnG%<4%zb7%e6=3Gpt8s7a0zM8u_&>-wJuFPfejd zljXZW<~XjSJCB*A@u4*@a|-oa=wY=z3icJg`uXko`^Wfs6cxD){HT?5=w)Tg;M0}5 z!^RX!cGz2aY8IaGKK*#y=euq-M-j`T(_}yT?>)L^=d;idsCy3{{iD$Wht?fJZ}v53 zMc00fxj(?11EJUJUT_sP*hdtF>^Y-eKwtJ2NTE05UHuMHL*X-PF!Y4gMTh$^PocfC z)tj>741c*7jQ%5x#+HG^e+-#|oYo^<^XgtPP!JD-A*VI`yC_R=A+Wy!mh1dO%q}{d z1Hk=U<6c%c-`Z=T3R=t~O)+C`w~Vm{lpXIugDMxE@n|V>eMjH_!mdCe?{K0C~`}OWJQ(4og_96{|4K2w04NHEZ7rP*(1F$q~<-3 zkJcl)&H}TD>_Dc^N0Z$KFt6!VcpekL^JAJY>D!21NOU~SXP`KaKyTihybGYW zX3LR>VNO7s^D{b&Jwt!!fJrHl3t>|D4Wt{>7H+^&3rzKEkT$)jc3Vo3(Llo{YeNye z{JY$S>`9xs4N1fOVIRQANU+A~8>95CZ9*GCE_TaYJOSnCko-)eG5G-QcfkBdPq%z? z8uAdi4gz^IAhH@$TFJ_M=QVLF4nlV-p_ijVUm&5pxpN~^+NS>uW(LgkgUBnC9mX^x zuW5_)qLuy(rD{!7)$gDxaybOxy;S&?#Xl4U4DSWq@SH#5XV1|%`~)OP>=Z}Ljo1|N z15Nx!I*F3Cuhd39MEGHXaMGCp%*z_beFTZ{*>3|EdIv{i;Z{z+L!Wc2J)hX< zVN2K{nck3!bKY!VCw_@cE<6S*kGvM0|Ir@Qg4iDN7TDfq!*~-A7>~K9{yiLq;~QAg z7Y2r#-$E4#eXJ5`MiS}7zJa6*8*irbHOg8AHm)ghe1}c2`P1^bp*P8+KD3|gus<^0e_)VzqyayL(tSNdr7p^G^ zy|3~x5fYeN7MxuadJmHi-;;8P%x`GqLN3ra+a=k`^L947iW}qE%0NY zVL6tVNUG*Oxc3~Iun-ETHax+t06G7BU#2!Nv#4?01)vu-_8X2MfGH{ZyDzQt;1G7g zho^&J`Rr_#Qp1DnYS=ek_#7f?mc@KvRh9O1uGIX6%E#qzpwCA>Iute(xOnpU51y65 zTLRnp()^;vfGek&)`vfpHZG^;n#Ti;GjoeV-&+aq2d&|k+*5-^Pj`)#KobaohTFO6 z$T@R;pp_O5l)E9QspB?-H(UxcPyp)qB3y$y~$PkLilSPgdjpEl@jlU&p`V6QcB&v)+5Ja zTbpuUf%b4ptxm`svoY7kx&P$JsO71Sl`2@Ef*J&zrT;AxJR!$KfVf%kW&wh|j#Ys8 zAWA+yP%wi$m_Kw4jM|HX$uZ;p1nyu`_?CmL0gk&V8*&^}DyGI%jJBfa(b}GO(caKF zoVM}?LcY3R;U2fW4gB?Adym_qFtWc5jk~Q4J=BgrH16Sc^n2RDyjc=UFAy5ns1WKt ztUg~vKBuEZJsHf@iSQT9qg+l0bKuf1VJuzI8D97u;z2JWI~Z|3yf4>KI4OKKg&Q0*rLg0A274XG`Ga**EJ>c&XT;G0@6^V2v53zP3w4x1S|fzzYgJg3OP#!2xxr~@>2 z*MTbgeXk`r3?#{61F9q9NHOv5nNM45orwUQ1&!B=77B2L?+OyIUy&KXOG%)o$epo|&%;DC0}1tr=R zO9RhHv17?y^HHzJ^rZ$V86FJ38oQd9{{aW#QLN{E>rxFC_?vRj!Jd8h1t^q2=(F&2 z*vwgGgdxT~R>&|=g?AF)Hx8c+1e}wFK;{qbczr+0mt`C2B^h(UkbD%^@Q=K%`w76065bPo zA0qr@48EW6Z#CS6UIh1m)@ELWTt_~J*02GeL(N;rFtRrr+!5a91*(worL2?k`w()j zJQP(qZ&n@#`p`dT)=ogpKjEuC53a^ViTWp@y!IJRk%e#V{d8pGgL=Yz7j)6)+t)vVCWtvZEdreX7iqaL7$y(+aQ1rHr zS=VgaQ41D%>oVk6Fdj1x?nQC^+84%R{ZdeWIC#dFzRJ2xz%gcw_2uNgQ*-|TT39P~ zHPrc;=jPx`G3JoNL*AcB-rzRHq7P)A#hL&fy$Tc9yrwIlB_>vkRqD^+e3bWj-?p~m zKqd&6>+%Y(IF1$(H9nlC3|adNlpIy(HC+s4SOd^GGm%0^S&Yek1X2t=UoMv~ynk4d zHzzV%RwTiTNEt8&7m+wXz=Y3GA*6)9h~(iahFT-d5LMYoei2N1hj2DD{GuzfQzI3m z{|)M-K==j-V0&~lBi#SltY6f%6xG9s^xGQT* z*@LgyP2sr4it-HKj`C-B?ZW)iw{`^i8i!B9z9sgi!s8`T9SIykg$p+Vf%Qi}(6Ja| zP#D@R*AWeAnE~I90&y&VqT$D-5j~y~hYN&%zy(6!YmUnOb=L1)e@41Yl5KboQ=Nk& z1F0leG2^TO=Z3x=)&)c7!6hToa9N2ltj=d#&itM8%AO-gHPrWP;D&8zCeJ!x_eS5>Q zv<&o8YuR$)p(K1A3I@wuDwqFEdWCygjgGHmRgu#X$EP};3;e6-Kv^s-iZ_6V2I(J! zQP;550G>k^k#_-riyZ_IsbUrou3~tk`>FF0yn<`NFJF>*c-P89KJw5UTu}F2Yw#TR zpj$_AsqpNlb>DfeNkmhx2OP?Ofcqln!%)mhXa0%pKvwnoGtsTfjpT46wB|HN9^gKX z9LGYAvmtt9Z?t}Mzd2kEwk8%sq!ygvzXH_uaz42$w&qgf!}i(w351RZ_J;$#vA?`G z&@@y(+#cHbxf$sFLZJ8Ffbnf0)DqYkwqrsT*zVI0Wwsmo3HTm)0URTDsr8WTgD@Qj z_|x-~2Y&LvPagQm13!7-ClCDOfggB)zs8bPQCU$oKm>6e6W1Rr%9k#Y*Fu*JE^UKb zxiomOMF5xZ`~5|gOZ>bkI@;fFg@=fIUJ>;#E5oULI~;!CMqdsc?ayA-Z;oUyke5OI z{3#uI6%;ay;|c0Arow8=suotSD658POO{nGTDVM9<41m$RxA-fURH)*;#sz|tRg6^ z%eeZ^BSGH5Ro90B$(y&h(`H?^#q}^5trEOYYh9SdB|j@FZ>(4sW|!a>fYcRPVcm)4 zH3SupWfVWaQnPTy>WWgaY+1?AYRYQLsIH<+-eeVJ!BVKNUX5S-5%_VS%IehueqdHq zPH7M^Ww?!stJs2tB&^%BmcuMWxphC5Kd7`KinT7nMlt$!E3c^w>wY?~qL&NaKNreL zqGDlC$h*w=r6S(S7SoQs4=Ww=`G_s^O=&5xg-DyuGESQ@QlL>3lXTBgpLt?Gy0Kdmh2 zmt~+3o|&>rtQajklVq(x%#VsXTGlXs5i*AAMm?kZE%s+!wsaw)0~E0sch2K2^ZU=y z7t3h|>Zm7AW!yiLSK)(N^_h}1b#)B8P%Yj@hky!)$B1<{}k(uxzDz|La{$M4& zW7TWfGoaz&C#x!}#ma@%{JARpvRAMgg=2Lrk!-C(xghq-|}3`R?_#^w(MwmqGh|krK#m9KNxnjJhy)>fXDXNfwp!3I>dp)za1@) zwe0YNxV7c!mSe9v-`4fba}Ket*@*ty@p! zx+dk=nAb-8dAOMi(+nlJ?vgSKpq7{cy~V z{nxS{!MPHAzHN&88&qy!&oJ1d>odxMHFg`sbX|EKde%S>imsgo@FKtMY#wdZJ0|Zh zm^G;w1&{hB%N!X!MVZqUwsC3oe=6o5MBgfVp?d%Is__lb8I%3T|DwW}eMLnj(`OgV zF8I%~7L%J=F!kIS7yM^Z@&1@}?zCwIlV%m3I}NgTw%jaR@W1saT*Io38t??W6&AfJ zEEMB5P@dWUi~ZNB`f0f<&i|8UTu?mg-2bbzJi~SUXFYoYrpY!K>k7~yw?+Fw)Tfq* zkPY;VC-2Cl8)Rql=>7(FBB}{Kj|{JowV2&ZOr8JC;PR?7Yd8~<e%P@~ z`_OJ#ajouH$hNC(N1p{3N7bK9I}7U>t7nLr^&G6N|6!({#_y1E`iE~{l*rx-Bk%Hs zD=r<4NrL>wGv)<;&TYz8tnimtRxe)|lmi&@P+DEaFTilNSXP1=Vl}2A{Qb(Z#Te!M zoW*e7=2sIj%K9a%U$R18rK@IUC2~rF--7%x6^h-DpP;NM4c1h1ko>RosJShN;{Q%w zO^T(w9Op;-3$?#69UBMF)s$&>VUB>wP>imoE}RMZPwTe=H^|!2wPi87&YzrbjrMCY zMg0|(K_pUBu~>RnnB8uS+_%7P9!{ zJY&9paIhv}NKV4yRgGRVeGl zT9-1Z20nyi6+2xbJ01PhxI@a;_Fqsgu=J$58@b3+TP;mIYUMa&c?qS zjpGqc{OiM=<7{5^#k=|4*470GSK;*lzm+%*w;*iBtJZ)E;g!{TEv*z&Cl3gWZniP} zZSy{Ww{C51DJbf2oG)N=`&A^TU)#1Bxu94kV7b* zgB-%*9j&dq5jG?I1mS|`VKdHFikn+om+_3{_10D%8*M;1kY_Ep8g@Nlgv|(Zal`E( z!eWGJXBR_!$X(AKvFuZdffe!k_n=;v(akejUrK?s<=sN23p_zSvmGbitZg{k{RSFV` zhkAo}*8?woN+h1Kc(2FXj51jXpNPrcl`$&1QNfy_0K0Y)V>B~j%cydzOZmNFes)|ud_vrU0f7kV@Dy(xKKR{*4W;Hw4S zRIR&__FT{Wtg{z-v+5jYdp8^AqaJS-P*4Grk(DIG zYpG7N&MftKUHOR@;&XE1gGcoaN03 zcvH@kIz~b)4dt4JI{$a-IM%G7SHd*R=uc zEYr6jdk^jdG5s4T`wQDdufNeg(VKO>BhQGF6-0tda|0k3kx zltf#a;ce<#>}~FP5yCw3CO8qB0ZfU7`K1l9=^8T;Jmy{?D6~McWWkKXu<1B1aa@PE zC}4R_WqB2$ye7g8s=!c*SYK1ne&^xhNg3NQ>fpb%|Bzu@Xn4cMIo_j25yCt#FwYn# z%@Opoj#feMGF-@7gSe{W`7EDvSUxP$xGGH!*4*A90WxH-V#R5;3a9JpES_>$1>ireA*z!kN_4FfLF4##8n{C2o`z~usm zQ+xTa->3p^EO5UiMwW$aJE&Xcbd`HFo=J(OYNv>UjeWxw$UP3Z7b%B2xsDulw#kqy zfE-E+yr|aRj7h5f?b;K7Pi^Z^_B=`FSC{f}5poLUr+QQB%%6E(6L*O!fOhnqt3VqM zo*ymqCV!FlP#@dxQSN;vdyn=hL^u%`^BF`?5&BF;h>Fn1tUFabSd3)Yf9IqB&Vzl- z&-JFP2a{wyI2KnAmV2{GyxHf%(autopp_pOygZRI$H#~NcYGy{eh`5e-8Yu(OYhvH++fjHLl;q){9 z(PCoL)e5V{#41hT_{K*si$rGyim+B};g9QtExV6uKDQ37^m}U9;ycl*@&|WH6t1Ak z{I1vfuvC?><@ci+K3&ssb5+u_DN!KYS-8+5o? zhg)^ntiu)^9@61a9r9}#eE78tKD;N)CtHWPIxN=V0v%TAuvUi~bhufETXop1!xkML z(&14ZT7Nt!1=qCs_;r}A!(1H}>u`Y%t8}QO{_{^)t>T?BY0_wa*6c+!6~P*R&d|X_ z2WJmDt44xDt{yUMaQ3hP8jaI!c`p^8s!|=&byhBym9{pYivKGZsQ8O@8~k;zHf&Kq z@t=O4#V@ek&8N(2>k5p19))IyFr@Mx-=SjSo5pIvG9k*NB8jeqW;D1&d%_TQ|yJ1=)xq^xs&AT1)^{T@M*Z#wB`*xQJj6-<3LB`SD z_9u=_7ep)X_-_BehiWe%4kbDtrT zcG+(Q$~{n~mm++WPkRSu&N0s1MnP#=wzN#xuR}8K91jV(jtKyUNNaKp$7qaIL#6tF z;b7K=Ww1VD=Tn-+xHPL(_!c9cQVZMs?h)T0t~@ca2jha7ALXN)UH03U#nCd0-Gy&2 z$Z4m;@`JW0ZdSF!;uEoT<;rDCrn2tq?G5Ogv)er1~5^o&oKM& z7La?A%)vmDKb|e}r5mzM2itLhq--@EmqJ}Y>VDjGa8IC6s(ak#cnrmJwk)09HpgW? zAqIeIzw zjIQ6)FOJ#B;7nO$2W^hmAu&rj_>j%i`}6|t0c zc4VWeNy{#QqKm1lu)VUavw@b6v}~cK8&(A0s*_JI$5ocHtAV%@uQS+QSuu^4k91Rm zrVEbf{ueBHcH$fGIyY-Nt1#ST3^9yZZrhk*sR18S$y1h+GlA)yfQl}2^Z=kOc%6SZ zsgk0o5}7TF$+RNYdZ3Tv0!!ly(AG?i@0?WQg0>nhCew;o8v8mPv^26?IY^CPpH$-& zZ8cgs@%A-L=MO zHQNv^OzFa6O*gDain2fqbNtFu))R`@c%2K{D?8LynMF6Ph-KLb$6-s^5Gcx~vSsa+ z`Onu$3u#%hUJRL5#8Q^$$U(0wvpE@x@~NzgW{zd^f;P%VYPw-XEM-$1S6j+1grYfA zmfv1k7Q0IMNXsm`X+>#Ex_<)UX}q2(zcIvFwj8u+V6prmI$%!{dmTY2r|e zI9n4rFx;$&Zd)&_VwzU%DFOL0MK&)(svJWYui8;Mlr-e8o9@9cO%BO}gJvJs8#tgSDQM=PH<`|O<;{vzsxHTr2 zkH;yRkgQyx3+LY}%9o0AXX^@Veh{Z9^gmE!x+Y3Z%~r!QJLz2q=Iub7CL{OXT4cJ$ zOGQ~xMN{Gwb@RsZW{mt1MWdvmE$3T#TNS5hu~tNH|EMA)1aDVG72O}F=%ILvQ2YK( zshO^EQc=?!%c3{p6n!112sQCXG>w&-YNMJGb6V8Ii{LyT<!uFh(wtJ`p}byj;|oz;$5XSMm2 z%*`)n|IUNSpf)txfus?D$DqePwFDUV!g^DCK~U(P|@0zf5mTy}2ivYzpe z8Nam~x=f$^)c;BdX(nTf+5?y+@Nn`Ouyx7v~$q1@d&y#)-@zeR7EU}rjA zGo0%%h4T2|Ps4Mg3mucE5S%uT2VR@#tLo%DpH^d5k6x%Ec&TyDM56fvU^TW?7nUHKq8x%HNmma2+k z4X8;O3Dja2-6-B&?!P7Z?;$TYsginUFfN?jPkNjV-8a=mK*jn?EEUbX*5^#Q}H_TQ#~ z3;S=RXA;sABYQ9|nE6pYI?Ltc{#(*$nZ@qHJrm@!^#0pi zWWc$F#*CN72(Q|IqhZ4Es{J>C>c95wbV8_Z3%#joxJgt9FSI5IqxK;Py#_`XUd$N zcssJN%DIh{sU3y`sCB-EHkLficP@Zyol|Mfxvpi%P_SDh4@0Wngf6?FXcpJ1$!B;+1i_gRb`(FGvhiwO7Aj6H&n+3vA0&;1fUdOZ&~Cl4`Sy7ybeJ@Q1yBTsZZ z@oI;ihXk6!?#UJi=5B!|#B^NCnvat3es10GEF`fYAy_^4|yzW6b11@Lh_( z9@dOC)6Rpk4F4_o9B0bJ{QfZj#+zw#5oC#OZy;^=jSz7&CX2=B>}>$i!`6yvf;8O$ zgz4E1jLmZ|;&zdI9Rj0=Z2^K*UK8k$lf-u11laKaQg;w?>_)7=NWB+X=$Mco+lS=7OC7C>u`yeJ~6?sNUX_T4^ROYlap+`-+yv81D;uMRI{BEWs~Vj zMX8NYm$oxK4{8=lI@R|*A6ZOKHKY+&S!kKQNc|&p+8#|^557A|V%{9DA?u3XcNFO9 z7b#i(VMPbCD?dcvU8F5mwAUmJTBHV%Iub5Ue?}(B`l2Kz-3M&yZRU5xu&_kmSBbgF zly2?|eaZl7MHay!_z-T^pI#)pDb;HbVyTZI)^W5HC z3TVsdmwYcUa-{A13LM_qWq3*6O+M*Seux786|@FZYONXt1fDf25XK!(VsGTM5y(=5 znaL!71B*7}JoOlz);Rydm zostoabK?Y^V<4m1*%@Yc5{fZk-nQ+>sWP%B@VfE3-VB>clJoQ&=%8u^Uat6;@(Ppu5aU>~cgnxwpOjZxXAGOYBM{X8*Zn=8vxZ8hT-!?OyCX z@l0odW)`*&G#4w5vLE+;KSgPrYl^d!*UC6z;W6&$)mexsrR7UN9K`r9U=-JeJ(l2D zzIP@tF9F(#H;ZLF)0C56+s3U|_gEoHOgU{n2I^OoDHWHR8JvT0z~oPtcVKnD%9OL& zj=-hj?Xi5dSgTN++47YA^pPW!J+o9G24Dq{Wugi$G`(B~*ft(TdP^&CHK4};*mN3j z{qQEL9cRuk=VK zEo_MUfeM<__A;mR$PIuS@hXoDi}F1#`I6I+iuA~BfH&c_P0}oFJi<)EBiAsiJgT%= z3C4TGyef@c^qz7^0t>-%iV=o6u&Y9dpkeV%4Q&_RC+R*b&$5=R48tOt*cjC2GomBJc zm}c8mwdf9{YKzwtR?Jd8$8>D4RCj~gE>!*Vld5iL>xjEGlMq(SD*Fpe$6-t7VCdtw z@od{ps`HMhPT5_&7S+kNb)o4Pn5MIx2OSfr=dF|Kc{Hj==GS5}g%z{%JJ)p7S~_Pz z-wf(Jc2b?sx7BGenZk-$I_H^=S1p}Ops$QNQ%>57{@7Nh#bgRAX4TD$Oh?~zo%hS3 zZxwZR*E*wh^Ub#EeyQn(uws_FOH4<#r7i?j*HPWw?bUtIR^7&E>$YN+x+_h`PD|Y; zsJeyf?$qp2>qgGh^;h+LFKfE`UD#_(x2u!R;O`)QA62}5k`|5r-|F*Z*}faK%8FT5 z)tQd-EtT7$u8As-w6|(zTdTV3kz0sAqROo^-FI0kUWE8ws_3s-;@hycijCL4M^~Se zTp|7dX-*NY+GNKR0X^l&^ExA!7@X`owIyUbqS)jJ*On_)Cq}MRh;lZ;aj*?nTAAkB zhh9p!xliNfmXw=2kwc!tc$aU=Ro7x@I0HLc`Q{Ktd-Ar5k#COO4zZs1U~UQE969}U zbN_Jig}@ImTY4hE^LG_s%Cfo(CIBS>2bldC=U-lX18;{{km?{cB}qjz5pRdpNPoDR zl<*?QX?RbUmt`Q2$-aEPfi)et?{=oyo!dzTV48$CEwPtkc#WK=`!%FpCvMNsVk6A% zob;Rv5pJs_i~~oGW{$-y#7NV<4wB{+aLorhmwQ8J0?5@ImRU)&fUE&^1>UrCG*#Ln zY;)p37K`k;_l-bLO-CyeJ^-#dsJw~^zXPDEeZuboc{`}LGT|%YQbOjK z#`P_#vB**iJJihyxG>x zOlP@Q4OeMir_(m>$VOJ>%B7n*n6uTtLgGcdT#@upD-!-eXdm7VC6LNAyHL@YfX~1y zyZxZnYRI`*chfNdU8@}Ja=|hlukDwbCB6;1ZQ1jc9c?Bh4@9+DF{^{U3=8NQ!~-A9=v2yS4mc&uX$NhxzOknrHVAmO0G`DoE`Wy?lZd6)6KeI<#&TY zH11dm9aB&|_ZcI(V7v-hSb=(uN1C^4!7-) zzEaqM`*XiH(ztK-?I+X3^OI}PuJ_#q5oyk&T` zs>-Y>5PQq$t>KktXTb8``;jJ(JN6oSR=>3}@dhM*7YKYX@dVpGquXeB&U_k_wNPG* z*Ze(zyAk;nfx7{`h{*GJGboa@2bg~X`Ul=Y2LK>HE0f+q#DhFJ@g{u?pcf)t2pj`| zZzv1yASLFE7ve8|X@M|ACG`EmaGc!(3C#gz0az}=YZd{hK;&Wqxd48F$n|(_A8BF7 zB)ABbh7;>q&W(r4oPF^N%4*i0xNn>gUqn;69I0$D9Q;dg=HEcM9eQrXn?XY62f*0A z*33^&u(S#oOErPsh~Y6rJ-BZ*I=o@2*#Z^6r5X~Hni-l|tFdo9ScbAuisovX30s7? z&G7Z^sqJ|RN_d?rg9N2$L0d)U3Q$C185~igDTcTmxH}EUc~nyi%AcWPA6_#E;1fi+ z4J+)=`SFQ|8(3A~h12YG7tZ3eD(-sStSHKzZrk=^B_!;9;+VF#W!f2K8l{=IzxIZe z2J+4U-8EX+FV%F~@P$@#HEi1-r@S-jAq$ktI_x^UXeqaaVq&zblV=#S;rMJo99(jdt7VUI#)Sb%1T2d zPGi&19=C~4q4aGd`FB8=D?#}yw7-VeydfT_1HfPSf1i{~0ep$b=LAXsBx0Y=fj5K7 zl5Pj)G(dgvCjAz`SVTq=coe{NM5YpW7C<>7i|}@3^LQm%3%-MzMUEiKOICu_;8=-Q zmS;U8b$G>K_D8i*87pQ@I_ADbarR6%fqMhR*)#nPk^2bb0(cCONATJZv8#_Dc2(brNi}Hz00x46@OV6SiHN&PHp|kdEvX7x-0@hIkDoKW=pS%u4zl zB>NWgLV`-aOfy?~;Z~POd<5%>fVK>0tKg0peP?ANV+TR`0xCYlYrX_v0CpsMaJTb$ z0M&?;<4xEJ=dT-}>@cG-tLP>h8FkQZ{smljgZ)mtwqI#>9)8FysXofc#>ioehEeyA zMD-k`bQ+=+MxAYR-$^~&L3tQD9-yA0Y{kN}7fTOX-Zq^bZuW-}w@9)>^UhE*!v5Il zDz+CY-N6RYbU~L82;&ixeT`U%n*a0599V*o0F@^Zd z=$o2_3{3&$6=Y@?UULip503`8!8sT}0FjXd`T)2ekz4U*P+t1|pIW6MtXOxQ#MMUE z6Cg^zABN2Pcx}#pih3%)&s~C4#zFbI(=eyF7y#xJ#CHLC%fK;)lzGsu0I2O=)`uC;hT6ZR+KhK{@2sP+q6ByNa`rRhi0aoaXrqBkye+3N&*r&QjcmnH`{re!kIg#6GItr=Z(MGqn0Y{thx@r*ZVm@u}S4Y3B&mpG2YR9XG%z;O*lI77Pwkxh7oZII@VcC>2Ns8)@B-E<7G z5z4+bQkS#w@@(ND2tSC|Hcqp&T{VVvmf!&U#@cc?kq1kdGGLB}y7&QFP(TZs;tuFO zY^3{hFirQOHk&3sxV60lugR^zBOQcz2Cr=X{gER+hOlB*^B-gM{+0%^+uhO;6J)%$ z5t{u}&A%&sjstu77~)eHb0Vb%eYN!Z(@^mkUg`CB5IKl<6umCJ-6<;fo%Hral;gC| zhTY2V$VzYHXkYWS_3}Agk=dVyfucz3yNo~A}HKv;DZH8Nr2aWp4x zfK4YG?;gUW{hAq0@a!`%x4(wRGv=JR-^Gr4hofB>EO9wKk! zwY{Q+wKGk*@;Rj+ZDGy+;wb5Rj*#pa%PP~!6nGb^3v_bsm5WgC41_?<%VEzhd zFW#iC06KQU)B$f&KLCRe>4w*SwN}R)JFNFs-zMz0XbSK7Y>^Z!$mdU2sSEqlapaek zAZO5Rcv>{iolx-)#iQ!C+g1`)bU2Qw6RHg?u$aoDOjqmK3N?*s%TyU<+8W2Spe@to zQKkt)RSu}BzAe)=QKqYg#h4yw%d{@abXy$LD{Yw?qfAf5F@4pR>82>tt8q+SP@Hit zxIN1BRUFg!woI`-n6%+B!%N#T-5u35ERJb?Tc+PyOt$H9OmX%+U@_=P0KS7KA46C% ztL2?(WVS-NY|WLZYRmE3I%xJ&wPxGI)0MvS6JvFe1B+aoAW=?@u0(>DGto;ld)ukeQN54Rc50+kSR5UJtymwO!UsnB$5skAKzk!oSfkn7 zrjRmE$+S(untaKe)adM*!irhxd~T%AfU{&y?m-%xna+)xy=^-CemtFLw4foZn4a~C zkB#*0Ryt20jmMbI!IP$w{cqE;SLo@)h&=3jK_zO98j+4Bpaj)Kz;=tCfRQQ65RNg3 zg!j-rW&5?~qOnx%{%uCmHE6-63kAFnrtZOO{tF_nB65JhQ2_5D@-E(_G_dgiGJ@BZ zr?v7QnPQLH^mVOy$+ZI^idA2EK5TH<9zH!poJApsuk$jjg9q8pEje7k|<0m=LT zBCR$tWTXSbgwdI&J?2?BNahKSQE!ao$x%-ptpLd9$xP3Gfd#;H9x1HF50_pr#$*n4WQH3yWQ&D9DcWk6#*N7o2AT!U97cy@H`V1cZ%rp8) z3wY{d%rnj+a0Y%O9fV>Y7Wd@07eKg}2hQ>o>j8+mZPpriSo#bU8gBARtJ07{sXAav z_rvm~hCHZFUk(3P8uB(7d_#v(i?l0AbfYmtc>bBEjN@Wb8kZMuT%I;=MfGK1ET%o8 z%K{gy`$lHTm^lK4ERVCkk$G~-Jbfdx080t#gO;;(((6vL|4PR0LCHLBzJk3p8BjC(>HzrO)lx7&n6P#k_GwM`Sjvw z8ftv5zAMzl4=)*qm7%*R^%Tqh@NgNQCy7%i)#(si^?~|YjhkNvDS89gw?3iB2Z$MO zjU;whC*A{ir#IuU=}zYEWj`S!3wt##sqy6rQse2+{wJwXmfuLw@xA4XD2PXv263`9 zI*4wRFFGAb?dArPYcvzxJ%$$nRib-zF?Pv<1;#c%DF$~sT`X~9+hpkoUR^Y{&6fu7 zF5zSHTTF2VSbIF8OAC9^hf=?SF$Oo9TwPJ|jfVzNR*d-oS-ZOiv7>rumcM^Z-aB}U zbu9jbg1Gxk@)mW*fhpcI`8~V4r;>Utz{~Lt*bOb4^_9>K*Gy%x{)TO53yAmNm23YV zdc|&tg&^JISOg7na=i~6&3NVH`g26&7vk+5H9sfUm#bwI_C$nzn#S|4{3eZ;LxeoN zTB@mBcs&_Mm4}UKUu&wmq{8oajd>B-*+36iU);P2aX!W!+(bC@8RTulY4kwX3{)Vz zXEJ{q#t}6L-~giNlno2$lzf0=iFN_CF`Zv#ux;FY&45&}-`t5e;cXi?UDLG@!{5Ey z=Y}{R>D}hI8R?migHQ(fg?P;^0Io#jasqb%s7Is@uWg%_^-qGO7$o5tVZT`;zk`9h zpbP|kGZ?`vbiK3$BL*!H_U)RRSN-2q+!$*`+F^~CP8xGPvQa_*RCUJ(o~iH}AL9-$ z);Y3SeGS6jc zL&ri5H_+xl#Vo4%tL9L1XFVl`d+pmbfjg$tA;x0U757YG|4dUyfLf!d>f$k3P;j4I zE$ogSDy!tVRrBD+JAcwc@*D-vO3lMT`AN;Af8RpbAJ;?<$^xYU6Rb^HC~(^>U$&oB zn6B&9!R7zc+MB>vSzP_&&w6w3%}sKXn*a%0!j3=)1jD|Gs9|3DIl zsJW>M;p4>(<&TCjm~&Mw?wk3g|DG!ko2yP|?hv6+NUu0|94V*{Xzp}@^L%h~?tH@A zErNDVmcF~)Sd?>~bnophhm}XO$*$c>1kV(xk^d42PLUT%OwQYj=<&=c`J_6cx_+Ty zP8U)}{{6^>?HF;SG&RM)`w$_?lO)tTsqOXwucu=F4T$$q*?V>aXX|v5<$^57gm|d8ai^l^I7{CbEJ4Yfg92eC7vlfX@ z^Jk(Q^c8eAE}p~d&bgR8C?*f}+g-Xai+bmdcsyY2D0s$b4(UoX@(n5v)U*Ke3~M8j zW`gwu%CZHKn-I7HSLiWB_9E~Wt_2*c9jhszhCstS(AWA2&`**4KCTV~h62N2YUu7e z;d07#22R9Y)@4ynvCwcTbt1d_f2c(5m$@B?Vs?N&DT>Hw5(eJcC9FT8{j>Wo$@~eP}aiNOXe! z+9slT8zM)1DHGuEhRk5XS2Baf!j2%ws~b-A_`iMo2*S{(b--1^cKQacZfP4_e8sH9 zD4|O_oze$4`1MD^u&)F6hq(;7UDXwmpn5Q=OWTsZPzvKNeV- z>eP%(b!tYYIyECxos=;y;b>S#osN;IkA~%m7|#0qMo8uWy}L&5Q* zZ36@By`a>$B2W@|2Ar&RC{$ojgd!UjJK(@zd5hAqIzcOe(g?BqRx5NI7!siNhgj1X zIaVUWtZHa7P$rR479XMr43)?@YdTYgg`Ps5ldS8RXLvSsH3Jhm-2M#0*(V_p-=pnE z#v|Ie0(l1^aaT7UAO^;20=pRv3SHNoMR$tVEULOu2~wb>kUg?t4i*nC2o0A=p~Zs>LL(&7$Kt^Sp^*|Pv3PJnXp}^TS(}(LS|Z~t9$XL_ zCy|pZ9$XL_FOeA*4=xBDCy@$^2N#5nZ+8H4o@uQlwG(m=BC^=x!3Cj{MCwM12N#6O zCDLN?;DXSk^m9;zr4|n^2)AJ*UY_fQ8L1?~+bC1P?3qofEsNZeY3Pz$q*8Sra z4=xCuDYEUfcyK{zfxiQ?K8rJ4!8Ic-pLKU44=xDx%RGh$7x?!<4nM(JbRd*USGS)Z z`#OdP7x?+9nPXZYkUtY4LFAXd1TkU+AA#{?9^2tl7>`tQ;SeMi^(XZDObH)i_y!f` zvu=TtB`i%bYoN*J>C-QRyl7PFkbv}Ykk@e1)gb}t>X3kRbx1(EIwT;SGeKj8+Y|Xr zR}Kkq<&XeZ9THHw1B|k%7_2|8zzx6B_aR6QU0%R`dj%mFh@1uk&dX(k$>qZq?NqQ7 zx!bGY8rr=I_D3*Z1*;J(P{D6OwW9=w@DQEZ;efqjg%CG}oQ$%~NekF#YQRZ60R_jv zo#NjK)mmpk^JYcxIfRV@9!)YY_!h%v0gom*Ra``&S-_)7PLt-|!<1)PPM1g@vw%mF z%$G=iv!FjBXGo;PH1badA*n14$*koAUc9SO&bP>OAFwjM1I(dK8997yJ^W23 z8)gKl4prkwe=vzJp=V9+-W6c~bpRjB7xcpmP2!Fv?(GOTbihUq-_H;45HdUiL&z98 zJo6xXP>AHtCUiI`3USHR-N@+$-;w#5AY4HN8jz8*5Y>^hFpJem@1XcFCfS@v0W+lk zDr9m_5z#M;yn!$;XOE;GB+>!=$*G2`%9)ZyOZbxM{A2hTJ(lmFXZlUD&4#RszADi= zC?@l95v@0qlxTdFKhh%5;|LXX@sCkv!Wy=kXP$^Kek=%o<}DupFT$$dYW63lIXBV{ z#olTjC$TvZR{mD=bWW1YnGx3h17>&X%$yY&fZGG+aEVn!SpN^0b0jt|LIZffJc}`F ztr2-0M7ODo)>?n$AWFT>9H~;n#$-PDW!9T60h=b^4}QOgRQ%yJ2b8P*6JT9$_Mlj2 zbi_;*S3gcVo6YoFfpszB7vc(W^0-vNHXwdIuKrKKoHIZq#0BaL%q(Vj3JE)y;ZoGl zB?|T@#CH?+F~q%8!TuNVcX0Kqs%$Z?HVfEWe~I|#gw<7_ZCr1*e;GnKoZjFXdf)^E z@WYT=etW?)^bXXx$;@W?@_-Z}avl##-FCdfGGBs>SDS@&DfSE`bOlOhT*H`|Cu0TNZr9p6UgG*bOU4MX8qe7izdVwbtQ^?-=ZZbRsq!l1? zKCX-cP`v_y)wqWDM1-C>qZa~C0`fatfmc&_HIrVr^ms*Xpy{fz<}PNQho z8Eq0*`-fmqz0EGP)Uk*UC9DC)+}j*NSRLYL62`@v-sYJ3;BY(QHxYI*==D)~Uq<{H zT-^ukLgD(EK?-57U2#UJ6V%Y(6z^(b+VmNaLi^C72ADGRIvcQAxCSgohJj`Vdq{ij z3-d$y2n;sU=`8q0Oc}1w2rwLL$|!9%;^lb+fK>rDjdO&LUdQSL|MN<;4((2tm_cC7FXb)rlXW4`hj9Ti;GTX)>uRR&VWqiX4c<4 z23tYmK3u}!IRu_!gbcn#;455#{yML&jr3;hvh?s5_Bzk#23>_vEzL$J1@h`A{d~j2 z>V!uG0`|Y+_2jO7eEvE}AyzWolWRF3)6B?nG!iLzVG$NzaEV;UA~1*%$~7B->9_)C z{ad-xmqUdAB$heHkRQy)S-WjM>{q0^TvIkzL#jDuqy#NQq&g3L&!r%=-K!C}k`YRE zGXgi@3helIQYq^l?&&73rS2=dpLsq`;_-gwG5AhNyNHLcbeR?xS{{7O<6_q z$n(rBZUWj|uzCSP?V^-LhlIKTCltTT(yd{^nGR_j2#q0SR_bg7W-`J`osYn2xB@R}I>~L!b3W*p zdTRs@Enfy?!g%LwOcVmEXW5*@BYDW3TfWYRuBxy@(#u zL|TG=S2%X;Q1~*9#DPA>ex<0+r%E)8(^3A+TRP@qn{XUd{#3W82$*Yh7=YN0!GNdr z084X6XS5+q60Zk=$I4^i$BGpAOJF~IW(@prPX%rS_5A zhe^BZ;~;X^?hy<_J?aMG{s2U=hr5bt4=bRq^Qf|}R{;O^of_Y3$>uGe0Dlzi=k@`_ zXJL4X)UAACH;txd-r7TqyX%Kg>1uDO^NtupK3zKKHZ=Y*nzs3YjjdGoG{Ef_0AC08 z*Wj|BKx6{~w=l8|k-HJtjLQh0tMiKDyV_v8V8(dGVmzeb&%EV%SWL$#f(wpi@LA-1 z6jw*iY8H&A4ud`L!XrVP*({hx5a07U4%ckHW&Iwq;WI0wfnWa(@X00Z(Fj=1B{F~5 z0Mwgs4WeGc$0F#8tK{Gveu9liIy^@SHAx5dEMxvMEb8Bqp-s4+=3nwUA zobe?j$k@wMp6mkj(DeAYp}$pj0lpJkX8wV+@MAR}%L`~JjSJib(7gRC;B1E~AE!D8 z(fEJmDV^)A=LS|2>q5Z)2#jZm<(r)#`(`93h-mmONJ!k3nz&sjvS1(FmQ2?0zxR3c{n0N2?jtj;(zVT4$O(urx|$OMS!UXqPk6p7q9C}YhmIuM!y6H*Cn5BW^Igqml31}`dwYlu0g+0 z)2}j$WSXz(cS~a?(l7PspAG;$kI>IBdQCzfZVkT~!g2VLCcD(bO~%v8;UTTzCIw5A zUFzYo5o`3~ZmvC*a1PhfxFc*&?Z{QAw3ls$M{%vEyV2`aD908rGok>)9&Ra*za7~S zsyD{7BTyN}o6L|Ff-LrUlepO90Ht2sX0l7ksnH`)8Y55|Cr~N`x*P52g0^YLh`jPM zZ@CtkJJSonRkve<*>?oWqeNkwAqrdJp(9ZKLKHSWqOgZDbT=1g^dnLD!Vgi{w}p>D z>Dmz}e4~gc>=p};Ksk;mJv9nj%Q%g4SFA*@0ma^oq_@$$U&A%`K}4o&JdVluTMMj= zhuGYDqAGD2CzISJjTQ@x6>Ny~j8BNR2WlR!@EfGCLOtMoCBTn@4+`<8&3`P1N z3*U&7AV!L7P2j3*4U8d0>qZ%k|E7y2waNG2=CFv zWJoi{+u0n3ZN*UAuiOaGQo6RV^G9u|JZTxp8h__*9rK6 z&gP)VEc5Z>(zDH+w2C5gUivBNWDsnZK-n2IW72IjmQWeQK`~9UTtvw%f+zz) zD=Q1<%KIr+IAYqF>FFX^k)PzUi_Nk5TGS$6@BC5ugyx7C5E2jQAhF1=#Ky=|2)HAp zAc{g>0)_P6`Q;HsLcT83K30mk?Nu3yF!07et(Ryi-!aD&rKJ}+<&hCZE_f7WGPR(+ znO+oDLJE3ENa5E!!2p#T2Wb6v&Nh2yM$+xB9lOqLpKbD~?Jin1=$7%uAF~xou`s(W zLNWYW52Dp>LeQ%(6uf(}5z`Aw#gDG^NGxc1g!MA6!?~g_JpFBFmPdxOhqcm+7j%=_Ke7ho1m*q&?i;r6$xgL z=2eF4nUt$c%Ad?S5HlCNSgEWrJ(g9~qs|x_61UupWV_51?nR-vy=QrL8TRZl(e@H7 zCr9*GWR7&%c(ei|o<-uT3QCiCNoZJz^y1Ryu&5Ys86DEixiEj(LAELgy+9H0{GMrG?^dlPZ>&w!0YQlL_M_AW716;0+rBDq)uedo(D)R zBSUdaRh|K*pQ76HKu-omT=}w+1QRaGA6W!h2PHIku}m@_EY+fztKyjim!`u5k`b0T z($T{otF);N8fzb(E251lS2Q#NvDs9ImE(JB5xQm!EU3g0QfsliBR!VL86r>e@f!0e zPZ^1Y8C|TU@vz2tzGQ5ww;BgQVId-xc&qVofc93$={haS5vNz58L#yTvFdh*7iD-3 z`S@gaL??>}0z4{WjH$&qAu*M%J+X`@dSy&(V<#O^zwRkT*v2WRZ&_*P(|=83Q)2Z@ z_Ns@GDGB<7%!Nf#gHz)sR}^o_)8d|*646snk9+DFa6V`#GaWS5G3n{ZrkV@6%u4mz zSlEs?C%)7%C+>tWs_6xbJ2}>XQBo2)C8l3guczJ$?HP3YOVrf7pz_N|GK^ET87Zy7 zyiTJQltNCAX%9X!eI8sS7S6=gL9jCtGvd+GbWzZmFL8@0BzS%dN@61nJWIf?(Dhuq9)?gg7>IcOktqIHKM?SGnbg6D=akVA zAgAX)w9C#&=p8cS&N$u=c%D4l>kx8c`jEQuh7x!NBau4Pp6l_GCW`TOL#{;6GR zqivr|N%^S`@rD9-fQF*YH(zxD1)@!qO?c7DI(k$DqD$LJdF#%iYKYiHYTV0)J|UG$ zqLCG<@kx;sOIIAbe{>Owdpg{w8c22rZKFr5yrjaBmF|&yN{jHgvdig}pwpD@gFOcv zl}#UnF(U>iM|018BWB`A=@`$)_VWs;8iUehf6qOj(@3eaVh@t2wSh+zMEAcXboql~ zsowBW+Q;Cy*^elqX3$y0i5F#I&;TV38DgA>VWrd?9ikUZ@V&SyHzYw{qI3gDLdG%iVqshDNG~}q?j^@7 zFL_+5L!97sO2^X?vPvh${GX`qgm_fB!xIxd=1G$3#yxI%g3e;ja?;WIqRGja$vx^6 zF_m;Zpo0dcdVXKC!sf+ zo7jn+9Lp(GG|hiufQuW96dMZ+b#*c4#`BWkhU-*Z4Y(|` ztHn=|c)zm|FfZVrOZn$|{INDOw2gm$k3aSw@W*+L(f9Dja6?AG+DF>>*+J8FCp(sF z+AkQUYv($q8}iw|yTAl2XuoU}6M%S&7q0-o_1V9_z?`>9^6ip*ZqR3LS4mIe9UsGT zOy7R|wS6liNwGZ1G@85;@*HQn-FzhcvFn%nb%J)RhvugoU^_|V^4VoAjDx;qPpTj@ z%4hoQA(J#+|0I=Rvd(a9Jj3L~45eO%Jqmvc@lAVhjPXhZpQ_-4QsL7Syd)JqQ^5y# zaIKqJrX8||sI)>a?XY4|slc5*u+FmJ-)S>hxBss(RVR@Y#&yYz^DYw2WTapofo^*jvxs4Xk-EmGLBveDG|F>AK26LDutYs7seE@^2U{|1O5 zf35}0@3u->VXuRQVN7R-!C#{bS&tbzeo6danrDSDPkun>Wz?RrwmUim%*&uf3ahquYLyo3{dh zd6C=*V5aM*8Y|oZ)1a~-!}h8RbOZj$s1QA_xB!aGQx^D>LEXj>nPG!U`Pnd6p?yXC zEpFI8x5hQkbx-m6bA9F>@=s#s`ZfyTEhLOKPAWdB>ro8)i}_c#wzp8Sc97Hcy{Oy$ zHEx!~N9Vd3zOWmb;+psSCl$uTy7PiH%x~d47PyH?DY9%t_|h|q_$LrG?cfGC(`Vmy z!5)wc5q=H-NYnR?a*sj8+OHCTFo4lXIy%X9Tiy16rZdM%>eFqoC?odQ*UyD0J z?kE8heUsdLxmT$8DCBFUN#Z`4s%t59=lJZ7YkYAX|IdBQ!3vpUXC;7Du{0RsK*OTa zI#yMpMx{7XeqF0MrW^6)x`niYQxPQhc~ETy%we8L6Y!08Z0P4xOR5I~f=^fb+T|4h zhwaa;N%WMWT>zQ?jD+gNYrw8AnhYT2JYU4ktp<}-2%5N0mgXO}m%>);%wqq_!bxst z(GSh1mxPFCpS@CLE>FljTI#{g_5w4Z(9Xv=C`e6dl3 z&ro1=jyEia(MP~3+b#6jiz^uE=|eLM+3iiYr(HWK9;CXce^8l^;2w~TwMy4F9TnuW zGt4|uTKZ%+uN+9DBV<)vOc54OLfGyws<05gZ*j42w$_JeMbgk+1Q@fc3Q^Y1ElDx| zC@)&<7W?dXEI-T-EGCOc#aAP^6bYGbhhHJsijP0JfdeQsviU~O18T^Z<+@R~gXCHz z>8o^ltD9k8vAji8A=!$OvWdyrUt8uXA(QF4XClLfc4wX9cAdw+uFpTv?J5P>$YRG_$vw-( zm8^sn1ndt$utPr>Q7rk6A1U}OZQNI_F1f1MOV(i(zYh(n}Wry3X8QCXC+>Bgk zIt&auFe>7dr2Hs+B6K6@Jk>_vQzG)-}) zRyS4(in3dYtVN5k z(1+T9lg}0^YuS0K4oZ>RAyS`}DzxaxUqjj(_{YDUe@7`r<+2WHh4u!x8jdVbil3mq zZ2$MlTz&T2=cBfi+edrK6JtPNsTXL+fX}}dI?>%NT44sP2>s~FJ=D~f=#|mg?1AXl zz^-%MZdbXUvA04?HK_hBESR0WM%-ZspZ{ziVw~f%dPm%Bd#QiFh=WfroxdH=I-v;m zTJt{Gm;D6*G4bq9pe6dT1}M3ZU;4a{AOlPYVkqmE28_bqa>0E7f%U)e9S?RH@926* z8|Nq-DVtHz4kj%2yXTo~EC5|)qp0?Ea0xNi{&PHq!~!uD#R4J|Y``B2cu96T##c)8 zxN2jRynr>JeRBCgOP4er@>wOS%y+>%9j(lNIUi+~nz`L%(M+1KG%yr;4N8bQyUaFy zY>##kT#9IP9q(xI=+p`TXt%>HXhy8nb=wJsX=h=G0C&0^vmBovJ&OIgQcwZv%R&|z z*>3<*Wvf-Gwrb5{1J1MW1aE(<4|R?T!lWaS9mWE=9fV)N=PzQt&=`F7mh(863D|q= z4Q}UKSty@<&v`8=&5Pq3(A;Bt13JmP(r5p;+!_X^kq9lszX9`an%Fx~V4iQ4sOTSY z6>X;n0GUJ39>-HRx1s0#=eIy(_M;bwI(k3@q6z!c^DEpQ_H`UK`0Rt{OFx>VBYM77 z(w>jhQGe~^I-#}Ju%_Nea~-0TaFPrW4nqxJoX^sTXY0f?G|7N(2SzIPyN2o8iyj8j z$kJ6h}O2U*Gw9>DhH%1 zVmD_qaBE+qbr!|+^N-~fZjrs#68(I!935IjY0k4D>Zc7G5+7}ippsBSf6z6A)9;Vk z61K1<*x41n15%Msw}JKtqE$cv_CVE(%S1NS&3%tT#CsErN+GV-oBX7eB=sbpLL*Z~)Gi z6Rv=7tlQDPSVk1+txVtH3cy}b7U3J_2G+)nLxh{;o@`?h2$OvlhI_L8gm{65_U_4` zhGFnCs)K-ivtCAPcX1sI1n$u{1;pWO^mNqqy~=J8;yqwu?90Jr?hdyD9m#0utHPdb zLfmZoBjq_tpf4LZTm`VG4Qr2Bf@$A^s)F-D7U)ELfPK;VqDKE=)IA&*8wg5fZ#GST z9O)^A)WV98G2kACBwS*adrmHyNN*?HN7> zPYizJY#&l=rP9@;RrqqH3QhPHD`C9_uYi-M&l)22iC)2e93Et^8+Rbji38c89LTe6 zIFL3Ph;>4O1BnZ`P;@7HI863b7=o8%S?SldP|x`-+!%mz4Mqs>DyXQDQY%Jkn*>iXwIO!l(P* zhJ}46L(hnw-^AA-V4fIIz>G0b$@KkZ6zZo;kYKX~lhB~yH+^;$9qOs^hO!^d;Z#wL zxQ6{mS<76LFqN}cSg1bH%tLJy`c)R18oiO_q}_HCyZaFM;@jLA zkSF$u#=ffEMmoi{@Ffan_Y<@BSp#zt@PTsMdG{qVQrfA2J3D}}6xcm-0X~gFyVnDB zu=^vN4#yq=7=MPnEk5C#ht48#o~kCC81~znmhVrX;pBdjA4QW;&;JFECAOpD|JU#i^CrbebU3ix z9p(_uN>-zV*-9`d^^gNlhqZKXP;8uulg(d#(yb3+=1i!HTlD$#{ zc=WttdT}7Xr1=d---|XPvs+B+)sXawNUkvdh08p$r_e&VA?6B)f}8fwu@v+Hl5(L= z`6-^Fz1&Kj^5YRHs~j4IQ!HYYBUyqF{W_-nsRuI!5(>JrF5{Cbop zoRZ*T&FA|g_*|k>zB?iXho?(e`zogf^n&)`U$;1}J^gTnk`==nrAH|j)V~(w)8WSG^Oiw>c2HDcA54|wx)eFA zV^kOUw-};S07$L}CdYc{y)oKm|96V-Z3tZmSjHqz=&eTxExQEv1_v`Y7m`2t>rtsf z?+0b{rW~=9jxxfP4y@zx$b*& zTT@u3Awqv|EY3xhULA#T=M6c(e z4`Tb&bi3HE8k5{^lhOI3!FU7Id$5&iy1nc-47jmw7eI|?Bh9q8%618wr)c&K!;GLg zaOi9L+PTO4k6U=Z+v{#%FOB4iuoB;tWTd{aT)kAT0kK@iC_vvBKx6cb z0oWOU=-?PYvb4ITzEd@qQY@?c7r6ydlxYKJ{VW{U_1UcFNka*@sMVheyeI>Yyaqr_ zS5B2h-VCfo^kKU-^C}8mpU-Z<7=e39dVY)s-Qsptx*NmX6-Zl)MbA^grUiHLNgRIN zhe}B|-Eu59?T59b4cMk#&LNk7P=p(my|5VQHo5ID zb=?MK&o8;|2o<`>byxUa$`c)g?4|pmNUVR3a&zs;qI{*JKlre(VRKMspGvS^HR`A~ z7XyPZ>{heGpRmgXAO_{{1UqD4Q>!p}<2l`*ZgA_~=O=pFL{> zV0NoN2e+Tah@^C;{g0%gQ%o7ADf_v~5?#^VoU*3HXZI3Xr>*(sORNW?zecYD^=5O% z6~vInzVm_#f3b~8ZW9Le+&hEJ$~`r4Z}4=$GhP2Q*+jH`Pk5`mvXRT#(Ti$zF{4F1 zF&7v`EfttFPnQ1Y1Q9St)^6a*{{#5Me}m12xeblKue5s<<`hgV?7kbs#hj9`lxzF$ z^W@~lsLfun9Wvq(9onbQo`J3vn;zXTFmx|Syed7?Z>c<&3c)9llo*Y zGgUZ)o8={n>x=}h*#Ecdtcm74sQ@*9#ik!0Q8MjL)+p9DmP;7z>Y7+D*CA{;Nf35P z2>JlOEL8ZcaXIJ}?eERQ$rGLqM1wJ7CS{ML3EL-G{iSQ6Z}diN>sVB7ddU(-7^{A1 z_T5-f4cmXQ)MA%UmZ4DN8Qk;|^Fhy>!-oXQMH2e7r81a6J?oC+I+^y-L&_$5z&90x zcZJQrYd{Q(0RqT&+b7dn=H}a1!+6nUrup(*8Nb-s`yp5Zj33)R7X1L8N0Tw3+T%pu zu496ZXDlST9ik;HP;w)0GP}C_SVQlE3cJ66Z#6vqU8x*{*q=zV&lZ+J3uh($*Bs}8 zjb1NL=J;}q<84))KC{~Zm2rTXkOgV)KUm8Ms0mrv~uCX zrc@Gh=1w!}t6QQAs~5B`N==+NXTpqB*xd26=S`cC3Y{_~I&<#Cxf4;URfnIBUGbF8ESXi~VvdL(yu5N+_E~u-misB0m(Z$tEb?%1N7FZ^$ z0hW1oWlMD|T{z007%du=sG$*NRa?KvfI?_r$^eFeE$Jk)Q3X@AG-uZM2@{D^->|Sc zTDvf*I;KsZHc=aI62m2xwe?Ae6KBnuK1+*_^FFE)>$-+TQPzB_Dur&M6sL@z zHO*+MUebWViuh=qyn_ZCkOZ4s8k#B>RqK+K4MAEoTC?QHdYV4%gz^(pS;C6gL{FSH zw{XIsp@oA=i%W}33I{BK*;J2SR9#=)R9l5;WmDDS(MyMw4JaE@IABrXfa!w^2h>zH zFRQOw+|*Ft(Aqqpwf>yi`h^2p=ogxi*xXWCb=H8!CWu}I2?yX*INF7mP*qpiyx2>I z2SGwrO+s9n!|aK(PM$bR8I)8nYc;_bQwuY7;#Bw@YC<)I(!mD4Pf}fv78`HZshP%4 znPODd)is<`y)f3kwYe^CXsufq)pl3cP*n-Pg)en1s%uz)pl(%-l}*jn(PosAj&N~x z<-%&1nYgwEtu+S94c!4!K0OK&XQ6snZ9|ON%11=!PMbV!`YF?hSXW(-;x)HKn`_Ul z7PK;h&H;_4UI>6-l;^ldqg5@-8mprVNHRuA`7Of}h%Ju|ZDD0grQ|-RsTOu5`RS6I zs+-|@nx&mUFtkn5G!{e2%?6to+m{h-pbBbg&~^>+G;GfCzM@8mYMyMb;xdftrltlk zZ)s|Q3hNT^msBsQYFuVOm6W`;F$x?c1HZn3g{h<#H94f7Q`@pQ3YWX2viU3nrLTc= z5aras*r0TlF}eV)lnxrCNL!a*w+|_KbLH8(;L(~zaNz9JqUTgLDXmtaiC3LXJy6e3 z_*v2V)+K0n&8>BWdi^5k*OP(j&uSJ$m2uI3pVe$MqiyQmk#)TQ=HJ?gTXQvfw`e1J zI%%1#U&u6@1_uox>D=n31_aKnrfJmbc3vYzYH2_*;Q)cZbYblx@z@KR#T(#wNO z6Ua5=H{iB~X@T zAqGZ2CfWz#B}=Mdn-GgV5?g^MVIwM8Rf}pn#_Z`6CP$B-fc{%TGsaJtJaLZTD_w9!X$aqWxf#3RE?qj>uYST_{n_tN|?q@vGTI!iEG4{Z>IU zb+M+TJx@(-6Ivs?`FeD4%S0bql!Urxt<_gAm1@$tTMp2r*bvAi%lwgT1S>uD3LrE_Z0!HSQ9&501agCVM`rmC(1 z?ME9R+7ZfI-_p>i>ruIB-LcZFW1S-UVYG94j@lXwb=ZcKMQh`jy{M_Oaj`PK2~(y| zte7|frBuEFqnJvNf-^zetyr`uewTCkFlzD;@+23X;Bm5bogr6D(fH=I5AdbW1rl|XBjRFgbKDfPdN5efyi`1hHvIbPbHs%jurCgv zz@xRX5$*%BvAIa+CqfPuS4LQEeGNpVr-KGtmv}vf>XK36E!9o+P%~^GhD|nffA9?~ zq8cc**0(lSFB~lLvU835omt0EH7c7qGNpFetk^qYeuR#uWl0o6gC@8jjQ^vH(3is8 z8sm+uYO#?tGXh@@ixyH>H68^i0wXYWG#U>D)~tFnW!!4;p+<|eXf+5I#xWj-MyhIP zl~J$PU%`xY#mgj4<{FKaEsKpsYCh%p|7ujY8kWcVy0a_Q*kECGO(l9kovwuR2DCDx z6BC6(&TXi#mNus(^!j9^EP;PQO*KLJQLpgJsxj1OjmR*#0ljz)I>cyY(;{5em6`+R z3yBI~ZfQi(DCjO)(t>(SKuvH2XL}`QUDY-(M4yXcG^~pi3A>AuWfMJ5LMEkgY8e`# z8^)kP=|v;41Hc@J&H?iqZzxJBo9OB4YL}pgRz~4fXI!JvXiO-n4#iF3iC)AGDDM(9 z0^F7@tX!r`r?I34*>qvTi|SjeFxXPHr0rfCDoYTP*sBOQmY6c77ElJLK#Pm+5aT-~ zQsN9ygH)Y@yirqA*9s4nB)qc5C6!B~Xtf-AG@#Kls_QNR8Y@A}-kYiysg7Fey{4gQ z3C=}KCa)(>)X6#233|$gMltqS4g} zIjYw68EvWLN(p%^zyur#UX!kowV4*q5pr<16!M$2G-VB)wM3H9*9|7v~0)#m{IG}0kEQh3u=KGdrw?UV;=;A zscjfsyx3q*)r>m<^(_OqqEe5j(x(!Osd@megVikv)Dlk%E(&d8s?Z&PF?bzfqok+< zYU{_OAhuORik4VF6+vMJE~=_Jvb2LrJxwI#Iuem;87M_fMyzaVtW1VbN2=*XVZRu0 z)K)K4<$@FlELetWz@S~h;87*e)LPXd0|>-qHitVXEo2U=oi~jMr-hhe;VHfW5ScYG z1*%4yr?7}6)k9laV`YmwB_B|xFfB_Yyb#8S08??pJ5#cQ&i(E9&sTxt9Qkwb?fDr_ z$A8)8Z07^#?`gRGJ?-l>z`joV1u;Ao<=?Mg4-We_ZO4gK*oim%w$2~&Z7K^BW*Pp` zaae=EfLEzu5#J`B>&Tz~HyV}~v`5ZRu#JR2rs0`EdrAxry3c8Ne$YNM5&pk%_^%V; z-^StV65-)?iWy{%;G``n#|#Za*(wDFerLw{6CE5M zhfNn4bigz7_(R@@1@;gCe5%3wIh)4(ehkiH|NWTT#ya?-6}6#$NO@!oK7{b8M zCp$Ney@d#KHafSAy$t{l%}^9(g2K&X&C{Km72ZsZce+Py`VN4W4;E~k{Ff-sl>0xTsbyquSiTJ z2#JY5XWP(EhoYpP4*hwlK z$R80S?fXW4JJMbrre2*tA`u(j%(j2zS0jV=H?xF@V*LGx>jC;lBIw7FSC0zXkwnE_ zJ?b8Sdd5Kbe)ImJhlct#Mdy+hI35~$$*`bZ6QcvrCBrrWv@8*{Y1npveiH*hrq_pk z2+%Ewpo@oJF&qz9#Xz8S#qhfTdMXig*YKSHy_5*rIsBF3zD=zn6H5Nfh}TE>HeKXJ zpC0w)6tU4eIl zu>N<09|RHmAoyWWnZ<{}j{p#Ski9MNXaLs#Xka_@>-g`@!bkF%+4dl}HSm+dNpt?erNwhVc{ExTl> zD)J>muNw-$bwlqQisUGi^iJ8wWxh?%c@^`Up__*W?TQ$N77;h+Q1Eh`YvNp<{(icRucbTh zr|&6M0`4i@FhoEbhFnu7pm;XsTbB;Ew@&h{sPXc_@u_?O>3nk%oaKY*yV4>1F3I&> zHE{QrBEDZ(eOH~p;J*%fZCo-yjMqZY>ZYuJW?7{hoquLs6+!f=$ioG=UDNUVPPpys z{8U#0yS~(wKH;UVn+p+rzVPnuxZU0T=kC_vjn2>AAMZg5kN5bj2jidh__!yMKkm7w zm%a9rYG+mNgS`{A-=BPmVQLr((j^T zL@z47zL?n87e8Bq=o=+p@b*Q?RfC9h*PwlJ-#6&O!2n+}c+X%W?-{(IRA_D}-BU^c z^-E*Mb9in1Qg!ok2yN!&&}$*9WTSH^YkvWv*K~w7=0gi8{(NUw_rG^Xh5Q{_K+*H~3RpS9lxPjL~o3d>GxQ&jK6an;6>W8HJ zGx1S~hKLY0Hi~qDlD?^V6wuk&5@;T(9xN4vb%1-Iu5KPtrS!tS@z z=dSAgTRPiU`(4;yPS4i$<7E@Nl2e74S%sKig-Ak$NJTj)1H8R+Ve2FR8KC_W>)d1%q)SIioU^HrEt8T^|-O81h({hsviShKDI^iRgzmP z0NGWD`Bmt%9zXX|p=bJDbBqdY>GwiE6?(ShjS>}NH!Go|GeDD&l0{LnZ;B8jUED|% zH&VonB0`4S1{Ol8u0 zdfrR@07@|=aIeRDp)qDvm2N8ajcz~c(AM6s z>z4Q?2BRwVwm$1wcjut~kGWdg_J#d_r<{PZz5f9nI@tfBVug2o@v8$>=uZPb(xH_l z&+68|p{YvcXjFw{TrckJKNKE?PE>$a0on!2j~fe*8%vHGi;Wx0j2jD#8%v8Di;5e| zi5m-v8%u~Ai-#M_h8qip+x5k7@%C2n9V`;YE_V!kbszw`i~y`G*~likQ56*cT~vg0 zQ4!KbZIJF_PyXLj{jp9_&Ca%1HO8xgWJOI$NMeS9Bq2c(gH+bx{PEvXLWg-_S8W~O ztRl*uRYZ+*u8QHHw_-$rZ-YXEcF`QxhFUsu2(mE7)7)<>`a_XlobzgDqw|NNXNxc+ zd=@w{Ac~6`d0H7BvN`*@eW@cz`JvA*eG)ln5IKm$`JvCxeL(SNps{&d&f}hK#v)#6 zn4-jfF@^(nj41Gp{qNO18o~YFiFI$z6$7+Mqm&XMe;rhLZ;oLgFCzk`R@BeAQ<)1| z&z}bF)ono=2w?xdEhxw6kpka4i}x180Xus^R4i{VZyQUVpxMck4=sR$|0vAt^J8&4 zeC(BF=q`}=!Q$tNm4k~Dl#ufxsKO`iE&dGBJD(N*!q_ilafS$un6NlQu{cAqI73Y= zWH|ibz+I|ntkiGwZz>4dhfY&$#LU-qd9X{+4#zzl#l_Y}R zPybTZ5NIC^tHGa+UA|p~ zU4x%Tj7oaa3&nNhY%+J@OD)bW^8pL9sRyiwEVY>Nko6A&{$b&StC`N%*4?%&8>yTs z*Bs>9YJt&Kl?zK54-tb=~$%CZ~Ltz2cTxA5US)rgS(s`;e}9;vLZ zBP$O{&~}^7+BFw!bapet=_SX#c=TnB*gc4k+kpBG-|g!qN@f{ji^rS$vG>`Lpr4y>@*3lbCpDK|NP zcEJ44&Rz!$_d17N3`!5XU%5)3U%8+A)bLbP2=vE@_n7m!3k`m*@Ss7#V{NG)@U}US zber>q3sJvNco0?aXbH|i=MopD58Hg%dE2r5W0ey@%F8Jtz3o5(aWF4CyB%lk3aS)& zpgpOtZ=DscRMZL=B=uSt{FwcujgmcS-{3&h8=M;*X=%=9JYY`mMtI^L^8GdqxW7%i zKh17A4>RDjhtre-9!`6aDKDnIs#0D}`?E^XS$T5~`L0WYR<28Xh}$*~Av5gf;j}+7 z_9tWjJE(qD+L|=Zy}huADo|J#^uToArq0q%bX3rF*!y;4jIpyX?JLUmRod--DEW5( zX1|hov;Teq?)N{U0gw216R_L=tpdbIr~n8Zr2Gfd?)1Y>?^J|PtDF4~5b%H^1i&Ny zj|uo#5rSb_mUEqdliwQ@YQoL|kKlDg|IcKQj}?rc^L^SHKeV^TkF2wi^*-M2^FQoI zNgwg=LYgrFpB75owUK0d~mGh_E>+=A*KJV5%yID;vFUupcO1?7h zMy@^HTktq;3bek%Kk`9KKpN}4{3}RTK(s;UANjukhbFO25o7;?@Qnpo_PsNOjR|=0B1zw)ZX2ZG0&e8DOh(V5_fT8R+piWkq+U(6#aHQiV8EBW{Z$YO06R zux4?XLSW2DnzMlVdI+)CL%Tx|Vivqe=ryd(C?zXdAbN#n@q}hkp;o) zc{5{eCSq$d@5PFl^FSu#p05{EIQCBbCC7v(dg!p`ItN_01{7dx;O_yb`m4a*!HmSO z+HQ324sHn|WlQk!)RZTK2bpp(cqkZ3_~PtF=bPYmZWO$T0Re7br~j0mLi49|lxqgc z^&*$!q(E~}pj$HmycOk2L67pqG(BvoK%lYhHEXy@pT2Q$Eo$1)P* zZ)Uuo6#oEfOibSeMJB`()abkxIKWCd03F$D*UWdm3H>V+v|o?0dMpFwnCHBo@c}r* zz@Xq9_J2b!g%absa|V;ON{(F7YMIS47d4vW%U8&M2@cqKLtmxn^PQ} z*FAgtG}+!1?bm}JlXj~|`(uzek~V7M^7I|)K|3vRa&&n*4OSWtk{?b_r1LP)kEA0! z4d<2gchm8;_W2u~cWLbNoG;T4D*zSr3L-YX`K!Wg3`*2j(mzjP@j390WP!R_7utyD z6CP}0HXVi0BlK9tOE5L>BfL_NVuVtUOgvgW3UmF(Ib>hW!QR!*n+}@Oo6cK~>MGxI z_7SiT51uH%2hOKN{M7k9hHnWZK;c@B|E_gzq|-$uLJUfPx;eIy;;fG1LO| z8*jwreCw>ku+O>IeT<_&<*S^FKroT&4vGOHI{C_!JsVZ#!eg*#7=H1Xrv;L3{NI(l!jw>{Sf(c60Axu5&o^y)erE zUU*+vDQsW(wj3%XXLpVQ?9N%u9m&;^wHTFn+4Wsf!NPUsRasE`RasAGOL?8AvtP-E z9$v}5H3#rpK|;ZWiniS(T8h$4PpAz7q5 zT}h|c1uxB76Xveun(&=ieuwH2+Qg9MIKK=34lg_2$;B?`VE6|@<3DGo^;Ziz`Kxsc zpIy4e-e4;gY_Kt_n(N$cf2;tK#reQmZNmUo+qbIulC!~n-=^xlpR?0_1H*Xd4fi9? zF+OsC(Q}bs+)I7Xr8bJ!kB+67yzk}xN#z9;gnj}&hQ4BGhH)ao-Z0Yj9<6eaZUvn5 zu;KnXPl(~kEY}J5G~Bxr<6Vq2?_&+$`i)C4sPafT(`GxfXFC-O)J)NN+**$r@fOU5 zoJTNmlIae*2Iofy^Oj3pLk7#v7Z}UqNw4et&OQDuehj&v_d756U-x57vEMHPZo}D* zU3GM@d(z=?E(|%Bhc*>B>pSl5gax(>2g#nKTGF}%`>|L%eXUfsC1ptgucsiRz!A2oSj{FcXhUP|Evf0r*7+meK#x{bBjv$fUrgC@fNll zN;O-x*Qhcx3+6W&t23}0cSDBZ+>miohQm!atd?f1%ycfv{7oiz+SX(`*J7Vd?XC$Y z?s^@{!fr36>9LbCtnMT1F;VQkqKC7y2L$6LmWTsSAG$K+a5IM6FDoM0{CK;IY<*zk zV_SD@du;73n;v?HgPR+M^GNPfx!70Oo#$}TA4~q*x?weaRd46{zBe9&735d@IiCz% zSt1Ma(Cmg%!&ytjIF4*AsFWLf-O&q67E7@j%m)#$Lb|RW z)<>}}c75?D1F__ZWnEdtk`+!aZemJ7T3dU+*;`gb@%WR9V`+IMmNKy=*X4&USaICh z)o`Bf^IjiW?85_7$2@opmhsm0Gn^FzE*>E3ZCvuga;q$z$*Nf@tM5Cl>MYAVd}ig& zGG}wygJsU|@R*GA2Ry=pdLDZ1P&^Q`c__~A8Mbn`!{?f?>@s}S2xrrX%_CqbBQ6`^ ztQoOsggo=bN3~uaDGzg@&PTpCQl8g>nU4B?lsv5jiATRU+TmkKMh@IVtR*^rr(hs_ zyn!ADhb65>hlEv346&S@f#(AlUhNGzpJ0sTtjM@D!&#rPF+&Df1_o9;{m%0kgkkgj zlaO<1#`+9rBSu-y+ZbaxpJc4hl<}3}?BsB6OJHljaGv+S;>U>ZrU0IP`Yqz$2Yv}) zxO8dIxjcAf&{-S2KIp6u-WkN;`aW!zzK%zs9FA8wIN6r&aLB>Y!@+ciLj%LPG;3QJ zj(Tl`o_I~zxe4x=?)PBW`5HdhaJJ^IDR8zGyi|aVnB1pxWddn9Pv!kF&v_q{J}jtp z_4h-nc@qb{1-c2<}J2?=AQ_Mvd(1um;qxtPT9} zfLlh`Sd${!1P$Mr0_PZn_y#_moA|3k#02o96$+~Q2E$k?@OH+g^Az0s%U*_b#--5N zBKUd6`V@RVczCq(zN6`QpZ-{fIDdb~Mf!&{{WMP?+(rIQf@ajJn-S9RMh%~;@w?$} z8ac*Jt$p1b2Q(5DSR){ z@Ov~I-_4cZbsD}Vh5ns@A5Cu$3x0vIZ-vV5J$buR!{1JUd-8i9NB87!%dU7lm}%r1 z`>~Un`a7CD?*pIp(#Wv@e!3V2PZ%?e_QpLa_Q0dY@UdK@G0J=Vhf~LZ-@#aaejLV7 zSHN?UK@vN*4LDCgI~smv8}OxVz^`fpzNHQL3vIyP1HAAk@_z?-Hrid`3YF;9;}sY_ z6&h%~8t!FHhabsdyU?GCeo-fM)$s6W1sH?;{Pn^8XnN?c@i$FSc)v%Szp=Q7!8g1@ zqOm_G2!1RY3(OOE*x21gfj-hH)r4Ew#%^s7UWT(Ye(Gnq&lk9d6~C`-1Abo{@Tb~< zzbWvrF=M=ujcN_5G0!Mv~P=s;#pZsRDfxn;) z_&IIBF9)3UWvtMI_3Z}1M|&Ku02gZby=~BW5^&n#iWLg6LgT-p>7;(;{XM`7HBRE~ zQ^8M!sIU(m+0pDUAMk7|-|(+U&eN+6e11(bo0Rm#3GC!|VjK7!eMjdzU+}}m&Xjs- zZiCJx8vjgf&xp!z69QrA?UNN6pl@rm-p+!;MQ&Z(K{hoA96ORR0*t%p1 z-?>bP$>BjssLAj2Ri&Q6pk2`9{+I43%1{BctzM&rjF zj5~2k`Ee7XgNp|jAB*hXyUl!!IF?k357_dsi5P&mc(G3(Vvq!my~&;g#qlS#3##yb zH7W$}w$|3iQt|oP)FWDAX_^fWMoEH2%jZnRYv!JP&Y3!a);WhS=|U{NKAnWFURuPd zISD83D45c?t_Ni$szQxuwHzCv4v9#jrcP*4@9K(O>+JeHYI(Jm%E8eBN%>L_x`-tX z5mhd3iUFk9`tAB8pc48h?@-6A5bua9q@%rQ4xxBlP!dUb={E_Y@*nZ;xSU^+ga~qS z0#6bU#~8o@l0fhoI3-Bcy>XO-J7`Et8k4f593r4Z;=AzbWox{lju-#6HNlpX4zx)k zBr?_|fgldfHCv4HSzs;d$e>vIAQ&SLcj3#&QJ&tQ+6j+AiQ%wzl5`Y%EnL07eiX#S z8dHZD!IP@9mXhAvk7Z?p<5?EXT5EFnSK_I8Ntj8}#NL9}LQ-p5LOI1mpAr!xa71yE z-&T*SKH(UGI8eJRIcY&og-N0-+K;;tvPwD}NYOds^?G&gQxc_U6pcLQ`VtNeieZV$ z=ogOa70?{K!#ZL)q?yS9BS|2ff3*|`kksOg51gBnl!Wr&e-bF^w5(W4Y0_aLF|gvu zqY;uoN)3ssG>V&-Ey3wJxTBHDeX$pkuBOo_uE*(T#dycQnB7!$Q_Hdh5Z`lOH~=Th zDXj6wmE-WVMTVd+uEZG&#S53!gN3@|=mkl^u@%icAuBNwMOssJT_rQczT)UySpKVlhQS_4F!-7X~Yw1PNj)JDP!k z4jBhP;RKRmrL4-P%KAkZkLakJcj6^2SkP2`wwEF&TIdvLFcFNl6^}RI;0_*Vpi!IZ z7pZvSQ11UJ+!47 z%DIo1f1l3p-G^z0EF*R2<;SPi<;QhJX7TcO&knwA^&Qf-@7;J{HgM5uyG{e@s{WqK zSD0I}y`1Xq<@fHVwvoTFpTel~8kQ#G<@4^p()p9Q7{+@86vo<~$thkw4|;AJ`IpRA zm|W*g{H5k+JViu7ufX@u_}i!ZBZ|SxvwN@q4FKlZE`qV&uqu`RNw4AQlvMsy@6q!A PlZwIb@{|lIQRDvumP_Zg literal 0 HcmV?d00001 diff --git a/tests/test_config_debug.c b/tests/test_config_debug.c new file mode 100644 index 00000000..2bb07e69 --- /dev/null +++ b/tests/test_config_debug.c @@ -0,0 +1,75 @@ +/** + * Тест загрузки debug настроек из конфигурационного файла + */ + +#include +#include +#include +#include "../lib/debug_config.h" +#include "../src/config_parser.h" + +int main() { + printf("=== Тест загрузки debug настроек из конфигурационного файла ===\n"); + + // Инициализация отладки по умолчанию + debug_config_init(); + debug_set_level(DEBUG_LEVEL_ERROR); // Начальный уровень + + printf("\n1. До загрузки конфигурации:\n"); + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "ERROR сообщение до загрузки конфигурации"); + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "INFO сообщение до загрузки конфигурации (не должно выводиться)"); + + // Загрузка конфигурации из файла + printf("\n2. Загрузка конфигурации из файла: test_config_with_debug.conf\n"); + struct utun_config *config = parse_config("test_config_with_debug.conf"); + + if (!config) { + printf("❌ Ошибка загрузки конфигурации\n"); + return 1; + } + + printf("✅ Конфигурация загружена успешно\n"); + printf(" Log file: %s\n", config->global.log_file[0] ? config->global.log_file : "(none)"); + printf(" Debug level: %s\n", config->global.debug_level[0] ? config->global.debug_level : "(none)"); + printf(" Debug categories: 0x%X\n", config->global.debug_categories); + printf(" Enable timestamp: %d\n", config->global.enable_timestamp); + printf(" Enable colors: %d\n", config->global.enable_colors); + + // Применение настроек из конфигурации (без переопределения CLI) + printf("\n3. Применение настроек из конфигурации:\n"); + debug_apply_config_values( + config->global.log_file, + config->global.debug_level, + config->global.debug_categories, + config->global.enable_timestamp, + config->global.enable_function_names, + config->global.enable_file_lines, + config->global.enable_colors, + 0, // CLI не переопределяет уровень + 0 // CLI не переопределяет категории + ); + + printf("✅ Настройки отладки применены\n"); + + // Тестирование новых настроек + printf("\n4. Тестирование после применения настроек:\n"); + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "ERROR от ETCP (должен выводиться)"); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "INFO от ETCP (должен выводиться - уровень debug)"); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "DEBUG от ETCP (должен выводиться - уровень debug)"); + DEBUG_INFO(DEBUG_CATEGORY_LL_QUEUE, "INFO от LL_QUEUE (должен выводиться)"); + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "INFO от MEMORY (должен выводиться)"); + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "INFO от CONNECTION (НЕ должен выводиться - категория не включена)"); + DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "INFO от CRYPTO (НЕ должен выводиться - категория не включена)"); + + // Проверка работы с файлом + printf("\n5. Проверка вывода в файл:\n"); + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Это сообщение должно быть записано в файл: /tmp/utun_debug.log"); + + // Освобождение ресурсов + free_config(config); + + printf("\n=== Тест завершен ===\n"); + printf("Проверьте файл /tmp/utun_debug.log для записанных сообщений\n"); + + return 0; +} \ No newline at end of file diff --git a/tests/test_config_with_debug.conf b/tests/test_config_with_debug.conf new file mode 100644 index 00000000..c82f7e33 --- /dev/null +++ b/tests/test_config_with_debug.conf @@ -0,0 +1,18 @@ +[global] +my_node_id=0x1111111111111111 +my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb +my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9 +tun_ip=10.99.0.1/24 +tun_ifname=tun99 +# Debug and logging configuration +log_file=/tmp/utun_debug.log +debug_level=debug +debug_categories=etcp,ll_queue,memory +enable_timestamp=1 +enable_function_names=1 +enable_file_lines=1 +enable_colors=1 + +[server: test] +addr=127.0.0.1:9001 +type=public \ No newline at end of file diff --git a/tests/test_debug b/tests/test_debug new file mode 100755 index 0000000000000000000000000000000000000000..14fed700e0cd9ee8d8f5d9a162d702048c6eb034 GIT binary patch literal 41192 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 literal 0 HcmV?d00001 diff --git a/tests/test_debug.c b/tests/test_debug.c new file mode 100644 index 00000000..4ba0d855 --- /dev/null +++ b/tests/test_debug.c @@ -0,0 +1,49 @@ +#include "u_async.h" +#include + +/* Test statistics */ +static struct { + int tests_run; + int tests_passed; + int tests_failed; + + /* Timer statistics */ + int timer_callbacks; + int timer_cancellations; + int immediate_timeouts; + + /* Socket statistics */ + int socket_events; + int socket_errors; + + /* Error statistics */ + int memory_allocation_errors; + int invalid_parameter_errors; + int race_condition_errors; +} test_stats = {0}; + +/* Timer callback for testing */ +static void test_timer_callback(void* arg) { + test_stats.timer_callbacks++; + printf("Timer callback fired. Total callbacks: %d\n", test_stats.timer_callbacks); +} + +int main() { + printf("Before any tests: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + uasync_t* ua = uasync_create(); + + /* Set 5 immediate timeouts */ + for (int i = 0; i < 5; i++) { + void* timer = uasync_set_timeout(ua, 0, NULL, test_timer_callback); + } + + printf("After setting 5 immediate timeouts, before poll: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + uasync_poll(ua, 1); + + printf("After poll: immediate_timeouts = %d\n", test_stats.immediate_timeouts); + + uasync_destroy(ua, 0); + return 0; +} diff --git a/tests/test_debug_categories b/tests/test_debug_categories new file mode 100755 index 0000000000000000000000000000000000000000..54de331fab65c4aacfe483a4013a03bbfcc1e244 GIT binary patch literal 49408 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

+4iA?eipN|a5#nTT$ZaK7-5`@%)3 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 a$NL0ph%KLn>)!Yud`NIz7Q+w&vi}Ek$vM0L literal 0 HcmV?d00001 diff --git a/tests/test_debug_categories.c b/tests/test_debug_categories.c new file mode 100644 index 00000000..ebbae621 --- /dev/null +++ b/tests/test_debug_categories.c @@ -0,0 +1,117 @@ +/** + * Тест настройки категорий отладки из аргументов командной строки + */ + +#include +#include +#include +#include "../lib/debug_config.h" + +int main(int argc, char *argv[]) { + printf("=== Тест настройки категорий из аргументов ===\n"); + printf("Запуск с аргументами: "); + for (int i = 0; i < argc; i++) { + printf("%s ", argv[i]); + } + printf("\n\n"); + + // Инициализация отладки + debug_config_init(); + + // Анализ аргументов командной строки + int debug_categories = 0; + + // Если есть специфические категории, сначала отключаем все + int has_specific_categories = 0; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--debug-etcp") == 0) { + has_specific_categories = 1; + } else if (strcmp(argv[i], "--debug-queue") == 0) { + has_specific_categories = 1; + } else if (strcmp(argv[i], "--debug-memory") == 0) { + has_specific_categories = 1; + } else if (strcmp(argv[i], "--debug-none") == 0) { + has_specific_categories = 1; + } + } + + // Если есть специфические категории, отключаем все по умолчанию + if (has_specific_categories) { + debug_set_categories(DEBUG_CATEGORY_NONE); + debug_categories = DEBUG_CATEGORY_NONE; + } + + // Теперь обрабатываем аргументы + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--debug-etcp") == 0) { + debug_enable_category(DEBUG_CATEGORY_ETCP); + debug_categories |= DEBUG_CATEGORY_ETCP; + printf("✅ Включена категория ETCP\n"); + } else if (strcmp(argv[i], "--debug-queue") == 0) { + debug_enable_category(DEBUG_CATEGORY_LL_QUEUE); + debug_categories |= DEBUG_CATEGORY_LL_QUEUE; + printf("✅ Включена категория LL_QUEUE\n"); + } else if (strcmp(argv[i], "--debug-memory") == 0) { + debug_enable_category(DEBUG_CATEGORY_MEMORY); + debug_categories |= DEBUG_CATEGORY_MEMORY; + printf("✅ Включена категория MEMORY\n"); + } else if (strcmp(argv[i], "--debug-all") == 0) { + debug_set_categories(DEBUG_CATEGORY_ALL); + debug_categories = DEBUG_CATEGORY_ALL; + printf("✅ Включены все категории\n"); + break; // Все категории уже включены + } else if (strcmp(argv[i], "--debug-none") == 0) { + debug_set_categories(DEBUG_CATEGORY_NONE); + debug_categories = DEBUG_CATEGORY_NONE; + printf("✅ Выключены все категории\n"); + break; // Все категории уже выключены + } else if (strcmp(argv[i], "--level") == 0 && i+1 < argc) { + i++; + if (strcmp(argv[i], "error") == 0) { + debug_set_level(DEBUG_LEVEL_ERROR); + printf("✅ Установлен уровень ERROR\n"); + } else if (strcmp(argv[i], "info") == 0) { + debug_set_level(DEBUG_LEVEL_INFO); + printf("✅ Установлен уровень INFO\n"); + } else if (strcmp(argv[i], "debug") == 0) { + debug_set_level(DEBUG_LEVEL_DEBUG); + printf("✅ Установлен уровень DEBUG\n"); + } + } + } + + printf("\n=== Тестовые сообщения ===\n"); + + // Сообщения от разных категорий + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Сообщение от ETCP категории"); + DEBUG_INFO(DEBUG_CATEGORY_LL_QUEUE, "Сообщение от LL_QUEUE категории"); + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Сообщение от MEMORY категории"); + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Сообщение от CONNECTION категории"); + DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "Сообщение от CRYPTO категории"); + + printf("\n=== Информация о текущих настройках ===\n"); + printf("Текущий уровень отладки: "); + debug_level_t level = debug_get_effective_level(DEBUG_CATEGORY_ETCP); + switch (level) { + case DEBUG_LEVEL_ERROR: printf("ERROR"); break; + case DEBUG_LEVEL_WARN: printf("WARN"); break; + case DEBUG_LEVEL_INFO: printf("INFO"); break; + case DEBUG_LEVEL_DEBUG: printf("DEBUG"); break; + case DEBUG_LEVEL_TRACE: printf("TRACE"); break; + default: printf("UNKNOWN"); break; + } + printf("\n"); + + printf("Активные категории: "); + if (debug_categories & DEBUG_CATEGORY_ETCP) printf("ETCP "); + if (debug_categories & DEBUG_CATEGORY_LL_QUEUE) printf("LL_QUEUE "); + if (debug_categories & DEBUG_CATEGORY_MEMORY) printf("MEMORY "); + if (debug_categories & DEBUG_CATEGORY_CONNECTION) printf("CONNECTION "); + if (debug_categories & DEBUG_CATEGORY_CRYPTO) printf("CRYPTO "); + if (debug_categories == DEBUG_CATEGORY_ALL) printf("ALL "); + if (debug_categories == DEBUG_CATEGORY_NONE) printf("NONE "); + printf("\n"); + + return 0; +} \ No newline at end of file diff --git a/tests/test_debug_new b/tests/test_debug_new new file mode 100755 index 0000000000000000000000000000000000000000..6a9220877a2efa360913278f5062276d6f9be5f4 GIT binary patch literal 46104 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 VE}w?$ +#include +#include +#include "../lib/debug_config.h" +#include "../lib/u_async.h" + +int main() { + printf("=== Тест новой системы отладки ===\n"); + + // Инициализация отладки + debug_config_init(); + + // Тест 1: Базовые уровни отладки + printf("\n1. Базовые уровни отладки:\n"); + debug_set_level(DEBUG_LEVEL_TRACE); + debug_enable_timestamp(1); + debug_enable_function_name(1); + debug_enable_file_line(1); + debug_enable_color(1); + + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Тест ERROR сообщения"); + DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Тест WARN сообщения"); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Тест INFO сообщения"); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Тест DEBUG сообщения"); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "Тест TRACE сообщения"); + + // Тест 2: Разные категории + printf("\n2. Разные категории отладки:\n"); + debug_set_level(DEBUG_LEVEL_INFO); + + DEBUG_INFO(DEBUG_CATEGORY_LL_QUEUE, "Сообщение от ll_queue"); + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Сообщение от connection"); + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Сообщение от memory"); + DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "Сообщение от crypto"); + + // Тест 3: Форматирование с аргументами + printf("\n3. Форматирование с аргументами:\n"); + int test_value = 42; + const char* test_string = "тестовая строка"; + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Значение: %d, строка: %s", test_value, test_string); + DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Размер буфера: %zu, флаг: %d", (size_t)1024, 1); + + // Тест 4: Включение/выключение категорий + printf("\n4. Выборочные категории:\n"); + debug_disable_category(DEBUG_CATEGORY_ALL); + debug_enable_category(DEBUG_CATEGORY_ETCP); + debug_enable_category(DEBUG_CATEGORY_LL_QUEUE); + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Это должно выводиться (ETCP)"); + DEBUG_INFO(DEBUG_CATEGORY_LL_QUEUE, "Это должно выводиться (LL_QUEUE)"); + DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Это НЕ должно выводиться (MEMORY отключена)"); + + // Тест 5: Разные уровни для разных категорий + printf("\n5. Уровни по категориям:\n"); + debug_set_level(DEBUG_LEVEL_ERROR); // Глобальный уровень + // Note: debug_set_category_level не существует в новой системе + debug_set_level(DEBUG_LEVEL_DEBUG); // Устанавливаем DEBUG для всех + + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "ERROR для ETCP"); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "DEBUG для ETCP (должен выводиться)"); + DEBUG_INFO(DEBUG_CATEGORY_LL_QUEUE, "INFO для LL_QUEUE (должен выводиться)"); + DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "DEBUG для LL_QUEUE (должен выводиться)"); + + // Тест 6: Без цветов и метаданных + printf("\n6. Минималистичный вывод:\n"); + debug_enable_color(0); + debug_enable_timestamp(0); + debug_enable_function_name(0); + debug_enable_file_line(0); + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Минималистичное сообщение"); + + // Тест 7: Возврат к полному формату + printf("\n7. Полный формат снова:\n"); + debug_enable_color(1); + debug_enable_timestamp(1); + debug_enable_function_name(1); + debug_enable_file_line(1); + + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Полное сообщение с метаданными"); + + printf("\n=== Тест завершен ===\n"); + return 0; +} \ No newline at end of file diff --git a/tests/test_ecc_encrypt b/tests/test_ecc_encrypt new file mode 100755 index 0000000000000000000000000000000000000000..99ac109a19e854524af00065605cf7e92bc7f74e GIT binary patch literal 208968 zcmeEve_T{m`u_|J0*c%*$!N26s;Oi%W6X*vEw0|tJ2n_8rlqB(kZgzqGiGIq4HRd` zYgYDSt8LxQcH6D3c0WeHaRv+!OA$*^OVLX2hzkBl@kh@0{hWK}2c)}wzOT>s_4@wt zq43=E`#H~f&U2pUInOyPcDTmG8I6YUPrTt)1H~-;wUlfYeXrMjtIF92(48oa!ae zrylPa{&TTbE*ysQBmFJc@(n%0RlnBK!(nVa!cEDiT7J*)O?}*%VT9;#5xS^e_GenX zo?)ksh;SILPB@|8EWO>HVW!rcAsqIsH%G76GnDjt;gI^EpNT)XXaC>ym#f#yepzd$ zXLvxTmv9*F?@rVsy!_H19i4^=dVf9fAC52A#~luJ^ct=!D!l*3;nx+-zP6~a*t6i; z1z9&a(rZ;DZE7#)T_9!n+&!_V2>b$-G%KNeD zi}}ADnY(#c-PWr*S5p~+o9a**`l0r8ltkhe;fu;}d>EG?VveJ7hXPkPBgIM^MYtJ^7Ytx(l0}YmPo1S2f=Z!cJ4V(x_F*+8q2xb8GfGhzn>@l zpXW)J&y)W8dD3&vlRol1cxX6J`47&MPV;rH@eVmp`uy{xZ#Yl-ob#l+&IA8Ul<#M_ z#9;B`13zK@umazg^OWxYD2P(|Y`>NsE^jFDxXTRFrS=4i>d-~j&g~e1hzqAw?vlh&pUQk#(v#9XV zeCl@A+!6}P^4*46Mf1w?QKYbJ-YmO)dRf`5nZ*UXOvxjLf`>~B-T4M;mzMsGhOsW^ zsL|7h4ZSJyWg8y(ynbl(JNk8f^mEwI8w}H(c{$SoR{jHpW$ygayqwWR^NREHX5L>! zP&_bqUa>xc=~~fq79^e!4;~Q5|1;?!#S)O(%aDjr%cHpVrOgxNevQvCf4r!$u&)8- zcj;*>o_PUFv6msJ$K4kfCKH!CrpMEGd2lMw5w)?Jmyb8}*5jUEQTaH-g^1Hnn9oM& zgP0(G7U=t8INh(y8-w+IFPv_eqLt6=k=~}UM?+SRbc0JPpXAp=Eii=R<{s&}%d`Tm zYqXH~P%OV~i%Adjb}FgHq^D|C`M;ep=>uZYyJFJQW75OxjrI}Z3E`jcx}x+jU#C1- zZzmDoI*KXTkQbm}AgQxcONnHyv-h)Ku9M}Ep< z(tAfz4Th?ibX`>AxyxhHQ)0@$6q61@jr^>PNf#oi2E&^%>0uhD?Dt~Q`^A+1FeW{g zANym{b<*Uu8e`IRQs(K+G3gh^wEw@K|BHeD#lZh!;Qv<)92I_jO737G4nq}|Wq&g0A=!OfB9J(?M{|bY$4OD&(% z{8uzi8&vSm(Ku~P!Dpj!+K_@zMB}s(1xurG+JJ)hN8_~d1Sdq}wBZCtMdP&51cycA zw7~=~kH%?Z30@G5(}o_5i^ge#2_88c882-t!9CG9Z79K(NIZ1qYws8g1;QZ7!0{Ej za_2v~$G7x|Z|D)P?-8%*5wGqMf2T+MFFoR~^@zXFBfhLh{K+2iMLpv4d&G--#0z@F z@9Po2t6O}ctX%kZxxui8=*H%kl^e(n~bb85La#^esx>Y0$#USBX&7*qi`h}`x0vmJD@ zP4~%ckL>-vGdIs+^V`+|FcH;47e4p4!Eh{E3F!f($V$S;6xh4m7h-A6r*AB+Kh!wP|z}u21W)eB|TE zHh8X?Bzs3*PM z2aqo2IoUCw_*;BWlG)cXJH;_Q(YAv)hI$z7E*FLBw1F;mC_H!GI-}Z(410U&_ryik z&{~{&uQ*M-H`sY56cQ(kcZrk4yh#(~B?ks$!e`4q)0Klc%oWyjBoG0-Z8c-AUCJu! za)jz+2>U?PbO0i=I`u=eBeU%?JLF>f)Zc;~Rr-aB43tEnDJbMpR$DWv;xd$Up|xhp zW7}QqxQlhDS$d66s8?m=x`kBZQ)@2On8w@Q=3=LK^Bw9fgLqIl z(V%}m4dQ*`bS#9CrBrt=wN#5(1;(XLN6f{}YLF`$c6qLJ?(zYV4eJNr! z)J6ts1s?+m6$l-EBwj_MV?WR&kc#3`t{j3CA)|sv)l;WJAt&3G!#Y1ALD|Lj)KDm6 zV%7}fgY|gymM%vJYXo2%; zBoINVzompv!Mu_1(j!Q?n-bpA5mO984pSWN8EJo} zw0DsfoQaC+4X6>}Cwv94A8p6^GGM1sJb>a@RF;_4AX(FGe)Vlstn@Fq%)t(@2AS<- zp95j)RY+wWPFAm`;48;|%zZVMjW5rdj=4|c%gOT~mY*d&Q&W8>Lm}I7neAhr ztMB65QF+j9mwh9wvd?WzviWsBzyfT)lXW@S;mxOx977P!{H|fK3^B#RBw5@QG|KmT}eXAa>U9aT2wq z+|UmYt1G%fA&KpC0;`YU)9L*wNvOO6biW|}D_4fx4zTWx zC};rUPP;SEX$uya`j4Av1Q%8#M^+xQfB@7-X8}fUm+@l1x9x15dP^>{Si%aFXkYgs zdI;6UG|%O;7@o^EgUSZWY=pHZrOB1Yg%y4obF!i~sadEzftdwhLd7Ba@Qxftz&Y6o zq2iyEz;>y3o}g8Lwfwm{2A|k&HUhgjOvNsnL-fo7HQJ7@_QIgS=dCC%D|0Z6B5RV` zk1CBUN3)($Im!q0L5n>N3}X$@$a1;S!M^Ts6u>jV5EBdJ1AymG=%lCZ*r9RGz=|-kuy-tGww6x;G-2N%5 z;9Wbo8>`pk1-1WpggPwo<~5k`rtGm3aGr2`n)!#PuIif^E&hy ze2*BRuWx)$s7h(@eW}DU;&`Kfq&~R(%b5gc>FlNgHn!B}uOI6+2>r$`sOwIjQNBT| zzZ-=v;OwHZFJ|_rFmEV+=bQ1*B5Vys)=YdAX9^i^YqpTF(3(qWGvvyHLIrJn<&T#d zgoO(9_c%f z+EenZW@<*5smTggAn!vuRK1QTk3o*?uJmCqABJg21}_!CbLE2R@wU=Zp`l=uW(G8Ammip`^9*TjH&mWn+>4drTd zokJyt#*|2*!AwSp_V>}x(4GxUq6V_40fAsDKGKbO>-0SdyCei2VzW-RMVyqwJ}24v z3u5Ki1JjM&XxY(767DFI%=XHxk}!{0e=tk=N<$|i8TGPvcc+X!{X$eGBfIQfYV9H& z6p5bpCA&lTmNUMPE7u8?ucC3MGUgKA+8eECEk{|D@_*Y}F$Ji_D(a+M^A`$G^o7 zX){KvvhrCQ8vLj)Rbjj@^}}E5?JTB54FJ+$Z0l?-F5mk!T1f+2VrM72e3csLC)8c2 zc+cGc!6QQ<&}IDXn4}!`IO!?g(F=^SZ&G7a=$b4m`K?n)+aWRg-n!+o*Ok&`2;Pl8 zxP0^>K0@tHV9FH$2HzLA@Aj9~V;$6hW`BV)#)&P5)r+9Luf@C= zN|TwwG0NWO^T2k;uu+}#xd3n|&MpR|Ytu=- zlo_-Fk{8t$2Vs$7q%5$6%c&BpZC|g!hi)Nx^8MVGa24`kr+E}+kX!JHt*&qXQXY=H zgohmmk#@r%E$zaq5z0!H0dtS!02izagtJZHuZXu52&K(Ne0*CV?ATWzocadgPRJ8= z-u*_;P9X!t=Qqnhdu>C)o3CiXc36HV`P!H80#adqxs4079}_tcNs_OCt%YOMr6fFy zG6l<7V3_G5*;{KAs{Mgrl6ZYeIct>dM@sjeXeRSf5tRby(nCDF*2Tk)9Z0(}S4$f? z1);3`j)t=w>Ghm9VLT_a@id3WvmWF48sh;s*zpC%6M&*mv*r@fiRWzfeJBwZ>-Vd3KegF1l)&uW90Qf`bz zj?0svS{cwL7ywZWcayAHqy#5q0R2Kg$!el92j&r#N>(eSr15EA$LAlKji1lIP;D9@ zRJ#Tk18wPn-R9&*io|O{Twr%nawFpuVUoS4%$`lYu_+$|1(@I){Al}lZMee7F?s7oI@e>h5bTbBGgCDx@@63Bqw(wKz%hG0uIkbmLDRfP+ccf?{Xy{k^_5_T*-S~ zzS-7n`j(TC7Z9qyq^7h4%x#mLyi=&&5=m30S!cMCTV>Q&u01ptqtr{glAHNwa-$aT zm@B#7g#pTY#u};LUV!aRBjZp2j-USi_?gM);hFZ$FJLWqt=6ia~_dPM+lVZN7@^2bc zauc6RpprVdQ)X#^cDptZo-{4ydveV8gqZJpW4@>8-#T>w6F@;3B8;bK~k)Z(4= z&jd82j?gpn^j2r7ztK}BQLC4RBO3H~ap)(9q0i&c&sI;&(uRH)LBBr|(V(9khMso; z2qzKr`D(3RZZh@rJ|YDDWP%dEsa#N|F6ZdP-PbKK5 zM4|t!`Ym!OaVm!%Z4_DWrho1s=>2-;G`-aa)pzuid#Tkwg(DjD_i^Z_hoPUwp)XP& z(@Wh)&=*G{8uZh{&`%@i@74R6tB%pjO{aeBh*0))0(4pw`eJoZxEO~XZ4_DWrGM@t z=;M&7tuH!g*oP#(ENF7)sc+0Aj(L;lTMVW-IZ5>0ks`8AksTG;Pa-?Znu)Q2YZAKz ziv;H+9_i;y^bj$@gZSOSOfYa%H{kO)XOUnv7S=^fRH!~fRs)N~T-JdO-VREwqxFew zvz*+8if573LP@UVPAyQi1q==h)+@6vEsDrt&NjhAIp5eo43Z)32#;e^bR}<+d4Mi< zgR>y-IR9cz#JdK{$tX(f3(*wt$~~yzhaC@)G|3nS0XtY`d7NXItTFu3X>SlJH^8dx zLXk`t1WuXcVVpBTX&}ZaB^bo2?2rqbbAOa`c4wO|SlO~;c_$ejlR3lr|DAoNfqmwI z1C~uU7#zOWXkQV1FRVh~@V!81d!p~PHxWpz6;!xiV%yl)kS;{_RUnw|V9h|TP+boL zDM&~R9555^G4}^S6uYK_1NGwzM?uP~-yLjut)_ z!BLp{`!>j!VR<@YE0^^%3}{F~^{uKEjSAIW>OehA|{Ux?o#*Y;r2-0T6F{eo(QUbEDrl#N>!d!P(RWO{!#1&d%Jgop> z*hI=q<+UHv3zX^MgFNJg;&G_cEZK0F(@9*@)`p!o1;2gqn`&#UascLA)SLB+!}Rbs zJnXLxrvZJWsu$^5f*vOFkcL~Yo}Q*5>L?F26ztQ!;~)mAJM{d`dbnQCqv5MHT7-`( zRU1`bKB_nL(tpsy=kHEa%}b8!AwKi+YD%ajYKR#zQ`-!}X*gJbcUuGQEO^ zH&9Pc)58=kKTaApi_i9%soL<5>EQt$YO|~E(i68v6Z@)7S|pwi~q^OB{e&`-;@F#f4%fyMcuRuES4%-=r?U=)MK(wt@x9a6K>R|&9 zAB(5u@(I2jwjXG%y%$NYS6A`mANh_w7w3;_(_@x!5B)K?6^6zh`jGB(fb*{<2R;O= zAbn^-j6PIhC8i2~(L*IlhYgaeL;^m@OQ4)rj?s#U<<#FzEw)O&=T;)y=OcDaia?gH zoLIK+6^&zW0e=?Ry1*f9^>rfq5_}h1eS13FE>yp5eG($SeH@3g;p~oNnjl)^Hr-4NnqKCA4GhWwH$F24><$hB{`EfQ>`UPiiuWq zkJ8amOz!WxN0shTuO8LdK6c@^tqe&q(WTv^Pj`<#(LGwJM{|MgleEXobCetW#Dic$tPTugHq>8-loYpdVmGkj50X(;rks)KmjV9Npg)$MP123 z=vi7vI0EAG4i}oRXF^7U)s1U-ZP1vYDsr(1DVc;qSMq)>Xwrf`8eW^I3KUz~bQ@jC zwR~UXu`VucXIJPF!A<*(2qQz$|cMT3_q@{%5zK@m;`$jPP1t9s5H@@*+cgxjTP ze>O)=3|LI$>p^g+55X4c0uUvz3CJ`Kk^|4BKZ|GsBK1NmWb5Hp5ok)Gbw+}9*bs3D z0#YZ$Z5As*A57B%&kVnEaEjs+}h!LtufTC<9 z`_C&}3)QFil#u#;4*8la7JPbez7ZCuUCFw9eG;c~8ZkAId_5R54!g8Y7$jnI@miDi zWDJsv*)h^}g@$pUwwzEqfS;V;xRfNUDQcn~l_@$y?cztvP_HREM-3txRcu0B5L3U( z!{pd!TeE};3ykB$V2B3^)fJpQ3Dv7PKNG5T%Hf0x0lb3qAq{XI<)jwPKtxS2vk`AQT&oF{v+3pw(Xv6p>!n%jvC$IL7t5G0^LddiKjcuq4Ts?#nevzT6bw zlq6lN{9_-xC^1b!MB`PG~#W7Y_b=ZD$v@R$l3UMBU-bw^>`o zzev9U&0>iy&?bav3}j2LhsozqcX}-0iFbBWdfbR|r6)KCl&OBcSwA;|uUX8I^f4SA z{xq?2o%Qu z*9mSxf=5hmwyhlpFytQ#MH7ya1Br9Kk?ukNSIh4@`*>xUgT4v=wq+ z>11@I=)M#O&F=Kdb&!i-lqN9@en_4~`(gJrIBy_|kYjuzHUnx%fm6brL6Ot;6IiV? zF-VS57pv7tu))PrtT~$f+HBjQUYH9}TKK?iy_L+|7MBl33M}sQ0x?jZ)P8x?zRuQR z_$d}~m}y{@bSb*5GfMXMGMQ}ydOW*zxQOy!w1CUexqlv#KxMng9LVy%qAzrdWQk8blxIP zcCyV8w_I(SG<}(bE13?--_|4w@(24J*nJN9)-1t?#MT4Zw6%VL)9A__?t~niv;E3Z z`F*zVe0@&k4$qCYmh6h-?(6jR-g6#zq7j-$*4%9-4dRkBp|ju;PhElnOK_-3-vYix zUyj+9uV9q_Vw;0;smU* zy^Z9xbch}tSrFYo^gK`X{tya*zJVsB+Sdyey8sqyyrbGT#=xi1o9-yX&!^5wZ!5y| zMr@(Q4M8nTWS@iN96;0n5Spi-%^!Rbqm9Ck*%F?|E={)EBsd;;60$2oo>rZl9^R;t z(!&jGr+7`N+1^?D8J{GlZybDIUCK1@0+7UI5qts>MzJa_F+worjMV)52s!qfZ653)TLL= zwhm>j8Q@nS6-*orAqnf|I`zN4(@7r*d`?}M-E^UH2N9=lf*DJDs*Az52Mu9$s8_(h zi&Gh^X%js6Z&_Mv^-I3y^wb zEzvlL-i2x$Fpm-ZrxJ~R@1Cb{P%&2cPyjB#f1yP73PFJHWo+>W6X9H)mk1{n_yO$I z_A{!_?GN#w5HZi@$HfVDl%Hd_6E@jT*gkE?Kc0e!I?qWPa=r{VVVpq7%Dsa61&o`z zZIm|Y9F%~^5=!trd&6!pgz8CvB1e!F#~^^H3{NH)k5o+>MDU|2+1qYp8-?mV+5V11 zB`w)@s%j$K6TbC)hiX(s_SOriql}=eN#OL53up`&5Yg1wMr)OUbFn$^3l*cml zB=tSS8%Cotl1YBXj}ZWj&gBF}`$OGwY;`f#9?jo`q=-S}*~775JFY%}hO|A;i+gMw z9==7QX&s;L+a~f};h~4}H}wGI>VFRiS_GZ;n4c?YezdDCGW&WWIZ0I{BFzb(G`M3d zmVJ|PntqyOo)&kRZ>%K4dj%VEF80g0nv|j}Ow7!v7uhkF{RiQRtr$M`6tJ0$A99R) z)%(X1^z-1wK9MFEpSNd*vBh>lM-K@7;?*;|$jRv^^TUI<|10S-VDs$-Hh*}3t)uMu z`(4W14rkz?DM!gSIs@OEa*W4lzQg+^?Et;7sC2$(_8ijtg(8aeWLBR;M>+Pro(;|u z-#P<7n8qnZ*LH%cFrO?)cNG3L1}yDCN#AeybOaY*r2up65EVJ1K8+&A2c`Bs(KQpH z_tycrM(dbZPVY;Q;u|A@>a_S_dGVRU(v12}iS6C&Q6tcHsKK2; z3ELGWj&bM|OejewsydCYXw;xA?3HOdo>8k_i9){7xHNLRrCy59EN_24EWe;8;S#f7 z4tR(TqQFPwsUE_VNx$F}k$G*p%$os`lmw>BNcJ7rQ}*ox**7dVk*?XDnXX0eJ(wx_ zCvoctH)@&U=Bhn=j2^p*{g%Oq@l z#X4bG7M7DssM%)21(pb%vb7>yMmpikd7S815^91Hd>R977Aa#tN(WuZZSt&^vA!XH z6MbpVI)!(?mW{REEnqCo-T+M4U&#U0lAU#O(OQ=G88F&_n=Pk3f0c!r&B}-iBxN47 zn*D-8<|`PZ&WN7^3WLbt@kMxYWbGA}y@(X2Q1eCAH0x=;b&p^{hZnrYSu&f z5J?WpQw#(i!L{@;pb$#O8F^NSNuQkwukx@yr1jYzAdCeG-7(xVygxxH?3xJvnFaMs z+p0m?=wed@0&ng=fLR8cZ<2!6@quSv+5H?|%R?kh&kEn38QfZ0m%6 zMb?yplu@Nxt5!hQ%y;cXi#erjR?@5aXe)8ijfPgL{864CW(18Oj9>rx*EDqoJ z!6YlAU=}w3145#o|7JfJ>UazO8Iy@#2wV-~d9AQYZ4&CY{V3Eo<_h(jGlcrUP$44? zHW#dT;Pb!P55OF9R9G~|q74S0;|E8^7aq{?$QTQR`vWvO+qwc_3#ri&8HoCo*j6cH z<256CYj`9C5-xip5%aicj1_~MpXtcJ;Hc9f+6aY=;qVc-KcKa)(bF$)7lWZ@7g4jA z8ID5?COtAcF00u$o-SxK2=yB~fw~MK16R%9$3U}GPUZePOePHSJgluP#((IHt& zj&l3=Vn&xxMTStFA3-Y`ZEI=2P>0*Tp>#MT4O+cz?%%*Z-rwgk4}_IxFKOa~V=O99 zq7CdPS$uDUU#!MYeIv~5WX56fnv{O_jo<{?!n0Vojw8eoU|tI)pXGd^Rm|vY_vWxB z%$Fu_MeB>clnaa}jfZ)C)IDrJNv-r~VT)+&T(B-!Ks!QWAgZV0PKr>|>jjfzm!}nvPK3;v!V0{NL|UJmbip77+86D zzpiLmG>XOsI?&gj=^_G9G6sx?MSCMlxd`k5)YPYKWFLlsZ z;HL7#qX0Hm9K|~0@(UaJelfq|t;zrJNqr+WF07dxr;JMV)+P$hTHN!FZG!3xHDis& zSoxBjf(d2Oc;FlN8%*zGJn)fED(nBI$#XNLha2h>4dL<04T+R3D~pq`Iukp2#JjIc z&N!u{m|0^$#qqJte^h>}PAiE*gdl@12T}jj*-hABuw}?f+6Tb6+w6ms-(ZsMr`-KH z-uh5Hsn6ImNb(%PEuR$-#r$>4`Mw?_5>b03#Pallz^R1IZL+YYqxPje1*y3m#v5Bph2K38z%~2wUOa=u{=#Q1@R%)l z2?|G`Ex}mJn(ILS9XkC}|CsZuQKN;1lkz+bHp-uTX}uWN?lHE2=WrdBFW+xma5c8= z_SAyq_CTqXB=w4x1!=yN7k#D|?2TpnwNVlu3hM#7TQ;}Xh3kVGF87&Uyx#NzwU8XB zXnCX)*Vxtx&(q{q5FlKBj6$R7GyJNXR4=@HD!D6`Hp2V0b}tuuYsI=nV=*vXW3l;Z z%TY$q0SVN~Y^dnN`?13r0W91L>>EUF<<^6=v3c^9dmN{en7Xa{yobj@x z=C5cGmaRnFghwYk>10Q2>s*S7V-vS`(YrQp=-0@OWHgD_q{Z2{0JT`^)GfHZpBTpR zqeQ5%Vrw_3%+&_S&;0Lwg(+K~d@z7gY4ZqOU31JE3n|q&F#L=ks07DgO2{pfgmv=YIJh_G|I1}eS^DGGE ziM~j{F~_5ZIvJP$xQ(Gh481^_I&PquNViDJt~j{pcA(@ZdLsi2S`O@sBO?h!5N$q1 zpF6=29ihnb65y!7Nd%&Am_)!`JzoR6Gew~$RlAdjV-a;1F5-3Tm-aAtap)IdJsvtD zPZF7mDtHn=_w{lrFHql6$vC(VMXeVEIcub6kS-2$z)_j#j`xcF0_!E7&~Z&#ynTzv(v0Uu z2G)zmG?mkLPk4tLL9cOPXko2953pZP1=9hWk8+zZKOvoKYeZ#Ta-b1#!n@((7=7^+m+#V@p!=GHanHcIE}|*-r~N1`iWV` zTRb?sc3CZSukc%(J~@P%cDk`z0+Rsl*hyp?*e5}k8mu0o8-rjAnYcwIv#q3-m_yGK zC_+sR*`~xEbE>E?!`#YDTKBKiMiRngfe>j}dg& zN2vGL<7;8H+qJQow-& z7}U^*ZAYDqdK^PI_yd5j(MR+Z(Pz2{=pZZ*0y>e$$Bpy>qJ9y`iK|(#oqZQ4dQT^n zJuV)vGYOUd08u({0G3Jy`Ao5@msU^qRq&+~VNycnYbXiUM5`>ylOpn){j}hv10GVF zB^aR~VR<_u_-=v==S8ypbm_j}b0}1>oH$^nQ(2UhgX1U2c}~_#rh_VH#;MUtO8;>_ z>5?v2#^yjU9tgOdNx$woD!;*{Y^o?yX>j#XJ}LdoXyl&Z_p#I`-{&S7^iRA2JQwT% zq=u`_${Z8z9WGV}M>>_pEYw)&l3+SrHHYO1S26X&LQ-)Ytj)!OF`G+;wH1}HF~F9B z6Sosm)odI#)RVtAJ0h{&bcGjo>K}1k5xFo^hn~}xdmqjn@H|q7+KNo%5GloEB8Oj3 zR6n9@%sNO!uZ@;8KsZHl+m-sA?(?F9sO5mBZUHD{7t0OTa8pA#KgR0B9;jY|e4(Zf zmB+Ywm+9&ly-S=^>0N5I$wvnTq4h6B)sk?RRG(foIimLc8LTuPN#VYEANA;7T}7{> z0IZT|mIf9uLhPmj`8rrg=U|!DcMoWoFh{`RV~;igcu%NKt*XX-QCD4{SBoN$BQ{zM z13#f&KoH^RI|3|*2rQ|m=)44+99b4^pqNwDN^R1dozUh?Ym8=_Rz3(!twlXrJJ&%v zx)_$RI8t%4pyCum#ld+V#x7PR(t08aimWG30oD=`Vz;$~LykOaiIcP-z9=rDMS*F^ zi^mGdOYEZQh#=OQZcYswiTSQ3aGg0|_AUO#4%6>Az^bad{F~begcO z^JM{pwJ>96BT-cq~Qr2;LSDGyDaP zO24S&4uW7O3eM(&K$lPuQ2U6=aCm8)O7QL-lu-|b=2~SG=mEDJpmoX0kV+F;#+x&x zxcVO?u`3u470>$v7*l_2u?09|Ty`<-wG}OHr?6(H_^AaIL+n^^GP>UT9oL`7n0y0Z z#&E~jU;EH?)1*GR(4Pa)pK&N@NuW);x{EZt8Q?VV5#JXFu~qbjk_7MTU?k#knAa=E z;fwb2astpd9R5)qT9KP0MShJhdZce9iU1*6wlk7_40?h~S#9+ryP<-YqO%k10b0q# zNXa!Q$+72b_#E>EtkV50Wnlv9r^P!nK9`j{&Wu-5t|LrYa*SU)l@YzjhMMUzwmB78 z2k@N^+GxxnEdjf?P+g0pG%7!Z?~wZXrImzzt-1YI+I-kI2u}FdQ3gWE_irH_B$!v(oX+XVRB zSA2@J_Ew}uq!umxAf^BP=jm@z`X73>GLceWk$omBAzltsKAs0~DFd@;J=%S}j9tcN zdjlg2NwXanV(AQ`9xEWAEyznIQ0#RT`LtDn%ChQV zZkxg*kuJ5#(7H?v)6XyI*9ouLt--7KTB2E@DQEz(1HlbaJkdMIfK2$Ol@vJZX|i2# zJE1pb9$5TnOyHSOn7A{+&ydMyM{5Voa3g>44Wz4Y{xwS9v>)M>77skKp@M4rEnKpV zen{b?!CSz1VGe+ZnYM%k*DZLV*1H$>9h?WSjd-KPt=sEk&I7jor}F@=Q<5zbk}sai z;x8DHs|Rhj3wmz1bI>hb1;iTso^+FMc41)r;nx`54|_dV4A+D+V--VsDUfRZIh?sK zrY0$ydu0}Rjfl;P=0ob)V)EBkdU?Ezj>`SqtS6v%}VeEuD96dsN z)09H0Gpk3BdL6){Z>b%s0d*ox?V%jhYsUr>NyMQewqNwCeuZSNuL3h%SnE)YI?#yg zKu7w(g#aj;RPR--9sma%r1DLU=($wY5I5SE~vA8n!>PKy) zQvQYNz_x2eF;XW@XWE}ruUJJCq|=Yg8g=`ti+wp!;>MLJ#Qi}JvZgLVQA!8Zr0K3i zE37i|Z7mSP`-M-ns`9Ki$=J)X>9FQ42v;P*D=Rcs4KbdaAqT_l7~*_sZ}?^Cd>Deg zP`Q>iSFld(321;WI<{fpV4ecZEaW_i4QJ=MDvqX##2Uxa2c$jt>;Y~l|1c7ug5YJy z;C+-(2~6Z*k{Qdk(^0d%Z#?l@KrcF%1q zNY#=g<*~D&=ilUwQW4M)HK?E4tB=L8ujO#JN#zSr1|2x@4Sbj2REjP&xQwTvqQku%J;N^R4D3y~U6F1uiQWT7k5~2;!AY6%8)xYxKPnK(eP)PO47W)3BzQZGIg0y2 zyhJu44Ro7;1&Z0oQCyIVjcqQ5GqhR~UUI3&bTyZHR3@3QIM5Lc6B6iwv;QwQOECR}ey zq+Z6)>S?``xm)W!@nP_ApCX|6J+yT7nNxs~qgj77_mZ`=He;9q)-l0}cJ;nskCoW~rw<%KQ-$+-VfzMofG+pq9A3`YqRBwP zq7bnG$mgj{;E!c~L_4=~j%8E}nD{dKJwBjL(K^HbhTz{&1445JzM-C=o#6FI3Cq6* z$X!oG|4r5FDs9N-*OHov))kT6R;_9MV|Z!0DqLf z1;08=-D&;nP6aXG^(OEl_Vozcd5iZv9K)v<GnUrYg*ih|FOIf%p~X zAYtYq!%%_DDO62kKw#>@0G66Q9@ZPyg#>P02wDMec&3Od7(770l*!H){01_e`qXO? z{PF!AyL%&dOC$>)tlyHbrZ5f;=nf_SRvcG({evXI*(L_gBsdse(Wp7*sQ6x37Ql4i za13XC$H}ElpPvz4bXIOPo-!VRFBwZ)A~=4upVEX;KU6bvsS&ek7jJ?4$^`!0DIFG) zPFYU#EBZz(2{+Kh8}LH0?9sqUYT)astW$sRJ;onH1h;b7e|-G!tcmkG_#;M~GQ=xP z&(O6UK)?8R?V5dw%EMa*HQsCgLf@}o9f5;0-Zmvx7lhCTNh#)Gu_+|6ae)U1cLlOg z+(iG>7YIcGa;sA5zKZZcPMoZGBqs}2`oTZoJ|Zcik!)^$hp@(vjc%>l#x25=48|6+ zN0Ga2GS?B7#}GttFbaHe>;`H0G+aG-|P2b<`8f)CDFn^+U|yPx{iZLa{S86ZK8N$ad6i3G{svamS}M~t5kZplH<=;9kg3s`Z(?<;MSN>F%GP)AX}*U65d}w!SP^VHjuzY z=)mp-vRFw~F6$+5?R+2UhKvCBzsXUogCH@PH>=~7qLGI2O5QE74DAAAfFTGQO1q2w zl*4|&W}d_FegGr@NFCVc!Mm}Kjj)QL%_PZepssu)tY9>0wifX+lNGznCAi%}A_1ms z*~=(E%u&;Y@P`=2ntT_z?5bN>96QU`-iEu1QO@!=9IxsCoGv zCj%}&c9mf)*pkx`9?$3;VRR4o7Fh=x z-Eh3V^g0>a!>_<%u_9^Ti<15TkbdmlNzMz}x2wwUyR88Iyn|aSo$L&hH17f2UB0#3 zdWVLlq}@g`C%xxN%ar}-1OtE^(iT~&;Gj7fZKAi$*3<~TBKFDsKrTC7 z-fk4CHsX^m+OyX7OLYE6cCQa<@{=m=X~6X=$_YJBRseF`Un-`c}l3V_ZPqcVxLoYe|5y2fg4))G)n(gJAshH&{aL z+tg_ir)_s|0+*e~yo>jph&#>u|JGd7}v2ZeCs%Ik+SWOLwDyt*A? z9;MzYde;uZ5!hGQpNP0f{sH$BSuqxvj%KXCmGLe1m2>;{Q`r_GXuai1RYlf|NZL;l)`)RR zt~Slh!T?GkScr?PAh_912^u8k`$=sSlJz2R$Xh6pI3)e$k|N3h$A+>4brWoUpPCHs zM*NkjQMifE4I3W;Rvazi<%w^d%6!u}-@*eHH4SLO4Bj!$=Q>~!g%8HW<{s%g%5=O;jhTf zps!5&C;R79e8tbFI4W=0)&=jQNm>1jw!dh$%Hw2p{gT$Gwx@GU;#o0e!L7=iRBwRv znUi85l)zVziqY?ai^Nay_OFGp`_(yohlGf-kL_2l!9YY|jU!GOeh`Q<*gMPaaIg^W zF@9u06vt1uE2dAyjKlDnEwe25EjR-_(Gk(9%|HZhHPmk2$IQ6Hh5XXpPVeVddOnly z#3h)45k~NJ@7koE^h;@U%uCi?+(X_M`O{Xx?Wh$Uf0+LCt7Koob7{TqnE^%3<`6!3 z1c#rH8M$Sa6bh{0bRt1qG(Qf)#Igikb)!WhUfNjkP%W8gg#jA3$W+%fbE3FLTZmLF zvJi#JQxJ|YQ>{pi84OSdXT?L3GJ{q;sgbfW8qyK>V8r7?P$`8PJlVKMyrjo5 zkD+t(D)}Qil(D25L7CcC=_g&e9DNY!fu^vAxl#ohUz@2e<91{+BB}j3tU_fjQO8}l zqR(la{=7MvG6wVK13J`eu@*=q{tZfzWpFU}+aW77QSNkHE~dqU(!2GffyAkPR`5m? z5ns+fV18Pux*I~^oy z&}g)++>z7d> z-QLUh51fB+s{AY)g>;X)DtvK+2#Jxa0v#Ce56vZi6U??c)aTLE24j{}N+I^fgUVA@6EklD!1fOYp@| zZ-I8-qy1|&Pod%I@+;0mh{2GTIUI!bq0NaSHo8WR13_nU12K`R8L5yGG3LRbN-Q(2 zGnuWhPC*u4`ndB}@FT&w31T38RD_y|Mle|1b+o4B%xW9eP_e2RFwqZvFDHcS-|y+) zYE>tn&WT2d$Qy8hM!O_YgC`b7BtlWS5CC8s!=+2`0qjF?UqnrH0%}ojAZ(yh9UX22 zwhh!kDQcfqK?ZWbE2vP-7n}hM(-;5^Ac8)ja-@kf0K!8I3U$4PyMcA6kMjmRYlsP8 zic4iG0nP9-k zahOko0s?a}Nc9%H$3f{csLm9LHt`ecT>zirGdLsR$A_cw9tMZP#lT=ORtYWedC2B} zt_~?Kw$!?SumC$|2}1gzy*_v+H~*%^(Wq- zjy4?R;2nYeNFr!vaGKqxzPt{sN+TYExxjlcPN*8Fx0j2MoN)#V9~`7rBD*l-b6hyL z$kpUwe%8QA3WixMpU`t}5(xOr0(G86+k8Ci5WSS!foS((grFSZ0<>?nHUfGIg{)mP z$#nh++rN4<9|9IAaeNDn7_$Y^2;tA8s+ZDW=vWA|ya!bXR*U)~HX$kvfALOwqy?lm zg}?yxXVrVan)wceeTX{IX+j-_w?SymWo^!BnmV?@&*h-80Iwaf{@4L&+!#C^I^ipZ zV)$-&8J%L#@|wn_JPy__wGUuHVVGn1irq;POGteln1HRuOvhRL90UhLm+^rDYCify z>OVDXak((~Hk5g_ByxTfNV&D-Vh2caB z=61TO1$%&l9TVY021$nPfMkikoTiHGAgo=PfyQ29;LARExs|B`JR(zu1^kI(e2R^n zFYpdAiWwVO&~e$1CpJl!?UrnCsr(jqzVW)-Wj`4~;@bo3`UaZfY)wcC1bW+=*fz;_ zQo0QP28~Th3YJxf%I%T8XS0RMQhlPZ7mj=_m1t?UHAVc?%*hIZ9`;^>JgXfS8MCpv zq7a{CXk{Wrcdy{xC*W^l zf;WDOA`K2!D+biwVxYEfpaB_M;lsH)drW-Z+u4$_MY1)(h0nN^HHnw0Y~ku`wjt2e zE3l!D4Zc!=`a}v&Y>_TIF4^iGtkrl#jK7H08&83BQUV)r;^4NX7;)tyu(7Z0ENc)i zYsHi5ra(h)+X)Hgft{DtqoVOFikmTeX5&`0;5`GJ3;<3*=Xi4n12=8~l@An|$6%}l zOUKFdB$I@D_RNulsFi=E;A^n43u$J24KX(2D-BJsJP+mo^~3X;I+Ie79WRORYF81}Q<=6~Dn zrv}4stH3gbg>@eI=)pS8rl-B$^9kazZ_FJ2oC*Gnn3DE6bt)V0!rr`SPQBD?a5Ouc5~20j0g_O0*b81_#0x3O))-qIy$perkfh z#}hf+)8$(}AFrcL8ZSGFOEE5t5_Ya6t~QPQ3P@Hrd!aY7wQzR!^l!g2GQYC%b2+2V z4LM~ywsQPMRrO{Rgh09&w$6R(rHE+g*6*!SViFRgqtf^h?+qN_5%h&WshJ<`?G2b= zY9js7eD#FizM%|rs@XYVuh_!YvnKVsCtwx8pAe6szOcN@$QXG%_z+5P`zPx%hWuTL z-nL6@=e)m5F92OluI#Wx!v0|rq+zC17~YZ_Ma5~vT~+v1mF~qx&+q%yX={ck8njji zwEj>27(ww@mRUwx3|D3jTVNQQUz}e$(_L8nfMr%uVScgO@?icWmXeu;r9+1fjm<5~ zFP)!X+AVjqR-r6^R%yQ5Fi|TIj?I}_nm^m3#mX#mXS!$28ESA8&nkVSg!fibG_$bS zoxi|M{mA)6Me~MGX-{ds<>7gyMYAt6I3`TEW5UgraGiO@mV%juMftN05<1jdnN?Ua zC%-h@j9w$0AFXjmY2gEf#WRb-Rnf@ANux(QCQckP$z>QLI$aLQFws4;l!NBZFLU2) zxw5RcAy=F@(J&#u%u}Q%P*Fo3@_F`kv-9U)=P8|8JbT_;!Sdl;v+~Qz3Oq$aEqsnkW|o!Z&(<)Pn_pTmuXJv7p}E@0@%))Zg|jU)OCRve z#S#SJVKW*(=8l^!=PF*}xxc7z7S**B8#(eZn(hP35G zG&yTtF&4MmqBYGIc|qyCxt2Nk3-o6Go9$@eoU5V8xcHcLm~Xat9HU21=SkCJ#s|LQ zDJ{k%UOD?0z!3wz-gsez1%#{r8@$qFU@i*Xg_w;;iFrlnq3DrF{U|;`fYVFz^Gjm@ zj?N`8r;Q#oGt-UQzp&mcw~q1uP#)ztJ;%VSf%qTlg=r0g{YB-A=Y?sDhV!4t(T#mr z@=HtSm1_NH)TsRniG$=W1Kb0-M-#=vzZUO@2V;L>L!rMw82QzUQgupB!Y@hs7W`)8 zxBP*<$-k|9=Yda3j~0)(`NuonSUe&1&ZTn_UU_SqchJROwoRS2@ZrafJUH+3OydLB zoZb4?{H7d)`Kx~kOw!B5X!b>nR(z8L<-4~HLWwOwdxxS}rog@l(A zFDT#Be)YdR$=)Hqx}^A?l@o5+@?RO>T~{%2li{G{%s;38c72U~oNLO)=8nIg9-VW2 zrQ>AW;@lB`eDluNv!8zU4_QBcTD|=;bLeQxmnr=ZOn*_jXzgDYjhub&#bq;=-0{uj zzgb>2=Fo>}JMZhWqixvDy`8EK7(@4Il!XHE-q`5aDVFG(`zm{bJwl6*S9=kd3lHb?1C@vIX39qH?OZsY_R?F0`uKhmmiou zcaXI^xTY0<|IZ@FrBVcM)o zm!&-Z)H$&dO?>IP-;H`ky6K^fr~U80vE%*V)Y`l3hx;AAd({)SJ^S^21K)b-?}@F( zsoo1}Ds9%f$5SS`@2x34`_G-_f9>_nEAGRu1OkPxeE;OwnvVKE1cWdClwzGXBzW!L zE8OG0`P+NbwpX2;KjF?>Uz%HQd$aNC`1cwX-@WE&pSJq3&;0f4J7+cZdLSbst+MwA zS7%*z$8`7tatt=y?;A@=(^e^l|xFO{gb-p&$k!+>dg}0?X|DpS=4Z` z`s^lauitN}ydm{i$;#iF2Lx7KoK$qvuU!wzSHJ)1d&B=LWuSfB-&@Du(e&!nqw|K& zIrPNS?s>D`Sdg}$u_7t1Vd<$*&GPo5+*K!DKUs5R!9(@NOX6=ifYmn*C~5PALKZAy z^TUwX5n2$=K$yJ%&o^UrwjoSHy!nw(XaT}ZC=>5d_&Dkz-@GUknvF1faVWGB;Yx&l zgcb!V-j;Fi@>R=*Mx%F-k|61dB^1OsxYE8gR-o2Vpi2s6Ry5){1=CsXDg-e+b*Q zheBkx%G?nORUtIP>iR0B!(e>?VeT&U2X1c0=}`%VdqSZ%5q2UxfY9Ro{H`{5squ)G8GfiM&2N|yeJBOHOyauj$*XvQH|Bf@5cN%Vvtjx^~h zKQqECgqa9k{AoXfK@SYZM<*DJ3(Uq#QWBGvW3!_4f%t948n_CfK}rOK27?v9!}0qe z%u<=Osim67q^6G(`aGOeZn*7N_TOA@y&S2$KCK~t8EA$Q^dD;1jo%NEZlU`654E`z zzq#{4tCTh>)vUzbo|?Wi{`OSMlO|W{U~j^h)Xaj^!DCV_Vrn|_N2MkiTqwF0zY9?B zH0_#5e>!edYWlMHQK=TiG%9uQ(gZOz^T}RfYL+)~Y-*b+zR=h^HEVQgrkFYyWl&aY zO9aXAF{c5pEO#jM4B`eys@WSCH>Y=M63@>@(s1M_JrWA-p!_kZ=4I5M5t;BtI)=kgabZSw6QcEC7(1&G}dvc<#BQE_D&s) zq{wjPJ~St*QRk&cu?FyMm_YF513YJ+@u^95GLpVRek<}HCwu_De~XV9@oncu$JjnJ zZx*zX0)93K-}FQH9*(qhr2XsPwl5LB$EEs>@oDGoz(M#Pg5L_XQw82wMC}3JPsWYu z+e>;newoRkBsiA#lDvuW#<<>~%u#)){A!eITmqVplph@}KW1q!6#v3V#nA)d>wA=I zg~8-Ld;?#Wdw?ye&m2$>)&zb{;MwUFFo#e+ekd&+X)BOMuNKn}r42#a4Djmz!`^p> zSy3!u_spEQXV2LjSQc2~E-X1CX<^AA0)l`7f`EV^AW0-Eh~Py81Ev)b6K1ZWqM{-u zj2HE)m{BosO{mu#_};g=`|Rw3-tWiv=X)OKd1_8ocXf4jcXf4`o;goRhz=`5SmEj; zN6_8m%Ex^;!b%WUgKgUnX7D>Q92iqWK=PfDrttF};4gh`)BBjVin>dS!d78V$FfyO zP;BofAv1r&kt6rv_s{KL+M{n`h_}f*mP90F8PhR7t-JKd5vs?F?cD%jYY_G;E9|fB zX+Ywz<4p?vxz4oxd63t!0zMl!^vp7Fg0$>| ze_mGU>%9^i9Cz3;Rp}$P{TpP>zV^rwyelXd>oOhV?Jk5(p>0W7q1&;2iO8ROOjpt? z#(EnASqH8=a^%CLEEHxv%XySvqAc$)`^lfQHQVM6$Xl`X$dS&#d9g3-M%Wz)%VK?; zkVs$WmM8L8(~qnSxIZQGdx)Ft`NY#NXrIjaL{B7FpU0c|#d`N9r_4`lcN z`jkcJBu|LVHbC5Gi0fnWF#Zm{)A&2xUYBZTm29vcF?TsU($g=z_!6A$%FycIy1+%= zdRO~D$UJ!Ckt6pqzrpPDaB?f@L*3yiiG0+2k3^(zY(^r#T+n_`JWWoq#~nyJ6?2yk z)aS45QQBzyF{`x~-H+-82w)C03NJuTNZM4tW3-d>AFmW`RJ}9yfus`183Z{iZaH$~ zD%h3wW4&0WUg&ed01Ab+K%shx?JWfFe=*P63RYfh-*pJvfVozxgy^s>2wPVzYzM;D zAS}nnw-;fTAxsKoQ$;UeqNYVQmqHKLV7CVNDUX z0%0wEVPy!bKv*|l*l>jHMOYhO*i?l57h#yr$VGWe5T-DfJVghI9d1C_AcQ^3uzra$ z_dfW}ay@2pPO32H{DyI8_jNAn*V_70UBQ?W^p3gxe1!HJpCRp&NINxYGyRio+`IZ5 zVYlOc0GsJ|R7($v6(Q@kaE_m0&U-!eknz_YC|WL&E+aNN3Ow^LA1c z-eCHsqvUOHy^D@5Pv%7Q>HRQoZ-9CCWZ;x{5$8nfVEexLjI+GjbOxzuORJD}6w=}^ zF-^O|7p!L5m4UvA*~?O72BbY1Go=1Ik!BUrtbs4k`ipNp_MC6K>$Uoe`lT&KAooeE z5Bz2j~;y zx@Q&AUWv3n8y(hhEHw5%fD?m;$eD`(Gng~aGQcjR8;>>VTfmv8e_XDdNA-p798)=J z`BfhnqdhCEV}FB;;$(eRoA3B-5G#@^j%m+*#GVInP5cVhph;QMCyzDX;p)a4a_qJd z`xyh(Dj=`u-$#yoLOmh6{!CVvlA24f3}` z{zX;$-=Fjs*IjhX40=dCa{d3`*RhX5T`T=UwX*lIYa(~cF=drYJzfS`Yu>6XKVLAg&AIE@d3%qh=g{;hA+U+H5p>fqxO-GlqejE(9j7Tc;Oijnc2S zK;AmY`^d;MeM8T0&w~>|UCWq%j?5H+>bWlc*E>jChP@Vx-#3t;xGqU%6n!BcI><>7~crw!huevdyYSY0VCd(7&>H2TC1z zy{3N@w%hc@#VjstImvb7#rn5~p=a;Xh<%2D?RL2e%|NS3FW-*B8W_WSS zgVzx5>*6v$>VS#Iwy#{L8%4Pn#*6E9UiO)Yrt}#+;w>N3A>0DtwcVsQ&8P5KM6NvZ zyB+)EylnV*qp;PRFOu;xY{~p<>tH#!NA%Ytx_n@lqT+-xcO}gZbTJez%+7-R5_n`8{BM51U_Q zHcQgYZ;|zw`|+k1S@VGX*1D{oo;O86k~!sg2QX0{yFO26d|s<16(@&Y-3aJCHqp=0@GAq{l=iPl!Dvdd#%SH0cQ@^5dX8AjT^aZLVkJuH8EE6woyU+Ob^pyfZ%RrAmJ z6C2d}*BEa3$M@F!ADQ~9rjF77@-dcwh}rX($GO#+L2LPmQ!W370h;MSQ;zCMH2*n! zE&q@unxo<`^*Qn1mjCc_&HuRV+to7A@_+iw^6y@&8CLR9-!U%D-}D>HZ%tCRCI6Ja z#`l&#-MHFIkHO2JZLqYUa>RJ*(67~ED958;iTJLV6IlkMin-xgV2cI9d;$`SguCM> zJQTM;!yF!lh&iDZ;0u>Bs8L=MMn6EjxZo^CyFqRx7X^ZN#RchuXCkm}BzHCAoX`b` z%en}7_(a@-`I&u)^Q$Al8d;NxH^VKIpT%d(S^s3fKr~Hwboy1qd1@wna>h->U!WG_ za$)K!i%&ViMZC>w_#=LzOTbw0II!K}*%1zFK?kyDs`*43WcO7IHUjCDeVRI(A*&KuOuaB=H;8SCv#G|Sl=newPmCk6IOV?} zcB-u3ATgDyF@1(qq^7U8behWJzl6BeZl5EKiZW%Cta)!Gc?8qZpD_9;hK8_DEU!07NEEL0bGZD{&Nr zTqgsSf|RQ8I^dC>f$8|kVtBL#ZsGf2=+W2nSlH-gxaBTED9m6ga9GoE4Bw2P+{