From 85977d97e078f99e1d04738c8bc0753f94ce17b9 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 22:13:46 +0100 Subject: [PATCH] Verify mandatory ACK subscriptions and addressed BlueZ ACKs --- lib/ble/ble_winrt.cpp | 2 +- lib/ble/tests/test_ble_bluez.c | 79 ++++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/lib/ble/ble_winrt.cpp b/lib/ble/ble_winrt.cpp index 3656039d..b0fcacf5 100644 --- a/lib/ble/ble_winrt.cpp +++ b/lib/ble/ble_winrt.cpp @@ -254,7 +254,7 @@ struct WinState : std::enable_shared_from_this { auto* l = incoming(client.Session()); if (!l) continue; l->subscriber = client; l->invite_subscriber = *invitation; l->ack_subscriber = *acknowledgement; active.push_back(l->id); if (!l->opened) { - auto capacity = std::min({client.MaxNotificationSize(), invitation->MaxNotificationSize(), acknowledgement->MaxNotificationSize()}); + auto capacity = std::min({client.MaxNotificationSize(), (*invitation).MaxNotificationSize(), (*acknowledgement).MaxNotificationSize()}); DEBUG_DEBUG(CAT, "BLE WinRT subscribed DATA/INVITE/ACK link=%llu capacity=%u", (unsigned long long)l->id, capacity); l->opened = true; ble_backend_opened(owner, l->id, capacity + 3); } diff --git a/lib/ble/tests/test_ble_bluez.c b/lib/ble/tests/test_ble_bluez.c index 736537e3..12d29527 100644 --- a/lib/ble/tests/test_ble_bluez.c +++ b/lib/ble/tests/test_ble_bluez.c @@ -1,4 +1,4 @@ -/* Private D-Bus + настоящий backend: DATA без ACK, адресные INVITE indications и два клиента. */ +/* Private D-Bus + настоящий backend: DATA без ATT ACK, адресные INVITE/ACK indications, два клиента. */ #include "ble_backend.h" #include "u_async.h" #include "mem.h" @@ -21,11 +21,19 @@ static struct ble_transport* transport; static char application[256]; static int ready, broadcasts, ready_links, advertisement_count; static ble_link_id links[4]; -static unsigned commands, requests; +static unsigned commands, requests, ack_requests, ack_values; +static int ack_fds[2] = {-1, -1}; static int remote_resolved; struct reply { sd_bus_slot* slot; int done, fd, failed; }; static void poll_all(void) { - for (int i = 0; i < 500; i++) { CHECK(sd_bus_process(bus, NULL) >= 0); uasync_poll(ua, 10); } + for (int i = 0; i < 500; i++) { + CHECK(sd_bus_process(bus, NULL) >= 0); uasync_poll(ua, 10); + for (int j = 0; j < 2; j++) if (ack_fds[j] >= 0) { + uint8_t ack[8]; ssize_t n = recv(ack_fds[j], ack, sizeof(ack), MSG_DONTWAIT); + if (n < 0) { CHECK(errno == EAGAIN); continue; } + CHECK(n == 2); ack_values++; CHECK(send(ack_fds[j], "\1", 1, MSG_NOSIGNAL) == 1); + } + } } static int register_app(sd_bus_message* m, void* arg, sd_bus_error* e) { (void)arg; (void)e; snprintf(application, sizeof(application), "%s", sd_bus_message_get_sender(m)); @@ -55,7 +63,8 @@ static int remote_property(sd_bus* b, const char* path, const char* i, const cha sd_bus_message* m, void* arg, sd_bus_error* e) { (void)b; (void)i; (void)arg; (void)e; if (!strcmp(name, "MTU")) return sd_bus_message_append(m, "q", remote_resolved ? 517 : 23); - const char* uuid = strstr(path, "/rx") ? BLE_RX_UUID : strstr(path, "/tx") ? BLE_TX_UUID : BLE_INVITE_UUID; + const char* uuid = strstr(path, "/rx") ? BLE_RX_UUID : strstr(path, "/tx") ? BLE_TX_UUID : + strstr(path, "/ack") ? BLE_ACK_UUID : BLE_INVITE_UUID; return sd_bus_message_append(m, "s", uuid); } static int remote_write(sd_bus_message* m, void* arg, sd_bus_error* e) { @@ -68,14 +77,15 @@ static int remote_write(sd_bus_message* m, void* arg, sd_bus_error* e) { CHECK(sd_bus_message_enter_container(m, 'v', "s") > 0); CHECK(sd_bus_message_read(m, "s", &type) > 0); int invitation = strstr(sd_bus_message_get_path(m), "/invite") != NULL; - CHECK(!strcmp(type, invitation ? "request" : "command")); - if (invitation) requests++; else commands++; + int ack = strstr(sd_bus_message_get_path(m), "/ack") != NULL; + CHECK(!strcmp(type, invitation || ack ? "request" : "command")); + if (ack) { CHECK(len == 2); ack_requests++; } else if (invitation) requests++; else commands++; int r = sd_bus_reply_method_return(m, ""); - if (!invitation && ((const uint8_t*)data)[0] == 1) { - sd_bus_message* signal = NULL; uint8_t accept[17] = {2}; accept[8] = 3; accept[16] = 200; + if (!invitation && !ack && len == 19 && ((const uint8_t*)data)[2] == 1) { + sd_bus_message* signal = NULL; uint8_t accept[19] = {0, 1, 2}; accept[10] = 3; accept[18] = 200; CHECK(sd_bus_message_new_signal(bus, &signal, REMOTE "/tx", "org.freedesktop.DBus.Properties", "PropertiesChanged") >= 0); - CHECK(sd_bus_message_append(signal, "sa{sv}as", "org.bluez.GattCharacteristic1", 1, "Value", "ay", 17, - accept[0],0,0,0,0,0,0,0,accept[8],0,0,0,0,0,0,0,accept[16], 0) >= 0); + CHECK(sd_bus_message_append(signal, "sa{sv}as", "org.bluez.GattCharacteristic1", 1, "Value", "ay", 19, + 0,1,2,0,0,0,0,0,0,0,accept[10],0,0,0,0,0,0,0,accept[18], 0) >= 0); CHECK(sd_bus_send(bus, signal, NULL) >= 0); sd_bus_message_unref(signal); } return r; @@ -120,9 +130,10 @@ static int replied(sd_bus_message* m, void* arg, sd_bus_error* error) { } reply->done = 1; return 0; } -static int acquire(const char* device, int invitation) { +static int acquire(const char* device, int channel) { struct reply reply = {.fd=-1}; - CHECK(sd_bus_call_method_async(bus, &reply.slot, application, invitation ? ROOT "/service0/invite" : ROOT "/service0/tx", "org.bluez.GattCharacteristic1", + const char* path = channel == 2 ? ROOT "/service0/ack" : channel ? ROOT "/service0/invite" : ROOT "/service0/tx"; + CHECK(sd_bus_call_method_async(bus, &reply.slot, application, path, "org.bluez.GattCharacteristic1", "AcquireNotify", replied, &reply, "a{sv}", 2, "device", "o", device, "mtu", "q", 23) >= 0); poll_all(); CHECK(reply.done && !reply.failed && reply.fd >= 0); sd_bus_slot_unref(reply.slot); return reply.fd; } @@ -142,9 +153,9 @@ static void announce(void) { CHECK(sd_bus_send(bus, m, NULL) >= 0); sd_bus_message_unref(m); poll_all(); CHECK(broadcasts == 1); } static void hello(const char* device, int fd, uint8_t node) { - uint8_t data[17] = {1}; data[8] = node; data[16] = 200; - write_value(device, data, sizeof(data), 0); - CHECK(recv(fd, data, sizeof(data), MSG_DONTWAIT) == 17); CHECK(data[0] == 2 && data[8] == 200 && data[16] == node); + uint8_t data[32] = {0, 1, 1}; data[10] = node; data[18] = 200; + write_value(device, data, 19, 0); + CHECK(recv(fd, data, sizeof(data), MSG_DONTWAIT) == 19); CHECK(data[2] == 2 && data[10] == 200 && data[18] == node); poll_all(); /* ACCEPT отправляется Notification: подтверждения от клиента нет. */ } static struct ll_entry* frame(int value) { @@ -155,14 +166,14 @@ int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); size_t before = u_get_allocated_count(); CHECK(sd_bus_open_user(&bus) >= 0); CHECK(sd_bus_request_name(bus, "org.bluez", 0) >= 0); - sd_bus_slot *manager = NULL, *adapter = NULL, *gatt = NULL, *adv = NULL, *remote[4] = {0}; + sd_bus_slot *manager = NULL, *adapter = NULL, *gatt = NULL, *adv = NULL, *remote[5] = {0}; CHECK(sd_bus_add_object_manager(bus, &manager, "/") >= 0); CHECK(sd_bus_add_object_vtable(bus, &adapter, ADAPTER, "org.bluez.Adapter1", adapter_vtable, NULL) >= 0); CHECK(sd_bus_add_object_vtable(bus, &gatt, ADAPTER, "org.bluez.GattManager1", gatt_vtable, NULL) >= 0); CHECK(sd_bus_add_object_vtable(bus, &adv, ADAPTER, "org.bluez.LEAdvertisingManager1", adv_vtable, NULL) >= 0); CHECK(sd_bus_add_object_vtable(bus, &remote[0], REMOTE, "org.bluez.Device1", device_vtable, NULL) >= 0); - const char* paths[] = {REMOTE "/rx", REMOTE "/tx", REMOTE "/invite"}; - for (int i = 0; i < 3; i++) + const char* paths[] = {REMOTE "/rx", REMOTE "/tx", REMOTE "/invite", REMOTE "/ack"}; + for (int i = 0; i < 4; i++) CHECK(sd_bus_add_object_vtable(bus, &remote[i+1], paths[i], "org.bluez.GattCharacteristic1", remote_vtable, NULL) >= 0); ua = uasync_create(); CHECK(ua); struct ble_config config = {0}; config.node_id = 200; config.company_id = 0xffff; @@ -173,23 +184,25 @@ int main(void) { announce(); int fd1 = acquire(ADAPTER "/dev_A", 0), fd2 = acquire(ADAPTER "/dev_B", 0); int invite1 = acquire(ADAPTER "/dev_A", 1), invite2 = acquire(ADAPTER "/dev_B", 1); + CHECK(ready_links == 0); /* Без обязательной ACK-подписки opened не наступает. */ + ack_fds[0] = acquire(ADAPTER "/dev_A", 2); ack_fds[1] = acquire(ADAPTER "/dev_B", 2); hello(ADAPTER "/dev_A", fd1, 1); hello(ADAPTER "/dev_B", fd2, 2); CHECK(ready_links == 2); CHECK(!ble_tx_put(transport, links[1], frame(0xa1))); CHECK(!ble_tx_put(transport, links[2], frame(0xb2))); poll_all(); uint8_t data[64]; /* Все DATA-фрагменты обоим клиентам проходят без единого ATT ACK. */ - for (int offset = 0; offset < 31; offset += 13) { - CHECK(recv(fd1, data, sizeof(data), MSG_DONTWAIT) == (offset == 26 ? 12 : 20)); - CHECK(data[6] == offset && data[7] == 0xa1); - CHECK(recv(fd2, data, sizeof(data), MSG_DONTWAIT) == (offset == 26 ? 12 : 20)); - CHECK(data[6] == offset && data[7] == 0xb2); + for (int offset = 0; offset < 31; offset += 11) { + CHECK(recv(fd1, data, sizeof(data), MSG_DONTWAIT) == (offset == 22 ? 18 : 20)); + CHECK(data[8] == offset && data[9] == 0xa1); + CHECK(recv(fd2, data, sizeof(data), MSG_DONTWAIT) == (offset == 22 ? 18 : 20)); + CHECK(data[8] == offset && data[9] == 0xb2); } CHECK(recv(fd1, data, sizeof(data), MSG_DONTWAIT) < 0 && errno == EAGAIN); CHECK(recv(fd2, data, sizeof(data), MSG_DONTWAIT) < 0 && errno == EAGAIN); - uint8_t incoming[] = {3, 0, 1, 0, 3, 0, 0, 0x11, 0x22, 0x33}; + uint8_t incoming[] = {0, 2, 3, 0, 1, 0, 3, 0, 0, 0x11, 0x22, 0x33}; write_value(ADAPTER "/dev_B", incoming, sizeof(incoming), 0); CHECK(!ble_rx_get(transport, links[1])); struct ll_entry* e = ble_rx_get(transport, links[2]); CHECK(e && e->len == 3); - CHECK(!memcmp(e->dgram, incoming + 7, 3)); queue_dgram_free(e); queue_entry_free(e); - write_value(ADAPTER "/dev_A", incoming, sizeof(incoming), 1); + CHECK(!memcmp(e->dgram, incoming + 9, 3)); queue_dgram_free(e); queue_entry_free(e); + write_value(ADAPTER "/dev_A", incoming + 2, sizeof(incoming) - 2, 1); CHECK(!ble_rx_get(transport, links[1])); CHECK(!ble_channel_rx_get(transport, links[2], BLE_CHANNEL_INVITE)); e = ble_channel_rx_get(transport, links[1], BLE_CHANNEL_INVITE); CHECK(e && e->len == 3); queue_dgram_free(e); queue_entry_free(e); @@ -202,12 +215,14 @@ int main(void) { poll_all(); /* INVITE не продвигается без подтверждения; другой клиент свободен. */ CHECK(recv(invite2, data, sizeof(data), MSG_DONTWAIT) < 0 && errno == EAGAIN); CHECK(!ble_tx_put(transport, links[1], frame(0xd4))); poll_all(); - for (int part = 0; part < 31; part += 13) { - CHECK(recv(fd1, data, sizeof(data), MSG_DONTWAIT) == (part == 26 ? 12 : 20)); - CHECK(data[6] == part && data[7] == 0xd4); + for (int part = 0; part < 31; part += 11) { + CHECK(recv(fd1, data, sizeof(data), MSG_DONTWAIT) == (part == 22 ? 18 : 20)); + CHECK(data[8] == part && data[9] == 0xd4); } CHECK(send(invite2, "\1", 1, MSG_NOSIGNAL) == 1); poll_all(); } + CHECK(ack_values >= 2); + close(ack_fds[0]); close(ack_fds[1]); ack_fds[0] = ack_fds[1] = -1; close(invite1); close(invite2); close(fd1); poll_all(); close(fd2); poll_all(); /* Настоящий клиент backend: HELLO/DATA используют command, INVITE использует request. */ @@ -220,11 +235,11 @@ int main(void) { poll_all(); CHECK(ready_links == 3 && commands == 1); CHECK(!ble_tx_put(transport, links[3], frame(0xe5))); CHECK(!ble_channel_tx_put(transport, links[3], BLE_CHANNEL_INVITE, frame(0xe6))); - poll_all(); CHECK(commands == 2 && requests == 1); /* После resolve используется актуальный MTU517. */ + poll_all(); CHECK(commands == 2 && requests == 1 && ack_requests >= 1); /* MTU517 и ACK Write Request. */ ble_destroy(transport); uasync_destroy(ua, 0); - for (int i = 0; i < 4; i++) sd_bus_slot_unref(remote[i]); + for (int i = 0; i < 5; i++) sd_bus_slot_unref(remote[i]); sd_bus_slot_unref(manager); sd_bus_slot_unref(adapter); sd_bus_slot_unref(gatt); sd_bus_slot_unref(adv); sd_bus_close_unref(bus); CHECK(u_get_allocated_count() == before); - puts("BlueZ private-bus tests PASS (DATA without ACK, addressed INVITE indications, two clients, RX, teardown)"); + puts("BlueZ private-bus tests PASS (DATA commands, addressed INVITE/ACK indications, mandatory ACK, two clients, teardown)"); return 0; }