Add the mandatory ACK characteristic to BlueZ

This commit is contained in:
evgeny committed 2026-10-29 22:10:24 +01:00
1 parent 4e38536a55
commit cecdbbe1d7
2 files changed
+55 -37

No files matched your search

+48 -33
View File
@@ -1,6 +1,6 @@
/* BlueZ D-Bus через sd-bus (без Qt/GLib). Один uasync-поток, без блокирующих вызовов.
* Серверные DATA notifications и INVITE indications адресуются через AcquireNotify fd каждого клиента.
* DATA завершается после send(fd); только INVITE ждёт байт 1 от BlueZ (ATT completion). */
* Серверные DATA notifications и INVITE/ACK indications адресуются через AcquireNotify fd каждого клиента.
* DATA завершается после send(fd); INVITE/ACK ждут байт 1 от BlueZ (ATT completion). */
#include "ble_backend.h"
#include "../u_async.h"
#include "../mem.h"
@@ -20,6 +20,7 @@
#define RX SERVICE "/rx"
#define TX SERVICE "/tx"
#define INVITE SERVICE "/invite"
#define ACK SERVICE "/ack"
#define ADV ROOT "/advertisement"
#define BEACON ROOT "/beacon"
#define DEVICE_IF "org.bluez.Device1"
@@ -27,10 +28,10 @@
struct bluez_link {
struct ble_backend* backend;
ble_link_id id;
char device[BLE_ENDPOINT_MAX], rx[BLE_ENDPOINT_MAX], tx[BLE_ENDPOINT_MAX], invite[BLE_ENDPOINT_MAX];
char device[BLE_ENDPOINT_MAX], rx[BLE_ENDPOINT_MAX], tx[BLE_ENDPOINT_MAX], invite[BLE_ENDPOINT_MAX], ack[BLE_ENDPOINT_MAX];
sd_bus_slot *connect, *query, *subscribe, *write;
int fd[BLE_CHANNEL_COUNT], outgoing, opened, sending, buffered, subscribed, channel, connected, resolved, mtu_known;
void* io[BLE_CHANNEL_COUNT];
int fd[BLE_WIRE_COUNT], outgoing, opened, sending, buffered, subscribed, channel, connected, resolved, mtu_known;
void* io[BLE_WIRE_COUNT];
unsigned int mtu;
uint8_t value[BLE_VALUE_MAX];
size_t value_size;
@@ -41,8 +42,8 @@ struct ble_backend {
struct ble_transport* owner;
struct UASYNC* ua;
sd_bus* bus;
sd_bus_slot *setup, *signals, *objects, *removed, *owner_watch, *service, *rx, *tx, *invite, *manager, *adv, *beacon, *beacon_op;
struct characteristic rx_context, tx_context, invite_context;
sd_bus_slot *setup, *signals, *objects, *removed, *owner_watch, *service, *rx, *tx, *invite, *ack, *manager, *adv, *beacon, *beacon_op;
struct characteristic rx_context, tx_context, invite_context, ack_context;
struct advertisement adv_context, beacon_context;
struct bluez_link links[BLE_LINKS_MAX];
char adapter[BLE_ENDPOINT_MAX], adapter_name[64], daemon_owner[BLE_ENDPOINT_MAX];
@@ -113,7 +114,9 @@ static struct bluez_link* alloc_link(struct ble_backend* b, ble_link_id id, cons
if (strlen(device) >= BLE_ENDPOINT_MAX || find_device(b, device)) return NULL;
for (int i = 0; i < BLE_LINKS_MAX; i++) if (!b->links[i].id) {
struct bluez_link* l = &b->links[i]; memset(l, 0, sizeof(*l));
l->backend = b; l->id = id; l->fd[0] = l->fd[1] = -1; l->mtu = 23; l->outgoing = outgoing; strcpy(l->device, device); return l;
l->backend = b; l->id = id;
for (int ch = 0; ch < BLE_WIRE_COUNT; ch++) l->fd[ch] = -1;
l->mtu = 23; l->outgoing = outgoing; strcpy(l->device, device); return l;
}
return NULL;
}
@@ -143,7 +146,7 @@ static void notify_read(int fd, void* arg) {
link_error(l, "notification socket read failed"); return;
}
if (!n) { link_error(l, "notification subscription closed"); return; }
if (n != 1 || data[0] != 1 || fd != l->fd[BLE_CHANNEL_INVITE] || l->channel != BLE_CHANNEL_INVITE ||
if (n != 1 || data[0] != 1 || l->channel == BLE_WIRE_DATA || fd != l->fd[l->channel] ||
!l->sending || l->buffered) {
DEBUG_WARN(CAT, "BLE indication completion invalid fd=%d expected_fd=%d bytes=%zd sending=%d buffered=%d",
fd, l->fd[l->channel], n, l->sending, l->buffered);
@@ -163,7 +166,7 @@ static void notify_write(int fd, void* arg) {
link_error(l, "notification socket write failed"); return;
}
l->buffered = 0; uasync_set_socket_write(l->backend->ua, l->io[l->channel], 0);
if (l->channel == BLE_CHANNEL_DATA) {
if (l->channel == BLE_WIRE_DATA) {
l->sending = 0;
ble_backend_sent(l->backend->owner, l->id, 1);
}
@@ -200,7 +203,8 @@ static int write_value(sd_bus_message* m, void* arg, sd_bus_error* error) {
struct bluez_link* l = incoming(b, device);
if (!l) return reject(m, "connection unavailable");
ble_link_id id = l->id;
ble_backend_received(b->owner, id, ctx->tx == 2 ? BLE_CHANNEL_INVITE : BLE_CHANNEL_DATA, data, len);
enum ble_wire_channel channel = ctx->tx >= 2 ? (enum ble_wire_channel)(ctx->tx - 1) : BLE_WIRE_DATA;
ble_backend_received(b->owner, id, channel, data, len);
if (!find_link(b, id)) return reject(m, "session rejected");
int r = sd_bus_reply_method_return(m, "");
if (r < 0) DEBUG_ERROR(CAT, "BLE BlueZ WriteValue reply failed link=%llu: %s", (unsigned long long)id, strerror(-r));
@@ -212,7 +216,7 @@ static int acquire_notify(sd_bus_message* m, void* arg, sd_bus_error* error) {
if (!sd_bus_message_get_sender(m) || strcmp(sd_bus_message_get_sender(m), b->daemon_owner)) return reject(m, "sender is not BlueZ");
if (!ctx->tx || options(m, &device, &mtu, &offset) < 0 || !device || offset || mtu < 23) return reject(m, "invalid AcquireNotify");
struct bluez_link* l = incoming(b, device);
int channel = ctx->tx == 2 ? BLE_CHANNEL_INVITE : BLE_CHANNEL_DATA;
int channel = ctx->tx >= 2 ? ctx->tx - 1 : BLE_WIRE_DATA;
if (!l || l->fd[channel] >= 0) return reject(m, "subscription unavailable");
int pair[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, pair) < 0) {
@@ -228,8 +232,8 @@ static int acquire_notify(sd_bus_message* m, void* arg, sd_bus_error* error) {
link_error(l, "AcquireNotify reply failed"); return r;
}
DEBUG_DEBUG(CAT, "BLE BlueZ subscription link=%llu channel=%d mode=%s mtu=%u",
(unsigned long long)l->id, channel, channel == BLE_CHANNEL_DATA ? "notification" : "indication", mtu);
if (l->fd[0] >= 0 && l->fd[1] >= 0 && !l->opened) {
(unsigned long long)l->id, channel, channel == BLE_WIRE_DATA ? "notification" : "indication", mtu);
if (l->fd[0] >= 0 && l->fd[1] >= 0 && l->fd[2] >= 0 && !l->opened) {
l->opened = 1; ble_backend_opened(b->owner, l->id, mtu);
}
bus_update(b); return r;
@@ -242,9 +246,10 @@ static int get_service(sd_bus* bus, const char* path, const char* interface, con
static int get_char(sd_bus* bus, const char* path, const char* interface, const char* property,
sd_bus_message* reply, void* arg, sd_bus_error* error) {
(void)bus; (void)path; (void)interface; (void)error; struct characteristic* ctx = arg;
if (!strcmp(property, "UUID")) return sd_bus_message_append(reply, "s", ctx->tx == 2 ? BLE_INVITE_UUID : ctx->tx ? BLE_TX_UUID : BLE_RX_UUID);
if (!strcmp(property, "UUID")) return sd_bus_message_append(reply, "s",
ctx->tx == 3 ? BLE_ACK_UUID : ctx->tx == 2 ? BLE_INVITE_UUID : ctx->tx ? BLE_TX_UUID : BLE_RX_UUID);
if (!strcmp(property, "Service")) return sd_bus_message_append(reply, "o", SERVICE);
if (!strcmp(property, "Flags") && ctx->tx == 2) return sd_bus_message_append(reply, "as", 2, "write", "indicate");
if (!strcmp(property, "Flags") && ctx->tx >= 2) return sd_bus_message_append(reply, "as", 2, "write", "indicate");
if (!strcmp(property, "Flags")) return sd_bus_message_append(reply, "as", 1, ctx->tx ? "notify" : "write-without-response");
return sd_bus_message_append(reply, "b", 0); /* NotifyAcquired объявляет поддержку адресного fd API. */
}
@@ -313,10 +318,11 @@ static const sd_bus_vtable beacon_table[] = {
static int subscribed(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct bluez_link* l = arg; l->subscribe = sd_bus_slot_unref(l->subscribe);
if (response_error(m, "StartNotify")) { link_error(l, "subscription failed"); return 0; }
if (++l->subscribed < BLE_CHANNEL_COUNT) {
int r = sd_bus_call_method_async(l->backend->bus, &l->subscribe, "org.bluez", l->invite, CHAR_IF,
if (++l->subscribed < BLE_WIRE_COUNT) {
const char* path = l->subscribed == BLE_WIRE_INVITE ? l->invite : l->ack;
int r = sd_bus_call_method_async(l->backend->bus, &l->subscribe, "org.bluez", path, CHAR_IF,
"StartNotify", subscribed, l, "");
if (r < 0) link_error(l, "invitation subscription failed");
if (r < 0) link_error(l, "control subscription failed");
return 0;
}
DEBUG_INFO(CAT, "BLE BlueZ DATA configured link=%llu write=without_response receive=notification mtu=%u; PHY/interval managed by OS/peer",
@@ -359,8 +365,10 @@ static int properties(struct ble_backend* b, const char* path, const char* inter
const void* bytes; size_t size; r = sd_bus_message_read_array(m, 'y', &bytes, &size);
if (r >= 0) for (int i = 0; i < BLE_LINKS_MAX; i++) {
struct bluez_link* l = &b->links[i];
if (size && l->id && l->outgoing && (!strcmp(l->tx, path) || !strcmp(l->invite, path)))
ble_backend_received(b->owner, l->id, !strcmp(l->invite, path) ? BLE_CHANNEL_INVITE : BLE_CHANNEL_DATA, bytes, size);
if (live && size && l->id && l->opened && l->outgoing &&
(!strcmp(l->tx, path) || !strcmp(l->invite, path) || !strcmp(l->ack, path)))
ble_backend_received(b->owner, l->id, !strcmp(l->ack, path) ? BLE_WIRE_ACK :
!strcmp(l->invite, path) ? BLE_WIRE_INVITE : BLE_WIRE_DATA, bytes, size);
}
} else r = sd_bus_message_skip(m, type);
if (r < 0) return r;
@@ -396,6 +404,7 @@ static int properties(struct ble_backend* b, const char* path, const char* inter
}
if (!strcmp(uuid, BLE_TX_UUID)) strcpy(l->tx, path);
if (!strcmp(uuid, BLE_INVITE_UUID)) strcpy(l->invite, path);
if (!strcmp(uuid, BLE_ACK_UUID)) strcpy(l->ack, path);
}
return 0;
}
@@ -426,9 +435,9 @@ static int queried(sd_bus_message* m, void* arg, sd_bus_error* error) {
l->query = sd_bus_slot_unref(l->query);
if (!l->id) return 0;
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 mtu=%u known=%d",
(unsigned long long)l->id, l->connected, l->resolved, !!l->rx[0], !!l->tx[0], !!l->invite[0], l->mtu, l->mtu_known);
if (l->connected && l->resolved && l->rx[0] && l->tx[0] && l->invite[0] && !l->subscribe && !l->opened) {
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);
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, "");
if (r < 0) link_error(l, "StartNotify request failed");
@@ -466,7 +475,8 @@ static int object_removed(sd_bus_message* m, void* arg, sd_bus_error* error) {
if (!strcmp(path, b->adapter)) { fail(b, "Bluetooth adapter removed", ENODEV); return 0; }
for (int i = 0; i < BLE_LINKS_MAX; i++) {
struct bluez_link* l = &b->links[i];
if (l->id && (!strcmp(path, l->device) || !strcmp(path, l->rx) || !strcmp(path, l->tx) || !strcmp(path, l->invite))) link_error(l, "BlueZ object removed");
if (l->id && (!strcmp(path, l->device) || !strcmp(path, l->rx) || !strcmp(path, l->tx) ||
!strcmp(path, l->invite) || !strcmp(path, l->ack))) link_error(l, "BlueZ object removed");
}
return 0;
}
@@ -531,6 +541,7 @@ struct ble_backend* ble_backend_create(struct ble_transport* owner, struct UASYN
if (cfg->adapter) snprintf(b->adapter_name, sizeof(b->adapter_name), "%s", cfg->adapter);
b->rx_context.backend = b; b->tx_context.backend = b; b->tx_context.tx = 1;
b->invite_context.backend = b; b->invite_context.tx = 2;
b->ack_context.backend = b; b->ack_context.tx = 3;
b->adv_context.backend = b; b->beacon_context.backend = b; b->beacon_context.beacon = 1;
int r = sd_bus_open_system(&b->bus);
if (r >= 0) r = sd_bus_add_object_manager(b->bus, &b->manager, ROOT);
@@ -538,6 +549,7 @@ struct ble_backend* ble_backend_create(struct ble_transport* owner, struct UASYN
if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->rx, RX, CHAR_IF, rx_table, &b->rx_context);
if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->tx, TX, CHAR_IF, tx_table, &b->tx_context);
if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->invite, INVITE, CHAR_IF, invite_table, &b->invite_context);
if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->ack, ACK, CHAR_IF, invite_table, &b->ack_context);
if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->adv, ADV, "org.bluez.LEAdvertisement1", adv_table, &b->adv_context);
if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->beacon, BEACON, "org.bluez.LEAdvertisement1", beacon_table, &b->beacon_context);
if (r >= 0) r = sd_bus_add_match_async(b->bus, &b->signals,
@@ -559,13 +571,14 @@ void ble_backend_close(struct ble_backend* b, ble_link_id id) {
struct bluez_link* l = find_link(b, id); if (!l) return;
l->connect = sd_bus_slot_unref(l->connect); l->query = sd_bus_slot_unref(l->query);
l->subscribe = sd_bus_slot_unref(l->subscribe); l->write = sd_bus_slot_unref(l->write);
for (int channel = 0; channel < BLE_CHANNEL_COUNT; channel++) {
for (int channel = 0; channel < BLE_WIRE_COUNT; channel++) {
if (l->io[channel]) uasync_remove_socket(b->ua, l->io[channel]);
if (l->fd[channel] >= 0) close(l->fd[channel]);
}
/* Не разрываем общий Device1 ACL: он может принадлежать другим профилям/приложениям. */
if (l->outgoing && !b->failed) for (int channel = 0; channel < l->subscribed; channel++) {
int r = sd_bus_call_method_async(b->bus, NULL, "org.bluez", channel ? l->invite : l->tx, CHAR_IF, "StopNotify", NULL, NULL, "");
const char* path = channel == BLE_WIRE_ACK ? l->ack : channel == BLE_WIRE_INVITE ? l->invite : l->tx;
int r = sd_bus_call_method_async(b->bus, NULL, "org.bluez", path, CHAR_IF, "StopNotify", NULL, NULL, "");
if (r < 0) DEBUG_WARN(CAT, "BLE StopNotify failed: %s", strerror(-r));
}
memset(l, 0, sizeof(*l)); bus_update(b);
@@ -578,7 +591,8 @@ void ble_backend_destroy(struct ble_backend* b) {
if (b->io) uasync_remove_socket(b->ua, b->io);
sd_bus_slot_unref(b->setup); sd_bus_slot_unref(b->signals); sd_bus_slot_unref(b->objects); sd_bus_slot_unref(b->removed);
sd_bus_slot_unref(b->owner_watch);
sd_bus_slot_unref(b->service); sd_bus_slot_unref(b->rx); sd_bus_slot_unref(b->tx); sd_bus_slot_unref(b->invite); sd_bus_slot_unref(b->manager);
sd_bus_slot_unref(b->service); sd_bus_slot_unref(b->rx); sd_bus_slot_unref(b->tx); sd_bus_slot_unref(b->invite);
sd_bus_slot_unref(b->ack); sd_bus_slot_unref(b->manager);
sd_bus_slot_unref(b->adv); sd_bus_slot_unref(b->beacon); sd_bus_slot_unref(b->beacon_op);
/* Закрытие приватного bus освобождает регистрации/scan сессии в BlueZ по owner-disconnect. */
sd_bus_close_unref(b->bus); u_free(b);
@@ -607,9 +621,9 @@ static int written(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct bluez_link* l = arg; l->write = sd_bus_slot_unref(l->write); l->sending = 0;
ble_backend_sent(l->backend->owner, l->id, !response_error(m, "WriteValue")); return 0;
}
int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_channel channel, const uint8_t* data, size_t len) {
int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_wire_channel channel, const uint8_t* data, size_t len) {
struct bluez_link* l = find_link(b, id);
if ((unsigned)channel >= BLE_CHANNEL_COUNT || !l || !l->opened || l->sending || !len || len > BLE_VALUE_MAX) {
if ((unsigned)channel >= BLE_WIRE_COUNT || !l || !l->opened || l->sending || !len || len > BLE_VALUE_MAX) {
DEBUG_ERROR(CAT, "BLE invalid backend send link=%llu", (unsigned long long)id); return BLE_ERROR;
}
l->channel = channel;
@@ -618,13 +632,14 @@ int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_channel cha
uasync_set_socket_write(b->ua, l->io[channel], 1); return BLE_OK;
}
sd_bus_message* m = NULL;
int r = sd_bus_message_new_method_call(b->bus, &m, "org.bluez", channel == BLE_CHANNEL_INVITE ? l->invite : l->rx, CHAR_IF, "WriteValue");
const char* path = channel == BLE_WIRE_ACK ? l->ack : channel == BLE_WIRE_INVITE ? l->invite : l->rx;
int r = sd_bus_message_new_method_call(b->bus, &m, "org.bluez", path, CHAR_IF, "WriteValue");
if (r >= 0) r = sd_bus_message_append_array(m, 'y', data, len);
if (r >= 0) r = sd_bus_message_append(m, "a{sv}", 1, "type", "s", channel == BLE_CHANNEL_DATA ? "command" : "request");
if (r >= 0) r = sd_bus_message_append(m, "a{sv}", 1, "type", "s", channel == BLE_WIRE_DATA ? "command" : "request");
if (r >= 0) r = sd_bus_call_async(b->bus, &l->write, m, written, l, 10000000);
sd_bus_message_unref(m);
if (r < 0) { DEBUG_ERROR(CAT, "BLE WriteValue request: %s", strerror(-r)); return BLE_ERROR; }
DEBUG_TRACE(CAT, "BLE WriteValue submitted link=%llu channel=%d type=%s bytes=%zu", (unsigned long long)id, channel,
channel == BLE_CHANNEL_DATA ? "command" : "request", len);
channel == BLE_WIRE_DATA ? "command" : "request", len);
l->sending = 1; bus_update(b); return BLE_OK;
}
+7 -4
View File
@@ -7,7 +7,7 @@
#include <stdlib.h>
#include <string.h>
#define CHECK(v) do { if (!(v)) { fprintf(stderr, "FAIL line=%d: %s\n", __LINE__, #v); exit(1); } } while (0)
struct fake_link { ble_link_id id, other; struct ble_backend* peer; uint8_t data[512]; size_t len; int pending; enum ble_channel channel; };
struct fake_link { ble_link_id id, other; struct ble_backend* peer; uint8_t data[512]; size_t len; int pending; enum ble_wire_channel channel; };
struct ble_backend { struct ble_transport* owner; struct UASYNC* ua; void* task; int index, started; struct fake_link links[16]; };
static struct ble_backend* backends[3];
static unsigned int mtu = 23;
@@ -41,7 +41,7 @@ int ble_backend_connect(struct ble_backend* b, ble_link_id id, const struct ble_
void ble_backend_close(struct ble_backend* b, ble_link_id id) {
struct fake_link* f = slot(b, id); if (f) memset(f, 0, sizeof(*f));
}
int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_channel channel, const uint8_t* data, size_t len) {
int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_wire_channel channel, const uint8_t* data, size_t len) {
struct fake_link* f = slot(b, id); CHECK(f && !f->pending && len <= sizeof(f->data));
CHECK(len <= mtu - 3);
f->channel = channel; memcpy(f->data, data, len); f->len = len; f->pending = 1; schedule(b); return 0;
@@ -56,7 +56,7 @@ static void run(void* arg) {
ble_link_id id = f->id, other = f->other;
struct ble_backend* peer = f->peer;
uint8_t data[512]; size_t len = f->len; memcpy(data, f->data, len); f->pending = 0;
int dropped = data[0] == 3 && drop && --drop == 0;
int dropped = f->channel != BLE_WIRE_ACK && data[f->channel == BLE_WIRE_DATA ? 2 : 0] == 3 && drop && --drop == 0;
if (!dropped && peer && slot(peer, other)) ble_backend_received(peer->owner, other, f->channel, data, len);
if (slot(b, id)) ble_backend_sent(b->owner, id, 1);
}
@@ -66,7 +66,10 @@ static void connection(void* arg, ble_link_id id, uint64_t node, enum ble_state
(void)arg; (void)id; (void)node; (void)reason; if (s == BLE_READY) ready_count++;
}
static void writable(void* arg, ble_link_id id) { (void)arg; (void)id; writable_count++; }
static void drain(struct UASYNC* ua) { for (int i = 0; i < 1000; i++) uasync_poll(ua, 0); }
static void drain(struct UASYNC* ua) {
uint64_t end = get_time_us() + 2 * BLE_ACK_INTERVAL_US;
do { uasync_poll(ua, 10); } while (get_time_us() < end);
}
static struct ll_entry* frame(unsigned int size, uint8_t value) {
struct ll_entry* e = ll_alloc_lldgram(size); CHECK(e); e->len = size; memset(e->dgram, value, size); return e;
}