Files
utun2/lib/ble/ble_bluez.c
T

631 lines
39 KiB
C

/* BlueZ D-Bus через sd-bus (без Qt/GLib). Один uasync-поток, без блокирующих вызовов.
* Серверные DATA notifications и INVITE indications адресуются через AcquireNotify fd каждого клиента.
* DATA завершается после send(fd); только INVITE ждёт байт 1 от BlueZ (ATT completion). */
#include "ble_backend.h"
#include "../u_async.h"
#include "../mem.h"
#include "../debug_config.h"
#include <systemd/sd-bus.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <time.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#define CAT DEBUG_CATEGORY_DISCOVERY
#define ROOT "/org/vibechat/ble"
#define SERVICE ROOT "/service0"
#define RX SERVICE "/rx"
#define TX SERVICE "/tx"
#define INVITE SERVICE "/invite"
#define ADV ROOT "/advertisement"
#define BEACON ROOT "/beacon"
#define DEVICE_IF "org.bluez.Device1"
#define CHAR_IF "org.bluez.GattCharacteristic1"
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];
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];
unsigned int mtu;
uint8_t value[BLE_VALUE_MAX];
size_t value_size;
};
struct characteristic { struct ble_backend* backend; int tx; };
struct advertisement { struct ble_backend* backend; int beacon; };
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;
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];
uint16_t company;
uint8_t data[BLE_BROADCAST_MAX], next_data[BLE_BROADCAST_MAX];
size_t data_size, next_size;
int beacon_registered, beacon_desired, beacon_action, step, failed, processing;
void *io, *timer;
};
static void bus_update(struct ble_backend* b);
static void bus_timer(void* arg);
static void query_link(struct bluez_link* l);
static void beacon_update(struct ble_backend* b);
static void fail(struct ble_backend* b, const char* where, int error) {
if (b->failed) return;
b->failed = 1;
DEBUG_ERROR(CAT, "BLE BlueZ %s: %s (%d)", where, strerror(error < 0 ? -error : error), error);
ble_backend_status(b->owner, 0, where);
}
static int response_error(sd_bus_message* m, const char* where) {
const sd_bus_error* e = sd_bus_message_get_error(m);
if (!e) return 0;
DEBUG_ERROR(CAT, "BLE BlueZ %s: %s: %s", where, e->name, e->message ? e->message : ""); return 1;
}
static void bus_process(struct ble_backend* b) {
if (b->processing || b->failed) return;
b->processing = 1;
int r = 0;
for (int i = 0; i < 64; i++) { r = sd_bus_process(b->bus, NULL); if (r <= 0 || b->failed) break; }
b->processing = 0;
if (r < 0) { fail(b, "D-Bus disconnected", r); return; }
bus_update(b);
if (r > 0 && !b->failed) {
DEBUG_DEBUG(CAT, "BLE D-Bus drain budget exhausted; continuing buffered messages");
if (b->timer) uasync_cancel_timeout(b->ua, b->timer);
b->timer = uasync_set_timeout(b->ua, 0, b, bus_timer, "ble_dbus_drain");
if (!b->timer) fail(b, "D-Bus drain allocation", ENOMEM);
}
}
static void bus_io(int fd, void* arg) { (void)fd; bus_process(arg); }
static void bus_timer(void* arg) { struct ble_backend* b = arg; b->timer = NULL; bus_process(b); }
static void bus_update(struct ble_backend* b) {
if (b->processing || b->failed) return;
int events = sd_bus_get_events(b->bus);
uint64_t timeout = UINT64_MAX;
int r = sd_bus_get_timeout(b->bus, &timeout);
if (events < 0 || r < 0) { fail(b, "D-Bus polling failed", events < 0 ? events : r); return; }
uasync_set_socket_read(b->ua, b->io, !!(events & POLLIN));
uasync_set_socket_write(b->ua, b->io, !!(events & POLLOUT));
if (b->timer) { uasync_cancel_timeout(b->ua, b->timer); b->timer = NULL; }
if (timeout != UINT64_MAX) {
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t now = (uint64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
uint64_t delay = timeout > now ? (timeout - now + 99) / 100 : 0;
b->timer = uasync_set_timeout(b->ua, delay > INT_MAX ? INT_MAX : (int)delay, b, bus_timer, "ble_dbus");
if (!b->timer) fail(b, "D-Bus timer allocation", ENOMEM);
}
}
static struct bluez_link* find_link(struct ble_backend* b, ble_link_id id) {
for (int i = 0; i < BLE_LINKS_MAX; i++) if (b->links[i].id == id && id) return &b->links[i];
return NULL;
}
static struct bluez_link* find_device(struct ble_backend* b, const char* device) {
for (int i = 0; i < BLE_LINKS_MAX; i++) if (b->links[i].id && !strcmp(b->links[i].device, device)) return &b->links[i];
return NULL;
}
static struct bluez_link* alloc_link(struct ble_backend* b, ble_link_id id, const char* device, int outgoing) {
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;
}
return NULL;
}
static struct bluez_link* incoming(struct ble_backend* b, const char* device) {
struct bluez_link* l = find_device(b, device);
if (l) return l->outgoing ? NULL : l;
struct ble_endpoint ep = {0};
if (strlen(device) >= sizeof(ep.id)) return NULL;
strcpy(ep.id, device);
ble_link_id id = ble_backend_incoming(b->owner, &ep);
if (!id) return NULL;
l = alloc_link(b, id, device, 0);
if (!l) ble_backend_closed(b->owner, id, "BlueZ link allocation failed");
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);
ble_backend_closed(l->backend->owner, l->id, reason);
}
static void notify_read(int fd, void* arg) {
struct bluez_link* l = arg;
uint8_t data[8]; ssize_t n = recv(fd, data, sizeof(data), MSG_DONTWAIT);
if (n < 0 && (errno == EAGAIN || errno == EINTR)) return;
if (n < 0) {
DEBUG_ERROR(CAT, "BLE notification socket read failed link=%llu fd=%d errno=%d: %s",
(unsigned long long)l->id, fd, errno, strerror(errno));
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 ||
!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);
link_error(l, "invalid indication completion"); return;
}
l->sending = 0;
ble_backend_sent(l->backend->owner, l->id, 1);
}
static void notify_write(int fd, void* arg) {
struct bluez_link* l = arg;
if (!l->buffered) { uasync_set_socket_write(l->backend->ua, l->io[l->channel], 0); return; }
ssize_t n = send(fd, l->value, l->value_size, MSG_DONTWAIT | MSG_NOSIGNAL);
if (n < 0 && (errno == EAGAIN || errno == EINTR)) return;
if (n != (ssize_t)l->value_size) {
DEBUG_ERROR(CAT, "BLE notification socket write failed link=%llu channel=%d expected=%zu sent=%zd errno=%d",
(unsigned long long)l->id, l->channel, l->value_size, n, n < 0 ? errno : 0);
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) {
l->sending = 0;
ble_backend_sent(l->backend->owner, l->id, 1);
}
}
static int options(sd_bus_message* m, const char** device, uint16_t* mtu, uint16_t* offset) {
int r = sd_bus_message_enter_container(m, 'a', "{sv}");
if (r < 0) return r;
const char *key, *type;
while ((r = sd_bus_message_enter_container(m, 'e', "sv")) > 0) {
if ((r = sd_bus_message_read(m, "s", &key)) < 0 || (r = sd_bus_message_peek_type(m, NULL, &type)) < 0) return r;
if ((r = sd_bus_message_enter_container(m, 'v', type)) < 0) return r;
if (!strcmp(key, "device") && !strcmp(type, "o")) r = sd_bus_message_read(m, "o", device);
else if (!strcmp(key, "mtu") && !strcmp(type, "q")) r = sd_bus_message_read(m, "q", mtu);
else if (!strcmp(key, "offset") && !strcmp(type, "q")) r = sd_bus_message_read(m, "q", offset);
else r = sd_bus_message_skip(m, type);
if (r < 0) return r;
sd_bus_message_exit_container(m); sd_bus_message_exit_container(m);
}
if (r < 0) return r;
return sd_bus_message_exit_container(m);
}
static int reject(sd_bus_message* m, const char* reason) {
DEBUG_WARN(CAT, "BLE BlueZ request rejected: %s", reason);
int r = sd_bus_reply_method_errorf(m, "org.bluez.Error.Failed", "%s", reason);
if (r < 0) DEBUG_ERROR(CAT, "BLE BlueZ error reply failed: %s", strerror(-r));
return r;
}
static int write_value(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct characteristic* ctx = arg; struct ble_backend* b = ctx->backend;
const void* data; size_t len; const char* device = NULL; uint16_t mtu = 23, offset = 0;
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 == 1 || sd_bus_message_read_array(m, 'y', &data, &len) < 0 || options(m, &device, &mtu, &offset) < 0 ||
!device || offset || !len || len > BLE_VALUE_MAX || mtu < 23) return reject(m, "invalid WriteValue");
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);
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));
return r;
}
static int acquire_notify(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct characteristic* ctx = arg; struct ble_backend* b = ctx->backend;
const char* device = NULL; uint16_t mtu = 23, offset = 0;
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;
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) {
DEBUG_ERROR(CAT, "BLE notification socketpair failed: %s", strerror(errno)); return reject(m, "socketpair failed");
}
l->fd[channel] = pair[0]; l->mtu = mtu;
l->io[channel] = uasync_add_socket(b->ua, l->fd[channel], notify_read, notify_write, notify_read, "ble_notify", l);
if (!l->io[channel]) { close(pair[0]); close(pair[1]); l->fd[channel] = -1; return reject(m, "fd registration failed"); }
uasync_set_socket_write(b->ua, l->io[channel], 0);
int r = sd_bus_reply_method_return(m, "hq", pair[1], mtu); close(pair[1]);
if (r < 0) {
DEBUG_ERROR(CAT, "BLE BlueZ AcquireNotify reply failed link=%llu: %s", (unsigned long long)l->id, strerror(-r));
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) {
l->opened = 1; ble_backend_opened(b->owner, l->id, mtu);
}
bus_update(b); return r;
}
static int get_service(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)arg; (void)error;
return !strcmp(property, "UUID") ? sd_bus_message_append(reply, "s", BLE_SERVICE_UUID) : sd_bus_message_append(reply, "b", 1);
}
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, "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")) 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. */
}
static int get_adv(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 advertisement* a = arg; struct ble_backend* b = a->backend;
if (!strcmp(property, "Type")) return sd_bus_message_append(reply, "s", "peripheral");
if (!strcmp(property, "ServiceUUIDs")) return sd_bus_message_append(reply, "as", 1, BLE_SERVICE_UUID);
int r = sd_bus_message_open_container(reply, 'a', "{qv}");
if (r >= 0) r = sd_bus_message_open_container(reply, 'e', "qv");
if (r >= 0) r = sd_bus_message_append(reply, "q", b->company);
if (r >= 0) r = sd_bus_message_open_container(reply, 'v', "ay");
if (r >= 0) r = sd_bus_message_append_array(reply, 'y', b->data, b->data_size);
if (r >= 0) r = sd_bus_message_close_container(reply);
if (r >= 0) r = sd_bus_message_close_container(reply);
if (r >= 0) r = sd_bus_message_close_container(reply);
return r;
}
static int adv_release(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct advertisement* a = arg;
DEBUG_WARN(CAT, "BLE advertisement released by BlueZ beacon=%d", a->beacon);
if (a->beacon) {
a->backend->beacon_registered = 0;
ble_backend_advertising(a->backend->owner, BLE_CLOSED, "advertisement released");
}
else fail(a->backend, "connectable advertisement released", EIO);
return sd_bus_reply_method_return(m, "");
}
static const sd_bus_vtable service_table[] = {
SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("UUID", "s", get_service, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Primary", "b", get_service, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END
};
static const sd_bus_vtable rx_table[] = {
SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("UUID", "s", get_char, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Service", "o", get_char, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Flags", "as", get_char, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_METHOD("WriteValue", "aya{sv}", "", write_value, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END
};
static const sd_bus_vtable tx_table[] = {
SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("UUID", "s", get_char, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Service", "o", get_char, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Flags", "as", get_char, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NotifyAcquired", "b", get_char, 0, 0),
SD_BUS_METHOD("AcquireNotify", "a{sv}", "hq", acquire_notify, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END
};
static const sd_bus_vtable invite_table[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("WriteValue", "aya{sv}", "", write_value, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_PROPERTY("UUID", "s", get_char, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Service", "o", get_char, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Flags", "as", get_char, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NotifyAcquired", "b", get_char, 0, 0),
SD_BUS_METHOD("AcquireNotify", "a{sv}", "hq", acquire_notify, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END
};
static const sd_bus_vtable adv_table[] = {
SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Type", "s", get_adv, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ServiceUUIDs", "as", get_adv, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_METHOD("Release", "", "", adv_release, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END
};
static const sd_bus_vtable beacon_table[] = {
SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Type", "s", get_adv, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ManufacturerData", "a{qv}", get_adv, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_METHOD("Release", "", "", adv_release, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END
};
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,
"StartNotify", subscribed, l, "");
if (r < 0) link_error(l, "invitation 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",
(unsigned long long)l->id, l->mtu);
l->opened = 1; ble_backend_opened(l->backend->owner, l->id, l->mtu); return 0;
}
/* Snapshot содержит также старые объявления; только live-событие обновляет discovery/last_seen. */
static int properties(struct ble_backend* b, const char* path, const char* interface, sd_bus_message* m, int live) {
const char *key, *type, *uuid = NULL;
int r, resolved = -1, connected = -1, rssi = 127, have_data = 0;
uint16_t mtu = 0;
uint8_t data[BLE_BROADCAST_MAX]; size_t data_size = 0;
if ((r = sd_bus_message_enter_container(m, 'a', "{sv}")) < 0) return r;
while ((r = sd_bus_message_enter_container(m, 'e', "sv")) > 0) {
if ((r = sd_bus_message_read(m, "s", &key)) < 0 || (r = sd_bus_message_peek_type(m, NULL, &type)) < 0) return r;
if ((r = sd_bus_message_enter_container(m, 'v', type)) < 0) return r;
if (!strcmp(interface, DEVICE_IF) && !strcmp(key, "ManufacturerData") && !strcmp(type, "a{qv}")) {
sd_bus_message_enter_container(m, 'a', "{qv}");
while ((r = sd_bus_message_enter_container(m, 'e', "qv")) > 0) {
uint16_t company; const char* value_type;
sd_bus_message_read(m, "q", &company); sd_bus_message_peek_type(m, NULL, &value_type);
sd_bus_message_enter_container(m, 'v', value_type);
if (company == b->company && !strcmp(value_type, "ay")) {
const void* bytes; size_t size;
r = sd_bus_message_read_array(m, 'y', &bytes, &size);
if (r >= 0 && size <= sizeof(data)) { memcpy(data, bytes, size); data_size = size; have_data = 1; }
else DEBUG_WARN(CAT, "BLE rejected advertisement size=%zu", r < 0 ? 0 : size);
} else sd_bus_message_skip(m, value_type);
sd_bus_message_exit_container(m); sd_bus_message_exit_container(m);
}
sd_bus_message_exit_container(m);
} else if (!strcmp(interface, DEVICE_IF) && !strcmp(key, "RSSI") && !strcmp(type, "n")) {
int16_t value; r = sd_bus_message_read(m, "n", &value); rssi = value;
} else if (!strcmp(interface, DEVICE_IF) && !strcmp(type, "b") && (!strcmp(key, "Connected") || !strcmp(key, "ServicesResolved"))) {
int value; r = sd_bus_message_read(m, "b", &value);
if (!strcmp(key, "Connected")) connected = value; else resolved = value;
} else if (!strcmp(interface, CHAR_IF) && !strcmp(key, "UUID") && !strcmp(type, "s")) r = sd_bus_message_read(m, "s", &uuid);
else if (!strcmp(interface, CHAR_IF) && !strcmp(key, "MTU") && !strcmp(type, "q")) r = sd_bus_message_read(m, "q", &mtu);
else if (!strcmp(interface, CHAR_IF) && !strcmp(key, "Value") && !strcmp(type, "ay")) {
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);
}
} else r = sd_bus_message_skip(m, type);
if (r < 0) return r;
sd_bus_message_exit_container(m); sd_bus_message_exit_container(m);
}
if (r < 0) return r;
sd_bus_message_exit_container(m);
if (!strcmp(interface, "org.bluez.Adapter1") && !b->adapter[0]) {
const char* name = strrchr(path, '/');
if (strlen(path) < sizeof(b->adapter) && (!b->adapter_name[0] || (name && !strcmp(name + 1, b->adapter_name)))) strcpy(b->adapter, path);
}
if (!strcmp(interface, DEVICE_IF)) {
struct bluez_link* l = find_device(b, path);
if (l && ((connected == 0 && (!l->outgoing || l->connected)) || (resolved == 0 && l->resolved)))
link_error(l, connected == 0 ? "device disconnected" : "GATT services invalidated");
else if (l && l->outgoing && (connected >= 0 || resolved >= 0)) {
if (connected >= 0) l->connected = connected;
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 (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);
}
}
if (uuid) for (int i = 0; i < BLE_LINKS_MAX; i++) {
struct bluez_link* l = &b->links[i]; size_t prefix = strlen(l->device);
if (!l->id || !l->outgoing || l->opened || strncmp(path, l->device, prefix) || path[prefix] != '/' || strlen(path) >= BLE_ENDPOINT_MAX) continue;
if (!strcmp(uuid, BLE_RX_UUID)) {
strcpy(l->rx, path);
if (mtu >= 23) { l->mtu = mtu; l->mtu_known = 1; }
}
if (!strcmp(uuid, BLE_TX_UUID)) strcpy(l->tx, path);
if (!strcmp(uuid, BLE_INVITE_UUID)) strcpy(l->invite, path);
}
return 0;
}
static int interfaces(struct ble_backend* b, const char* path, sd_bus_message* m, int live) {
int r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); const char* iface;
if (r < 0) return r;
while ((r = sd_bus_message_enter_container(m, 'e', "sa{sv}")) > 0) {
if ((r = sd_bus_message_read(m, "s", &iface)) < 0 || (r = properties(b, path, iface, m, live)) < 0) return r;
sd_bus_message_exit_container(m);
}
if (r < 0) return r;
return sd_bus_message_exit_container(m);
}
static int managed_objects(struct ble_backend* b, sd_bus_message* m) {
int r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}"); const char* path;
if (r < 0) return r;
while ((r = sd_bus_message_enter_container(m, 'e', "oa{sa{sv}}")) > 0) {
if ((r = sd_bus_message_read(m, "o", &path)) < 0 || (r = interfaces(b, path, m, 0)) < 0) return r;
sd_bus_message_exit_container(m);
}
if (r < 0) return r;
return sd_bus_message_exit_container(m);
}
static int queried(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct bluez_link* l = arg;
/* Оставляем slot ненулевым при разборе, чтобы ServicesResolved не породил рекурсивный запрос. */
int r = response_error(m, "GetManagedObjects") ? -EIO : managed_objects(l->backend, m);
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) {
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");
}
return 0;
}
static void query_link(struct bluez_link* l) {
if (l->query || l->subscribe || l->opened) return;
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");
}
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;
}
static int signal_changed(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct ble_backend* b = arg; const char* iface;
int r = sd_bus_message_read(m, "s", &iface);
if (r >= 0) r = properties(b, sd_bus_message_get_path(m), iface, m, 1);
if (r < 0) DEBUG_WARN(CAT, "BLE invalid BlueZ PropertiesChanged: %s", strerror(-r));
return 0;
}
static int object_added(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct ble_backend* b = arg; const char* path;
int r = sd_bus_message_read(m, "o", &path);
if (r >= 0) r = interfaces(b, path, m, 1);
if (r < 0) DEBUG_WARN(CAT, "BLE invalid BlueZ InterfacesAdded: %s", strerror(-r));
return 0;
}
static int object_removed(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct ble_backend* b = arg; const char* path;
if (sd_bus_message_read(m, "o", &path) < 0) { DEBUG_WARN(CAT, "BLE invalid InterfacesRemoved"); return 0; }
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");
}
return 0;
}
static int owner_changed(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; const char *name, *old_owner, *new_owner;
if (sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner) >= 0 && !new_owner[0]) fail(arg, "BlueZ stopped", ECONNRESET);
return 0;
}
static int setup_reply(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct ble_backend* b = arg; b->setup = sd_bus_slot_unref(b->setup);
if (response_error(m, "startup")) { fail(b, "BlueZ startup failed", EIO); return 0; }
int r = 0;
switch (b->step++) {
case 0:
snprintf(b->daemon_owner, sizeof(b->daemon_owner), "%s", sd_bus_message_get_sender(m));
r = managed_objects(b, m);
if (r < 0 || !b->adapter[0]) { fail(b, "Bluetooth adapter unavailable", r < 0 ? r : ENODEV); return 0; }
r = sd_bus_call_method_async(b->bus, &b->setup, "org.bluez", b->adapter, "org.bluez.GattManager1",
"RegisterApplication", setup_reply, b, "oa{sv}", ROOT, 0); break;
case 1:
r = sd_bus_call_method_async(b->bus, &b->setup, "org.bluez", b->adapter, "org.bluez.LEAdvertisingManager1",
"RegisterAdvertisement", setup_reply, b, "oa{sv}", ADV, 0); break;
case 2:
r = sd_bus_call_method_async(b->bus, &b->setup, "org.bluez", b->adapter, "org.bluez.Adapter1",
"SetDiscoveryFilter", setup_reply, b, "a{sv}", 2, "Transport", "s", "le", "DuplicateData", "b", 1); break;
case 3:
r = sd_bus_call_method_async(b->bus, &b->setup, "org.bluez", b->adapter, "org.bluez.Adapter1", "StartDiscovery", setup_reply, b, ""); break;
case 4: ble_backend_status(b->owner, 1, "BlueZ scan/GATT ready"); break;
}
if (r < 0) fail(b, "BlueZ setup request failed", r);
return 0;
}
static int beacon_reply(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)error; struct ble_backend* b = arg; b->beacon_op = sd_bus_slot_unref(b->beacon_op);
if (response_error(m, "advertisement update")) { fail(b, "advertisement update failed", EIO); return 0; }
b->beacon_registered = b->beacon_action == 1; b->beacon_action = 0;
DEBUG_DEBUG(CAT, "BLE broadcast registered=%d bytes=%zu", b->beacon_registered, b->data_size);
ble_backend_advertising(b->owner, b->beacon_registered ? BLE_READY : BLE_CLOSED, "BlueZ advertisement registration changed");
beacon_update(b); return 0;
}
static void beacon_update(struct ble_backend* b) {
if (b->beacon_op || b->failed) return;
int r;
if (b->beacon_registered) {
if (b->beacon_desired && b->next_size == b->data_size && !memcmp(b->data, b->next_data, b->data_size)) return;
b->beacon_action = 2;
r = sd_bus_call_method_async(b->bus, &b->beacon_op, "org.bluez", b->adapter, "org.bluez.LEAdvertisingManager1",
"UnregisterAdvertisement", beacon_reply, b, "o", BEACON);
} else {
if (!b->beacon_desired) return;
b->beacon_action = 1;
b->data_size = b->next_size; memcpy(b->data, b->next_data, b->data_size);
r = sd_bus_call_method_async(b->bus, &b->beacon_op, "org.bluez", b->adapter, "org.bluez.LEAdvertisingManager1",
"RegisterAdvertisement", beacon_reply, b, "oa{sv}", BEACON, 0);
}
if (r < 0) fail(b, "advertisement request failed", r);
}
struct ble_backend* ble_backend_create(struct ble_transport* owner, struct UASYNC* ua, const struct ble_config* cfg) {
struct ble_backend* b = u_calloc(1, sizeof(*b));
if (!b) { DEBUG_ERROR(CAT, "BLE BlueZ allocation failed"); return NULL; }
b->owner = owner; b->ua = ua; b->company = cfg->company_id;
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->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);
if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->service, SERVICE, "org.bluez.GattService1", service_table, b);
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->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,
"type='signal',sender='org.bluez',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'", signal_changed, NULL, b);
if (r >= 0) r = sd_bus_add_match_async(b->bus, &b->objects,
"type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'", object_added, NULL, b);
if (r >= 0) r = sd_bus_add_match_async(b->bus, &b->removed,
"type='signal',sender='org.bluez',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'", object_removed, NULL, b);
if (r >= 0) r = sd_bus_add_match_async(b->bus, &b->owner_watch,
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",
owner_changed, NULL, b);
if (r >= 0) r = sd_bus_call_method_async(b->bus, &b->setup, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager",
"GetManagedObjects", setup_reply, b, "");
if (r >= 0) { b->io = uasync_add_socket(ua, sd_bus_get_fd(b->bus), bus_io, bus_io, bus_io, "ble_dbus", b); if (!b->io) r = -ENOMEM; }
if (r < 0) { DEBUG_ERROR(CAT, "BLE BlueZ create: %s", strerror(-r)); ble_backend_destroy(b); return NULL; }
bus_update(b); return b;
}
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++) {
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, "");
if (r < 0) DEBUG_WARN(CAT, "BLE StopNotify failed: %s", strerror(-r));
}
memset(l, 0, sizeof(*l)); bus_update(b);
}
void ble_backend_destroy(struct ble_backend* b) {
if (!b) return;
b->failed = 1;
for (int i = 0; i < BLE_LINKS_MAX; i++) if (b->links[i].id) ble_backend_close(b, b->links[i].id);
if (b->timer) uasync_cancel_timeout(b->ua, b->timer);
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->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);
}
int ble_backend_advertise(struct ble_backend* b, const uint8_t* data, size_t len) {
if (b->failed) return BLE_NOT_READY;
b->beacon_desired = data != NULL; b->next_size = len; if (len) memcpy(b->next_data, data, len);
if (!b->beacon_op && b->beacon_desired == b->beacon_registered &&
(!b->beacon_desired || (len == b->data_size && !memcmp(b->data, b->next_data, len)))) {
ble_backend_advertising(b->owner, b->beacon_registered ? BLE_READY : BLE_CLOSED, "advertisement unchanged"); return BLE_OK;
}
ble_backend_advertising(b->owner, BLE_STARTING, "updating advertisement");
beacon_update(b); bus_update(b); return b->failed ? BLE_ERROR : BLE_OK;
}
int ble_backend_connect(struct ble_backend* b, ble_link_id id, const struct ble_endpoint* ep) {
if (b->failed || strncmp(ep->id, b->adapter, strlen(b->adapter)) || !strstr(ep->id, "/dev_")) {
DEBUG_WARN(CAT, "BLE invalid BlueZ endpoint=%s", ep->id); return BLE_ERROR;
}
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; }
bus_update(b); return BLE_OK;
}
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) {
struct bluez_link* l = find_link(b, id);
if ((unsigned)channel >= BLE_CHANNEL_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;
if (!l->outgoing) {
memcpy(l->value, data, len); l->value_size = len; l->buffered = l->sending = 1;
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");
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_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);
l->sending = 1; bus_update(b); return BLE_OK;
}