138 lines
8.4 KiB
C
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;
|
|
}
|