/* Настоящие radio TX/RX без сокетов: размеры1..10, хвост/FIN, malformed, дедупликация и таймеры. */ #include #include #include #include #include "../src/radio/radio.h" #include "../src/chat/chat_setting.h" #include "../src/utun_instance.h" #include "../src/routing_layer/topo_group.h" #include "../src/routing_layer/topo_node.h" #include "../src/transport_layer/etcp.h" #include "../src/transport_layer/etcp_api.h" #include "../lib/u_async.h" #include "../lib/ll_queue.h" #include "../lib/debug_config.h" #include "../lib/mem.h" static unsigned received, fins, expected_seq; static void receive_frame(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t src, uint16_t stream, uint16_t seq, uint8_t fin, const uint8_t* opus, int len, void* arg) { (void)inst; (void)stream; (void)arg; assert(group == 77 && src == 1 && seq == expected_seq++); if (fin) { assert(!len && !opus); fins++; return; } assert(len == RADIO_MAX_OPUS); for (int i = 0; i < len; i++) assert(opus[i] == (uint8_t)seq); received++; } /* Скопировать исходящий пакет как входящий от другого узла; ownership передаётся dispatcher. */ static void inject(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, const uint8_t* wire, size_t len) { struct ll_entry* e = ll_alloc_lldgram((uint16_t)len); assert(e); memcpy(e->dgram, wire, len); e->len = len; inst->api_bindings.callbacks[ETCP_ID_RADIO](conn, e); } static void poll_for(struct UASYNC* ua, unsigned duration_tb) { uint64_t until = get_time_tb() + duration_tb; while (get_time_tb() < until) uasync_poll(ua, 10); } int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); size_t baseline = u_get_allocated_count(); struct UASYNC* ua = uasync_create(); assert(ua); struct UTUN_INSTANCE inst = {.ua = ua, .node_id = 2}; struct TOPO_GROUPS groups = {.instance = &inst}; inst.topo_groups = &groups; groups.group_list = queue_new(ua, 16, 0, 8, "radio_test_groups"); assert(groups.group_list); struct TOPO_GROUP* group = (void*)queue_entry_new(sizeof(*group) - sizeof(struct ll_entry)); assert(group); group->instance = &inst; group->group_id = 77; group->radio_active = 1; assert(!queue_data_put_with_index(groups.group_list, &group->ll)); group->nodes = queue_new(ua, 0, 0, 0, "radio_test_nodes"); group->senders_list = queue_new(ua, 0, 0, 0, "radio_test_senders"); struct ETCP_CONN conn = {.instance = &inst, .peer_node_id = 1}; conn.send_input_q = queue_new(ua, 0, 0, 0, "radio_test_wire"); assert(group->nodes && group->senders_list && conn.send_input_q); struct TOPO_GROUP_NODE* node = (void*)queue_entry_new(sizeof(*node) - sizeof(struct ll_entry)); assert(node); node->node_id = 1; node->radio = 1; node->paths = queue_new(ua, 0, 0, 0, "radio_test_paths"); assert(node->paths); struct TOPO_NODEPATH* path = (void*)queue_entry_new(sizeof(*path) - sizeof(struct ll_entry)); assert(path); path->conn = &conn; assert(!queue_data_put(node->paths, &path->ll)); assert(!queue_data_put(group->nodes, &node->ll)); struct ll_entry* sender = queue_entry_new(sizeof(struct TOPO_GROUP_CONN_ITEM)); assert(sender); ((struct TOPO_GROUP_CONN_ITEM*)sender->data)->conn = &conn; assert(!queue_data_put(group->senders_list, sender)); assert(!radio_init(group) && !radio_init_instance(&inst)); radio_set_frame_cb(&inst, receive_frame, NULL); uint8_t opus[RADIO_MAX_OPUS], sample[1 + RADIO_HDR_SIZE + 8 + RADIO_MAX_BODY]; size_t sample_len = 0; for (unsigned target = 1; target <= 10; target++) { char value[8]; snprintf(value, sizeof(value), "%u", target); assert(!chat_setting_set(&inst, "radio_burst_frames", value)); assert(!radio_talk_begin(&inst, 77)); /* Смена настройки не меняет уже открытую передачу. */ assert(!chat_setting_set(&inst, "radio_burst_frames", "1")); unsigned total = target + 1; for (unsigned i = 0; i < total; i++) { memset(opus, (int)i, sizeof(opus)); assert(!radio_talk_send(&inst, 77, opus, sizeof(opus))); assert(conn.send_input_q->count == (int)((i + 1) / target)); } assert(!radio_talk_end(&inst, 77)); assert(conn.send_input_q->count == (int)((total + target - 1) / target + 1)); received = fins = expected_seq = 0; struct ll_entry* e; while ((e = queue_data_get(conn.send_input_q))) { uint8_t* p = e->dgram + 1; assert(e->dgram[0] == ETCP_ID_RADIO && p[RADIO_OFF_HOPS] == 1); uint64_t peer = 1; memcpy(p + RADIO_OFF_SRC, &peer, 8); memcpy(p + RADIO_OFF_HOPLIST, &peer, 8); unsigned before = received, before_fins = fins; inject(&inst, &conn, e->dgram, e->len); assert(received == before + p[RADIO_OFF_COUNT]); assert(fins == before_fins + !!p[RADIO_OFF_FLAGS]); inject(&inst, &conn, e->dgram, e->len); // Дубль пачки не выдаёт кадры повторно. assert(received == before + p[RADIO_OFF_COUNT] && fins == before_fins + !!p[RADIO_OFF_FLAGS]); if (target == 10 && p[RADIO_OFF_COUNT] == 10) { sample_len = e->len; memcpy(sample, e->dgram, sample_len); } queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(conn.send_input_q); } assert(received == total && fins == 1); } /* Повреждённый последний кадр не должен выдать даже корректный префикс. */ assert(sample_len); uint16_t stream = 60000; memcpy(sample + 1 + RADIO_OFF_STREAM, &stream, 2); unsigned before = received; for (size_t len = 0; len < sample_len; len++) inject(&inst, &conn, sample, len); assert(received == before); uint8_t invalid[sizeof(sample) + 1]; memcpy(invalid, sample, sample_len); invalid[sample_len] = 0; inject(&inst, &conn, invalid, sample_len + 1); for (unsigned count = 0; count <= 11; count += 11) { invalid[1 + RADIO_OFF_COUNT] = count; inject(&inst, &conn, invalid, sample_len); } memcpy(invalid, sample, sample_len); invalid[1 + RADIO_OFF_FLAGS] = RADIO_FLAG_FIN; inject(&inst, &conn, invalid, sample_len); invalid[1 + RADIO_OFF_FLAGS] = 2; inject(&inst, &conn, invalid, sample_len); memcpy(invalid, sample, sample_len); memset(invalid + 1 + RADIO_HDR_SIZE + 8, 0, 2); inject(&inst, &conn, invalid, sample_len); assert(received == before); received = fins = expected_seq = 0; inject(&inst, &conn, sample, sample_len); assert(received == 10 && !fins); /* Пауза захвата: частичная пачка уйдёт по deadline без END. */ assert(!chat_setting_set(&inst, "radio_burst_frames", "10")); assert(!radio_talk_begin(&inst, 77)); memset(opus, 0, sizeof(opus)); assert(!radio_talk_send(&inst, 77, opus, sizeof(opus))); assert(!conn.send_input_q->count); poll_for(ua, 2500); assert(conn.send_input_q->count == 1); struct ll_entry* e = queue_data_get(conn.send_input_q); assert(e->dgram[1 + RADIO_OFF_COUNT] == 1); queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(conn.send_input_q); /* Destroy отменяет timer, не отправляет данные уже закрытой группы. */ assert(!radio_talk_send(&inst, 77, opus, sizeof(opus))); radio_destroy(group); poll_for(ua, 2500); assert(!conn.send_input_q->count); radio_destroy_instance(&inst); queue_entry_free(queue_data_get(node->paths)); queue_free(node->paths); queue_entry_free(queue_data_get(group->nodes)); queue_free(group->nodes); queue_entry_free(queue_data_get(group->senders_list)); queue_free(group->senders_list); queue_entry_free(queue_data_get(groups.group_list)); queue_free(groups.group_list); queue_free(conn.send_input_q); uasync_destroy(ua, 0); if (u_get_allocated_count() != baseline) DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio test retained allocations before=%zu after=%zu", baseline, u_get_allocated_count()); assert(u_get_allocated_count() == baseline); debug_set_level(DEBUG_LEVEL_INFO); DEBUG_INFO(DEBUG_CATEGORY_RADIO, "PASS radio batches:1..10/max Opus, immutable target, tail/FIN, dedup, malformed, deadline, teardown, no leaks"); return 0; }