diff --git a/tests/Makefile.am b/tests/Makefile.am index a91dd361..ed32fbfe 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -955,6 +955,10 @@ test_radio_headless_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) test_radio_mesh_SOURCES = test_radio_mesh.c test_radio_mesh_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/chat -I$(top_srcdir)/src/dm -I$(top_srcdir)/src/call -I$(top_srcdir)/src/radio -I$(top_srcdir)/src/routing_layer -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/lib test_radio_mesh_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) +if !OS_WINDOWS +test_radio_mesh_CFLAGS += -DUTUN_TEST_MESH_OBSERVE +test_radio_mesh_LDFLAGS = -Wl,--wrap=etcp_bind -Wl,--wrap=etcp_send +endif test_stcp_traffic_SOURCES = test_stcp_traffic.c test_stcp_traffic_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib diff --git a/tests/test_radio_mesh.c b/tests/test_radio_mesh.c index eea7b4a5..ca488c9b 100644 --- a/tests/test_radio_mesh.c +++ b/tests/test_radio_mesh.c @@ -19,6 +19,7 @@ #include "chat_event.h" #include "member_sync.h" #include "../radio/radio.h" +#include "../radio/radio_proto.h" #include "../routing_layer/topo_node_sqlite.h" #include "../routing_layer/topo_group.h" #include "../routing_layer/topo_node.h" @@ -90,6 +91,76 @@ struct mesh_shared { }; static struct mesh_shared g_sh; static struct rx_evt g_rx[4]; +static const char* g_names[4] = {"A", "B", "D", "C"}; + +static int mesh_node_index(uint64_t id) { + for (int i = 0; i < 4; ++i) if (g_sh.nid[i] == id) return i; + return -1; +} + +#ifdef UTUN_TEST_MESH_OBSERVE +/* Наблюдаем raw ETCP до дедупликации, не меняя данные/результат или владение entry. */ +static etcp_recv_fn g_radio_recv; +static unsigned int g_wire_tx[4][4], g_wire_rx[4][4]; +extern int __real_etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback); +extern int __real_etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry); + +static void mesh_wire(const char* direction, struct ETCP_CONN* conn, struct ll_entry* entry) { + if (!conn || !entry || entry->len < 1 + RADIO_HDR_SIZE || entry->dgram[0] != ETCP_ID_RADIO) return; + const uint8_t* p = entry->dgram + 1; + uint64_t group, src; uint16_t stream, seq; + memcpy(&group, p + RADIO_OFF_GROUP, 8); memcpy(&src, p + RADIO_OFF_SRC, 8); + memcpy(&stream, p + RADIO_OFF_STREAM, 2); memcpy(&seq, p + RADIO_OFF_SEQ, 2); + int self = mesh_node_index(conn->instance->node_id), peer = mesh_node_index(conn->peer_node_id); + if (self >= 0 && peer >= 0) { + if (!strcmp(direction, "TX")) ++g_wire_tx[self][peer]; else ++g_wire_rx[self][peer]; + } + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, + "mesh wire %s self=%s peer=%s group=%016llx src=%016llx stream=%u seq=%u fin=%d hops=%u", + direction, self < 0 ? "?" : g_names[self], peer < 0 ? "?" : g_names[peer], + (unsigned long long)group, (unsigned long long)src, stream, seq, !!(p[RADIO_OFF_FLAGS] & RADIO_FLAG_FIN), + p[RADIO_OFF_HOPS]); +} + +static void mesh_radio_recv(struct ETCP_CONN* conn, struct ll_entry* entry) { + mesh_wire("RX", conn, entry); + g_radio_recv(conn, entry); +} + +int __wrap_etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) { + if (id == ETCP_ID_RADIO) { + if (g_radio_recv && g_radio_recv != callback) { + DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "mesh: unexpected different radio callback"); abort(); + } + g_radio_recv = callback; + return __real_etcp_bind(inst, id, mesh_radio_recv); + } + return __real_etcp_bind(inst, id, callback); +} + +int __wrap_etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) { + mesh_wire("TX", conn, entry); + return __real_etcp_send(conn, entry); +} +#endif + +static void mesh_snapshot(struct mesh_ctx* t, const char* reason) { + uint64_t gid = strtoull(CH_ID, NULL, 10); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "mesh snapshot reason=%s phase=%d tick=%d tb=%llu", reason, t->phase, t->ticks, + (unsigned long long)get_time_tb()); + for (int i = 0; i < 4; ++i) { + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "mesh node=%s id=%016llx frames=%d fin=%d seq_ok=%d next_seq=%u dup=%u", + g_names[i], (unsigned long long)g_sh.nid[i], g_rx[i].frames, g_rx[i].fin_seen, + g_rx[i].seq_ok, g_rx[i].expect_seq, radio_dup_dropped(t->inst[i], gid)); +#ifdef UTUN_TEST_MESH_OBSERVE + for (int j = 0; j < 4; ++j) if (g_wire_tx[i][j] || g_wire_rx[i][j]) + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "mesh edge self=%s peer=%s tx=%u rx=%u", + g_names[i], g_names[j], g_wire_tx[i][j], g_wire_rx[i][j]); +#endif + } +} + +static void mesh_tail_stop(void* arg) { uasync_stop(arg); } static int G_PASSED = 0, G_FAILED = 0, G_TOTAL = 0; #define TEST(n) do { G_TOTAL++; printf(" %-58s", n); fflush(stdout); } while(0) @@ -345,6 +416,7 @@ static void mesh_tick(void* arg) { struct UTUN_INSTANCE* D = t->inst[IDX_D]; struct UTUN_INSTANCE* C = t->inst[IDX_C]; uint64_t gid = strtoull(CH_ID, NULL, 10); + enum mesh_phase previous = t->phase; switch (t->phase) { case P_WAIT_CONN: @@ -383,11 +455,14 @@ static void mesh_tick(void* arg) { t->phase = P_DONE; break; case P_DONE: + mesh_snapshot(t, "original_stop"); t->result = 1; uasync_stop(t->ua); return; } + if (previous != t->phase) mesh_snapshot(t, "phase_change"); + if (++t->ticks > MAX_TICKS) { fprintf(stderr, "test: timeout in phase %d (B_rx=%d/%d fin=%d seqok=%d D_rx=%d/%d fin=%d seqok=%d C_rx=%d/%d fin=%d seqok=%d dupC=%u)\n", (int)t->phase, @@ -407,6 +482,7 @@ static void mesh_tick(void* arg) { int main(int argc, char** argv) { (void)argc; (void)argv; debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); + debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO); if (getenv("UTUN_TEST_DEBUG")) { debug_set_category_level_by_name("radio", "debug"); debug_set_category_level_by_name("bgp", "info"); @@ -457,6 +533,15 @@ int main(int argc, char** argv) { uint64_t gid = strtoull(CH_ID, NULL, 10); uint32_t dup_c = radio_dup_dropped(t.inst[IDX_C], gid); + /* Диагностический хвост не меняет сохранённый результат исходной проверки. */ + if (getenv("UTUN_TEST_MESH_TAIL")) { + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "mesh observe tail=500ms original_ok=%d original_dupC=%u", ok, dup_c); + void* tail = uasync_set_timeout(ua, 5000, ua, mesh_tail_stop, "mesh_diagnostic_tail"); + if (!tail) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "mesh: tail timer allocation failed"); abort(); } + uasync_mainloop(ua); + mesh_snapshot(&t, "after_diagnostic_tail"); + } + if (ok && dup_c == 0) { /* кадры не задвоились, но и дедупликация не сработала — топология не дала дублей */ printf("WARN: dupC=0 — diamond не породил дублей, кэш не проверен\n");