Files
utun2/tests/test_radio_batch.c
T

138 lines
8.4 KiB
C

/* Настоящие radio TX/RX без сокетов: размеры1..10, хвост/FIN, malformed, дедупликация и таймеры. */
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#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;
}