diff --git a/lib/ble/tests/test_ble_transport.c b/lib/ble/tests/test_ble_transport.c index ad522d513..6c509f00b 100644 --- a/lib/ble/tests/test_ble_transport.c +++ b/lib/ble/tests/test_ble_transport.c @@ -226,6 +226,13 @@ static void exercise(unsigned int test_mtu) { for (int i = 0; i < e->len; i++) CHECK(e->dgram[i] == 0x31); release(e); e = ble_rx_get(t[0], a); CHECK(e && e->len == 1499 && e->dgram[1498] == 0x71); release(e); + e = frame(BLE_FRAME_MAX + 1, 0x81); CHECK(ble_tx_put(t[0], a, e) == BLE_ERROR); release(e); + e = frame(BLE_INVITE_FRAME_MAX + 1, 0x82); + CHECK(ble_channel_tx_put(t[0], a, BLE_CHANNEL_INVITE, e) == BLE_ERROR); release(e); + CHECK(!ble_channel_tx_put(t[0], a, BLE_CHANNEL_INVITE, frame(BLE_INVITE_FRAME_MAX, 0x83))); drain(ua); + e = ble_channel_rx_get(t[1], b, BLE_CHANNEL_INVITE); CHECK(e && e->len == BLE_INVITE_FRAME_MAX); + for (int i = 0; i < e->len; i++) CHECK(e->dgram[i] == 0x83); + release(e); /* Два независимых канала одновременно, включая отказ TX и потерю фрагмента только INVITE. */ CHECK(ble_channel_tx_ready(t[0], a, BLE_CHANNEL_INVITE)); CHECK(!ble_tx_put(t[0], a, frame(1500, 0x61))); diff --git a/tools/cgathui_common/tests/test_ble_etcp.c b/tools/cgathui_common/tests/test_ble_etcp.c index 3ba718e1e..e08bedd50 100644 --- a/tools/cgathui_common/tests/test_ble_etcp.c +++ b/tools/cgathui_common/tests/test_ble_etcp.c @@ -13,6 +13,9 @@ #include "../../../src/chat/chat_local.h" #include "../../../src/chat/chat_event.h" #include "../../../src/chat/chat_setting.h" +#include "../../../src/chat/chat_core.h" +#include "../../../src/chat/chat_join.h" +#include "../../../src/chat/chat_sync.h" #include "../../../src/chat/invite_link.h" #include "../../../src/local_transport/protocol.h" #include @@ -337,6 +340,67 @@ static void invitation_without_etcp(void) { uasync_destroy(ua, 0); puts("PASS BLE invitations: connect on demand, empty name, invitation delivery without ETCP/UTUN link"); } +static int bootstrap_result; +static void bootstrap_event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len) { + (void)inst; + if (type == CHAT_EVT_CONNECT_RESULT) { assert(len == 28); memcpy(&bootstrap_result, data + 8, 4); } +} + +/* JOIN проходит без IP и без ETCP до подписанного членства, затем тот же GATT становится групповым транспортом. */ +static void bootstrap_join(unsigned att_mtu, int reverse) { + struct UTUN_INSTANCE* inst[2]; struct ble_etcp* driver[2]; + ua = uasync_create(); assert(ua); created = hold = drop = 0; mtu = att_mtu; + for (int i = 0; i < 2; i++) { + inst[i] = utun_instance_create_from_str(ua, "[global]\nauto_sockets=no\n[ntp]\nenabled=no\n" + "[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n"); + assert(inst[i] && !utun_core_start(inst[i]) && !chat_service_start(inst[i])); + struct ble_config c = {.node_id = inst[i]->node_id, .company_id = 0xffff}; + driver[i] = ble_etcp_create(ua, inst[i], &c, 1); assert(driver[i]); assert(!ble_start(ble_etcp_radio(driver[i]))); + } + uint64_t group = chat_core_create_channel_auto(inst[0], "BLE bootstrap"); assert(group); + assert(!chat_join_store_local_key(inst[0], group, 123)); + chat_event_set_handler(inst[1], bootstrap_event); drain(); + for (int i = 0; i < 2; i++) { + uint8_t bytes[24]; int len = ble_identity_encode(bytes, inst[1-i]->node_id, "bootstrap"); + struct ble_endpoint ep = {0}; snprintf(ep.id, sizeof(ep.id), "%d", 1-i); + ble_backend_broadcast(backends[i]->owner, &ep, bytes, len, -30); + } + drain(); + if (reverse) { assert(!ble_etcp_connect(driver[0], inst[1]->node_id)); drain(); } + struct InviteData invite = {.channelId = group, .join_key = 124, .addrCount = 1}; + memcpy(invite.pubkey, inst[0]->my_keys.public_key, 32); + invite.addrs[0] = (struct InviteAddr){.family = 4, .address = {127,0,0,1}, .proto = INVITE_PROTO_UDP, .port = 9}; + for (int attempt = 0; attempt < 2; attempt++) { + char link[2048]; assert(invite_link_encode(&invite, NULL, link, sizeof(link)) > 0); + bootstrap_result = -999; assert(!chat_sync_join_link(inst[1], attempt + 1, link, strlen(link))); + uint64_t end = get_time_us() + 10000000; + while (bootstrap_result == -999 && get_time_us() < end) { + if (!topo_groups_peer_key_known(inst[0], inst[1]->my_keys.public_key)) { + assert(!instance_find_conn(inst[0], inst[1]->node_id)); + assert(!instance_find_conn(inst[1], inst[0]->node_id)); + } + uasync_poll(ua, 10); + } + assert(bootstrap_result != -999); + if (!attempt) { + assert(bootstrap_result != 0 && !topo_groups_peer_key_known(inst[0], inst[1]->my_keys.public_key)); + end = get_time_us() + 100000; while (get_time_us() < end) uasync_poll(ua, 10); + invite.join_key = 123; + } else assert(bootstrap_result == 0); + } + assert(topo_groups_peer_key_known(inst[0], inst[1]->my_keys.public_key)); + assert(topo_groups_peer_key_known(inst[1], inst[0]->my_keys.public_key)); + struct TOPO_GROUP* g[2] = {topo_groups_find(inst[0]->topo_groups, group), topo_groups_find(inst[1]->topo_groups, group)}; + assert(g[0] && g[1]); + uint64_t end = get_time_us() + 10000000; + while (get_time_us() < end && (!topo_group_peer_ready(g[0], inst[1]->node_id) || + !topo_group_peer_ready(g[1], inst[0]->node_id))) uasync_poll(ua, 10); + assert(topo_group_peer_ready(g[0], inst[1]->node_id) && topo_group_peer_ready(g[1], inst[0]->node_id)); + for (int i = 0; i < 2; i++) ble_etcp_destroy(driver[i]); + drain(); for (int i = 0; i < 2; i++) utun_instance_destroy(inst[i]); uasync_destroy(ua, 0); + printf("PASS BLE bootstrap JOIN: MTU=%u reverse=%d invalid key, retry, signed membership, ETCP and group READY\n", att_mtu, reverse); +} + int main(void) { debug_config_init(); debug_set_level(getenv("UTUN_TEST_DEBUG") ? DEBUG_LEVEL_DEBUG : DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO); @@ -417,6 +481,8 @@ int main(void) { reject_key(); assert(u_get_allocated_count() == baseline); invitation_without_etcp(); assert(u_get_allocated_count() == baseline); group_fallback(); + bootstrap_join(23, 0); + bootstrap_join(517, 1); puts("PASS BLE ETCP: discovery, encrypted handshake, MTU23 fragmentation, 1500-byte messages, backpressure, teardown"); return 0; }