diff --git a/lib/ble/ble_bluez.c b/lib/ble/ble_bluez.c index f343141cb..1933d3ae1 100644 --- a/lib/ble/ble_bluez.c +++ b/lib/ble/ble_bluez.c @@ -57,7 +57,8 @@ struct ble_backend { }; static void bus_update(struct ble_backend* b); static void bus_timer(void* arg); -static void query_link(struct bluez_link* l); +static int query_link(struct bluez_link* l); +static int connected(sd_bus_message* m, void* arg, sd_bus_error* error); static void beacon_update(struct ble_backend* b); static void fail(struct ble_backend* b, const char* where, int error) { if (b->failed) return; @@ -135,7 +136,8 @@ static struct bluez_link* incoming(struct ble_backend* b, const char* device) { return l; } static void link_error(struct bluez_link* l, const char* reason) { - DEBUG_WARN(CAT, "BLE BlueZ link=%llu failed: %s", (unsigned long long)l->id, reason); + DEBUG_WARN(CAT, "BLE BlueZ link=%llu failed: %s device=%s outgoing=%d connected=%d resolved=%d opened=%d", + (unsigned long long)l->id, reason, l->device, l->outgoing, l->connected, l->resolved, l->opened); ble_backend_closed(l->backend->owner, l->id, reason); } static void notify_read(int fd, void* arg) { @@ -408,7 +410,8 @@ static int properties(struct ble_backend* b, const char* path, const char* inter if (resolved >= 0) l->resolved = resolved; DEBUG_DEBUG(CAT, "BLE BlueZ readiness link=%llu connected=%d resolved=%d opened=%d", (unsigned long long)l->id, l->connected, l->resolved, l->opened); - if (l->connected && l->resolved && !l->opened && !l->query) query_link(l); + if (l->connected && l->resolved && !l->opened && !l->query && query_link(l) < 0) + link_error(l, "discovery request failed"); } if (live && have_data && strlen(path) < BLE_ENDPOINT_MAX) { struct ble_endpoint ep = {0}; strcpy(ep.id, path); ble_backend_broadcast(b->owner, &ep, data, data_size, rssi); @@ -456,6 +459,17 @@ static int queried(sd_bus_message* m, void* arg, sd_bus_error* error) { if (r < 0) { link_error(l, "service discovery failed"); return 0; } DEBUG_DEBUG(CAT, "BLE BlueZ GATT snapshot link=%llu connected=%d resolved=%d RX=%d TX=%d INVITE=%d ACK=%d mtu=%u known=%d", (unsigned long long)l->id, l->connected, l->resolved, !!l->rx[0], !!l->tx[0], !!l->invite[0], !!l->ack[0], l->mtu, l->mtu_known); + /* Connect уже подключённого dual-mode Device1 может запустить BR/EDR вместо использования готового LE. */ + if (!l->connected && !l->connect) { + DEBUG_INFO(CAT, "BLE BlueZ Connect requested link=%llu device=%s: no existing connection", (unsigned long long)l->id, l->device); + r = sd_bus_call_method_async(l->backend->bus, &l->connect, "org.bluez", l->device, DEVICE_IF, "Connect", connected, l, ""); + if (r < 0) { + DEBUG_ERROR(CAT, "BLE Connect request link=%llu: %s", (unsigned long long)l->id, strerror(-r)); + link_error(l, "connect request failed"); + } + return 0; + } + if (l->connected) DEBUG_DEBUG(CAT, "BLE BlueZ reusing connection link=%llu resolved=%d", (unsigned long long)l->id, l->resolved); if (l->connected && l->resolved && l->rx[0] && l->tx[0] && l->invite[0] && l->ack[0] && !l->subscribe && !l->opened) { if (!l->mtu_known) DEBUG_WARN(CAT, "BLE BlueZ MTU unavailable link=%llu: using ATT minimum 23", (unsigned long long)l->id); r = sd_bus_call_method_async(l->backend->bus, &l->subscribe, "org.bluez", l->tx, CHAR_IF, "StartNotify", subscribed, l, ""); @@ -463,16 +477,19 @@ static int queried(sd_bus_message* m, void* arg, sd_bus_error* error) { } return 0; } -static void query_link(struct bluez_link* l) { - if (l->query || l->subscribe || l->opened) return; +/* Сначала snapshot: существующее LE соединение не требует повторного Device1.Connect. */ +static int query_link(struct bluez_link* l) { + if (l->query || l->subscribe || l->opened) return 0; int r = sd_bus_call_method_async(l->backend->bus, &l->query, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", queried, l, ""); - if (r < 0) link_error(l, "discovery request failed"); + if (r < 0) DEBUG_ERROR(CAT, "BLE discovery request link=%llu: %s", (unsigned long long)l->id, strerror(-r)); + return r; } static int connected(sd_bus_message* m, void* arg, sd_bus_error* error) { (void)error; struct bluez_link* l = arg; l->connect = sd_bus_slot_unref(l->connect); if (response_error(m, "Connect")) { link_error(l, "connect failed"); return 0; } - query_link(l); return 0; + if (query_link(l) < 0) link_error(l, "discovery request failed"); + return 0; } static int signal_changed(sd_bus_message* m, void* arg, sd_bus_error* error) { (void)error; struct ble_backend* b = arg; const char* iface; @@ -685,8 +702,8 @@ int ble_backend_connect(struct ble_backend* b, ble_link_id id, const struct ble_ } struct bluez_link* l = alloc_link(b, id, ep->id, 1); if (!l) { DEBUG_WARN(CAT, "BLE BlueZ duplicate/limit endpoint=%s", ep->id); return BLE_ERROR; } - int r = sd_bus_call_method_async(b->bus, &l->connect, "org.bluez", l->device, DEVICE_IF, "Connect", connected, l, ""); - if (r < 0) { DEBUG_ERROR(CAT, "BLE Connect request: %s", strerror(-r)); ble_backend_close(b, id); return BLE_ERROR; } + int r = query_link(l); + if (r < 0) { ble_backend_close(b, id); return BLE_ERROR; } bus_update(b); return BLE_OK; } static int written(sd_bus_message* m, void* arg, sd_bus_error* error) { diff --git a/lib/ble/tests/test_ble_bluez.c b/lib/ble/tests/test_ble_bluez.c index 7585090b4..7b6662e9c 100644 --- a/lib/ble/tests/test_ble_bluez.c +++ b/lib/ble/tests/test_ble_bluez.c @@ -23,7 +23,8 @@ static int ready, broadcasts, ready_links, advertisement_count, closed_links; static ble_link_id links[4]; static unsigned commands, requests, ack_requests, ack_values; static int ack_fds[2] = {-1, -1}; -static int remote_resolved; +static int remote_resolved, remote_connected = 1; +static unsigned remote_connects; struct reply { sd_bus_slot* slot; int done, fd, failed; }; static void poll_all(void) { for (int i = 0; i < 500; i++) { @@ -105,10 +106,17 @@ static int remote_state(sd_bus* b, const char* path, const char* i, const char* sd_bus_message* m, void* arg, sd_bus_error* e) { (void)b; (void)path; (void)i; (void)arg; (void)e; if (!strcmp(name, "ManufacturerData")) return sd_bus_message_append(m, "a{qv}", 1, 0xffff, "ay", 3, 'a', 'b', 'c'); - return sd_bus_message_append(m, "b", !strcmp(name, "Connected") || remote_resolved); + return sd_bus_message_append(m, "b", !strcmp(name, "Connected") ? remote_connected : remote_resolved); +} +/* Повторный Connect существующего ACL недопустим: BlueZ может начать другой bearer (BR/EDR). */ +static int remote_connect(sd_bus_message* m, void* arg, sd_bus_error* e) { + (void)arg; (void)e; CHECK(!remote_connected); + remote_connects++; remote_connected = 1; + CHECK(sd_bus_emit_properties_changed(bus, REMOTE, "org.bluez.Device1", "Connected", NULL) >= 0); + return sd_bus_reply_method_return(m, ""); } static const sd_bus_vtable device_vtable[] = { - SD_BUS_VTABLE_START(0), SD_BUS_METHOD("Connect", "", "", simple, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_START(0), SD_BUS_METHOD("Connect", "", "", remote_connect, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_PROPERTY("Connected", "b", remote_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("ServicesResolved", "b", remote_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("ManufacturerData", "a{qv}", remote_state, 0, 0), @@ -272,18 +280,29 @@ int main(void) { struct ble_endpoint ep = {REMOTE}; CHECK(ble_connect(transport, &ep, 3)); poll_all(); /* Кеш UUID доступен сразу, но до ServicesResolved нельзя объявлять GATT готовым с прежним MTU23. */ - CHECK(ready_links == 3 && commands == 0); + CHECK(ready_links == 3 && commands == 0 && remote_connects == 0); remote_resolved = 1; CHECK(sd_bus_emit_properties_changed(bus, REMOTE, "org.bluez.Device1", "ServicesResolved", NULL) >= 0); poll_all(); CHECK(ready_links == 4 && 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 && ack_requests >= 1); /* MTU517 и ACK Write Command. */ + /* Новый ACL требует ровно одного Connect; discovery ждёт ServicesResolved. */ + ble_disconnect(transport, links[3]); poll_all(); remote_connected = remote_resolved = 0; + CHECK(ble_connect(transport, &ep, 3)); poll_all(); + CHECK(remote_connects == 1 && ready_links == 4 && commands == 2); + remote_resolved = 1; + CHECK(sd_bus_emit_properties_changed(bus, REMOTE, "org.bluez.Device1", "ServicesResolved", NULL) >= 0); + poll_all(); CHECK(remote_connects == 1 && ready_links == 5 && commands == 3); + /* Следующий GATT на уже готовом ACL сразу использует snapshot без Connect. */ + ble_disconnect(transport, links[3]); poll_all(); + CHECK(ble_connect(transport, &ep, 3)); poll_all(); + CHECK(remote_connects == 1 && ready_links == 6 && commands == 4); ble_stop(transport); power = ble_get_advertising_power(transport); CHECK(power.max_dbm == 127 && power.can_set == -1); ble_destroy(transport); uasync_destroy(ua, 0); 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/ACK commands and notifications, INVITE indications, two clients, teardown)"); + puts("BlueZ private-bus tests PASS (existing/new ACL, DATA/ACK commands, INVITE indications, two clients, teardown)"); return 0; }