|
|
|
|
@ -13,6 +13,7 @@
|
|
|
|
|
#include "chat_core.h" |
|
|
|
|
#include "chat_sync.h" |
|
|
|
|
#include "chat_event.h" |
|
|
|
|
#include "chat_member.h" |
|
|
|
|
#include "member_sync.h" |
|
|
|
|
#include "../radio/radio.h" |
|
|
|
|
#include "../routing_layer/topo_node_sqlite.h" |
|
|
|
|
@ -59,6 +60,9 @@ enum rad_phase {
|
|
|
|
|
P_WAIT_OFF_C, |
|
|
|
|
P_TALK2, |
|
|
|
|
P_WAIT_RX2, |
|
|
|
|
P_REENABLE_C, |
|
|
|
|
P_WAIT_REENABLE_C, |
|
|
|
|
P_WITHDRAW_C, |
|
|
|
|
P_DONE, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
@ -89,6 +93,63 @@ struct rad_shared {
|
|
|
|
|
}; |
|
|
|
|
static struct rad_shared g_sh; |
|
|
|
|
static struct rx_evt g_rx[3]; |
|
|
|
|
static int g_member_radio[3][3]; |
|
|
|
|
static int g_member_lists[3]; |
|
|
|
|
static int g_member_event_bad; |
|
|
|
|
|
|
|
|
|
/* Проверяем именно UI-события: self, прямой пир и пир за посредником. */ |
|
|
|
|
static void member_event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len) { |
|
|
|
|
if (type != CHAT_EVT_MEMBER_UPDATED && type != CHAT_EVT_MEMBER_LIST) return; |
|
|
|
|
if (!data || len < 3) { g_member_event_bad = 1; return; } |
|
|
|
|
int cl = data[0]; |
|
|
|
|
if (cl != (int)strlen(CH_ID) || len < cl + 3 || memcmp(data + 1, CH_ID, cl)) return; |
|
|
|
|
int count = data[cl + 1]; |
|
|
|
|
int off = cl + 2; |
|
|
|
|
if (type == CHAT_EVT_MEMBER_LIST) { count |= data[cl + 2] << 8; off++; } |
|
|
|
|
if (len != off + count * CHAT_MEMBER_DISPLAY_SIZE) { g_member_event_bad = 1; return; } |
|
|
|
|
int dst = -1; |
|
|
|
|
for (int i = 0; i < 3; i++) if (inst->node_id == g_sh.nid[i]) dst = i; |
|
|
|
|
if (dst < 0) { g_member_event_bad = 1; return; } |
|
|
|
|
if (type == CHAT_EVT_MEMBER_LIST) g_member_lists[dst]++; |
|
|
|
|
for (int i = 0; i < count; i++) { |
|
|
|
|
struct chat_member_display member; |
|
|
|
|
if (chat_core_deserialize_member(data + off + i * CHAT_MEMBER_DISPLAY_SIZE, &member) < 0) { |
|
|
|
|
g_member_event_bad = 1; return; |
|
|
|
|
} |
|
|
|
|
for (int j = 0; j < 3; j++) |
|
|
|
|
if (member.node_id == g_sh.nid[j]) g_member_radio[dst][j] = !!(member.flags & CHAT_MEMBER_FLAG_RADIO); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Полный список, одиночная строка и текущие события должны совпадать. */ |
|
|
|
|
static int check_member_radios(struct UTUN_INSTANCE* inst, int dst, int c_radio) { |
|
|
|
|
uint8_t* wire = NULL; int count = 0; |
|
|
|
|
if (chat_core_get_member_list(inst, CH_ID, &wire, &count) < 0 || count != 3 || g_member_event_bad) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "test: invalid member list dst=%d count=%d event_bad=%d", dst, count, g_member_event_bad); |
|
|
|
|
u_free(wire); return 0; |
|
|
|
|
} |
|
|
|
|
int seen = 0; |
|
|
|
|
for (int i = 0; i < count; i++) { |
|
|
|
|
struct chat_member_display member; |
|
|
|
|
chat_core_deserialize_member(wire + i * CHAT_MEMBER_DISPLAY_SIZE, &member); |
|
|
|
|
for (int j = 0; j < 3; j++) { |
|
|
|
|
if (member.node_id != g_sh.nid[j]) continue; |
|
|
|
|
uint8_t single[CHAT_MEMBER_DISPLAY_SIZE]; |
|
|
|
|
int expected = j == IDX_C ? c_radio : 1; |
|
|
|
|
int radio = !!(member.flags & CHAT_MEMBER_FLAG_RADIO); |
|
|
|
|
if (radio != expected || g_member_radio[dst][j] != expected |
|
|
|
|
|| chat_core_get_single_member(inst, CH_ID, member.node_id, single) < 0 |
|
|
|
|
|| !!(single[10] & CHAT_MEMBER_FLAG_RADIO) != expected) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "test: member radio dst=%d src=%d list=%d event=%d expected=%d", |
|
|
|
|
dst, j, radio, g_member_radio[dst][j], expected); |
|
|
|
|
u_free(wire); return 0; |
|
|
|
|
} |
|
|
|
|
seen |= 1 << j; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
u_free(wire); |
|
|
|
|
return seen == 7; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int G_PASSED = 0, G_FAILED = 0, G_TOTAL = 0; |
|
|
|
|
#define TEST(n) do { G_TOTAL++; printf(" %-58s", n); fflush(stdout); } while(0) |
|
|
|
|
@ -325,6 +386,14 @@ static void rad_tick(void* arg) {
|
|
|
|
|
t->phase = P_TALK1; |
|
|
|
|
break; |
|
|
|
|
case P_TALK1: |
|
|
|
|
for (int i = 0; i < 3; i++) { |
|
|
|
|
if (!check_member_radios(t->inst[i], i, 1)) { t->result = 2; uasync_stop(t->ua); return; } |
|
|
|
|
struct chat_member_list_req* req = u_calloc(1, sizeof(*req)); |
|
|
|
|
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "test: member request OOM"); t->result = 2; uasync_stop(t->ua); return; } |
|
|
|
|
req->inst = t->inst[i]; strcpy(req->ch_id, CH_ID); |
|
|
|
|
chat_core_request_member_list_trampoline(req); |
|
|
|
|
if (g_member_lists[i] != 1) { t->result = 2; uasync_stop(t->ua); return; } |
|
|
|
|
} |
|
|
|
|
memset(g_rx, 0, sizeof(g_rx)); |
|
|
|
|
do_talk(A); |
|
|
|
|
t->phase = P_WAIT_RX1; |
|
|
|
|
@ -346,14 +415,40 @@ static void rad_tick(void* arg) {
|
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case P_TALK2: |
|
|
|
|
for (int i = 0; i < 3; i++) |
|
|
|
|
if (!check_member_radios(t->inst[i], i, 0)) { t->result = 2; uasync_stop(t->ua); return; } |
|
|
|
|
do_talk(A); |
|
|
|
|
t->phase = P_WAIT_RX2; |
|
|
|
|
break; |
|
|
|
|
case P_WAIT_RX2: |
|
|
|
|
if (g_rx[IDX_B].frames == N_FRAMES && g_rx[IDX_B].fin_seen && g_rx[IDX_B].seq_ok |
|
|
|
|
&& g_rx[IDX_C].frames == 0 && !g_rx[IDX_C].fin_seen) |
|
|
|
|
t->phase = P_DONE; |
|
|
|
|
t->phase = P_REENABLE_C; |
|
|
|
|
break; |
|
|
|
|
case P_REENABLE_C: |
|
|
|
|
radio_set_active(C, gid, 1); |
|
|
|
|
t->phase = P_WAIT_REENABLE_C; |
|
|
|
|
break; |
|
|
|
|
case P_WAIT_REENABLE_C: |
|
|
|
|
if (node_radio(A, nc) && node_radio(B, nc)) t->phase = P_WITHDRAW_C; |
|
|
|
|
break; |
|
|
|
|
case P_WITHDRAW_C: { |
|
|
|
|
struct TOPO_GROUP* group = topo_groups_find(A->topo_groups, gid); |
|
|
|
|
struct TOPO_GROUP_NODE* node = topo_node_find_by_id(group, nc); |
|
|
|
|
while (node && node->paths) { |
|
|
|
|
struct ETCP_CONN* from = ((struct TOPO_NODEPATH*)node->paths->head)->conn; |
|
|
|
|
struct TOPOMSG_WITHDRAW_PKT withdraw = { .node_id = nc }; |
|
|
|
|
if (topo_group_process_withdraw(group, from, (const uint8_t*)&withdraw, sizeof(withdraw)) < 0) { |
|
|
|
|
t->result = 2; uasync_stop(t->ua); return; |
|
|
|
|
} |
|
|
|
|
node = topo_node_find_by_id(group, nc); |
|
|
|
|
} |
|
|
|
|
if (!radio_is_active(C, gid) || !check_member_radios(A, IDX_A, 0)) { |
|
|
|
|
t->result = 2; uasync_stop(t->ua); return; |
|
|
|
|
} |
|
|
|
|
t->phase = P_DONE; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case P_DONE: |
|
|
|
|
t->result = 1; |
|
|
|
|
uasync_stop(t->ua); |
|
|
|
|
@ -397,7 +492,7 @@ int main(int argc, char** argv) {
|
|
|
|
|
|
|
|
|
|
printf("=== test_radio ===\n"); fflush(stdout); |
|
|
|
|
|
|
|
|
|
TEST("single-thread: radio flag + talk (seq/fin) + relay + prune (3 nodes)"); { |
|
|
|
|
TEST("radio: member snapshots/events + relay + off + withdraw"); { |
|
|
|
|
struct UASYNC* ua = uasync_create(); |
|
|
|
|
if (!ua) { FAIL("uasync_create failed"); return 1; } |
|
|
|
|
|
|
|
|
|
@ -422,6 +517,8 @@ int main(int argc, char** argv) {
|
|
|
|
|
utun_instance_init(t.inst[IDX_A]); |
|
|
|
|
utun_instance_init(t.inst[IDX_B]); |
|
|
|
|
utun_instance_init(t.inst[IDX_C]); |
|
|
|
|
memset(g_member_radio, -1, sizeof(g_member_radio)); |
|
|
|
|
for (int i = 0; i < 3; i++) chat_event_set_handler(t.inst[i], member_event); |
|
|
|
|
|
|
|
|
|
radio_set_frame_cb(t.inst[IDX_A], rx_frame_cb, &g_rx[IDX_A]); |
|
|
|
|
radio_set_frame_cb(t.inst[IDX_B], rx_frame_cb, &g_rx[IDX_B]); |
|
|
|
|
|