231 lines
15 KiB
C
231 lines
15 KiB
C
/* Private D-Bus + настоящий backend: DATA без ACK, адресные INVITE indications и два клиента. */
|
|
#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 <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#define CHECK(v) do { if (!(v)) { fprintf(stderr, "FAIL line=%d: %s errno=%d\n", __LINE__, #v, errno); exit(1); } } while (0)
|
|
#define ROOT "/org/vibechat/ble"
|
|
#define ADAPTER "/org/bluez/hci0"
|
|
#define REMOTE ADAPTER "/dev_C"
|
|
static struct UASYNC* ua;
|
|
static sd_bus* bus;
|
|
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 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); }
|
|
}
|
|
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));
|
|
return sd_bus_reply_method_return(m, "");
|
|
}
|
|
static int simple(sd_bus_message* m, void* arg, sd_bus_error* e) { (void)arg; (void)e; return sd_bus_reply_method_return(m, ""); }
|
|
static int advertise(sd_bus_message* m, void* arg, sd_bus_error* e) {
|
|
(void)arg; (void)e; advertisement_count++;
|
|
return sd_bus_reply_method_return(m, "");
|
|
}
|
|
static int property(sd_bus* b, const char* p, const char* i, const char* name, sd_bus_message* m, void* arg, sd_bus_error* e) {
|
|
(void)b; (void)p; (void)i; (void)name; (void)arg; (void)e; return sd_bus_message_append(m, "b", 1);
|
|
}
|
|
static const sd_bus_vtable adapter_vtable[] = {
|
|
SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Powered", "b", property, 0, 0),
|
|
SD_BUS_METHOD("SetDiscoveryFilter", "a{sv}", "", simple, SD_BUS_VTABLE_UNPRIVILEGED),
|
|
SD_BUS_METHOD("StartDiscovery", "", "", simple, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END
|
|
};
|
|
static const sd_bus_vtable gatt_vtable[] = {
|
|
SD_BUS_VTABLE_START(0), SD_BUS_METHOD("RegisterApplication", "oa{sv}", "", register_app, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END
|
|
};
|
|
static const sd_bus_vtable adv_vtable[] = {
|
|
SD_BUS_VTABLE_START(0), SD_BUS_METHOD("RegisterAdvertisement", "oa{sv}", "", advertise, SD_BUS_VTABLE_UNPRIVILEGED),
|
|
SD_BUS_METHOD("UnregisterAdvertisement", "o", "", simple, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END
|
|
};
|
|
static int remote_property(sd_bus* b, const char* path, const char* i, const char* name,
|
|
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;
|
|
return sd_bus_message_append(m, "s", uuid);
|
|
}
|
|
static int remote_write(sd_bus_message* m, void* arg, sd_bus_error* e) {
|
|
(void)arg; (void)e;
|
|
const void* data; size_t len; const char *key, *type;
|
|
CHECK(sd_bus_message_read_array(m, 'y', &data, &len) > 0 && len > 0);
|
|
CHECK(sd_bus_message_enter_container(m, 'a', "{sv}") > 0);
|
|
CHECK(sd_bus_message_enter_container(m, 'e', "sv") > 0);
|
|
CHECK(sd_bus_message_read(m, "s", &key) > 0 && !strcmp(key, "type"));
|
|
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 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;
|
|
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_send(bus, signal, NULL) >= 0); sd_bus_message_unref(signal);
|
|
}
|
|
return r;
|
|
}
|
|
static int remote_state(sd_bus* b, const char* path, const char* i, const char* name,
|
|
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);
|
|
}
|
|
static const sd_bus_vtable device_vtable[] = {
|
|
SD_BUS_VTABLE_START(0), SD_BUS_METHOD("Connect", "", "", simple, 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),
|
|
SD_BUS_VTABLE_END
|
|
};
|
|
static const sd_bus_vtable remote_vtable[] = {
|
|
SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("UUID", "s", remote_property, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
|
SD_BUS_PROPERTY("MTU", "q", remote_property, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
|
SD_BUS_METHOD("StartNotify", "", "", simple, SD_BUS_VTABLE_UNPRIVILEGED),
|
|
SD_BUS_METHOD("StopNotify", "", "", simple, SD_BUS_VTABLE_UNPRIVILEGED),
|
|
SD_BUS_METHOD("WriteValue", "aya{sv}", "", remote_write, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END
|
|
};
|
|
static void status(void* arg, enum ble_state state, const char* reason) {
|
|
(void)arg; (void)reason; if (state == BLE_READY) ready++;
|
|
}
|
|
static void connection(void* arg, ble_link_id id, uint64_t node, enum ble_state state, const char* reason) {
|
|
(void)arg; (void)reason;
|
|
if (state == BLE_READY) { CHECK(node < 4); links[node] = id; ready_links++; }
|
|
}
|
|
static void broadcast(void* arg, const struct ble_endpoint* ep, const uint8_t* data, size_t len, int rssi) {
|
|
(void)arg; CHECK(strstr(ep->id, "dev_A")); CHECK(len == 3 && !memcmp(data, "abc", 3)); CHECK(rssi == -42); broadcasts++;
|
|
}
|
|
static int replied(sd_bus_message* m, void* arg, sd_bus_error* error) {
|
|
(void)error; struct reply* reply = arg;
|
|
reply->failed = sd_bus_message_is_method_error(m, NULL);
|
|
if (reply->failed) { const sd_bus_error* e = sd_bus_message_get_error(m); fprintf(stderr, "reply error %s: %s\n", e->name, e->message); }
|
|
if (!reply->failed && !strcmp(sd_bus_message_get_signature(m, 1), "hq")) {
|
|
int fd; uint16_t mtu; CHECK(sd_bus_message_read(m, "hq", &fd, &mtu) > 0); CHECK(mtu == 23);
|
|
reply->fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); CHECK(reply->fd >= 0);
|
|
}
|
|
reply->done = 1; return 0;
|
|
}
|
|
static int acquire(const char* device, int invitation) {
|
|
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",
|
|
"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;
|
|
}
|
|
static void write_value(const char* device, const uint8_t* data, size_t len, int invitation) {
|
|
struct reply reply = {0}; sd_bus_message* m = NULL;
|
|
CHECK(sd_bus_message_new_method_call(bus, &m, application, invitation ? ROOT "/service0/invite" : ROOT "/service0/rx", "org.bluez.GattCharacteristic1", "WriteValue") >= 0);
|
|
CHECK(sd_bus_message_append_array(m, 'y', data, len) >= 0);
|
|
CHECK(sd_bus_message_append(m, "a{sv}", 2, "device", "o", device, "mtu", "q", 23) >= 0);
|
|
CHECK(sd_bus_call_async(bus, &reply.slot, m, replied, &reply, 1000000) >= 0); sd_bus_message_unref(m);
|
|
poll_all(); CHECK(reply.done && !reply.failed); sd_bus_slot_unref(reply.slot);
|
|
}
|
|
static void announce(void) {
|
|
sd_bus_message* m = NULL;
|
|
CHECK(sd_bus_message_new_signal(bus, &m, ADAPTER "/dev_A", "org.freedesktop.DBus.Properties", "PropertiesChanged") >= 0);
|
|
CHECK(sd_bus_message_append(m, "sa{sv}as", "org.bluez.Device1", 2,
|
|
"ManufacturerData", "a{qv}", 1, 0xffff, "ay", 3, 'a', 'b', 'c', "RSSI", "n", -42, 0) >= 0);
|
|
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);
|
|
poll_all(); /* ACCEPT отправляется Notification: подтверждения от клиента нет. */
|
|
}
|
|
static struct ll_entry* frame(int value) {
|
|
struct ll_entry* e = ll_alloc_lldgram(31); CHECK(e); e->len = 31; memset(e->dgram, value, e->len); return e;
|
|
}
|
|
int main(void) {
|
|
CHECK(getenv("DBUS_SESSION_BUS_ADDRESS")); CHECK(!setenv("DBUS_SYSTEM_BUS_ADDRESS", getenv("DBUS_SESSION_BUS_ADDRESS"), 1));
|
|
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};
|
|
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++)
|
|
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;
|
|
config.callbacks.status = status; config.callbacks.connection = connection; config.callbacks.broadcast = broadcast;
|
|
transport = ble_create(ua, &config); CHECK(transport && !ble_start(transport)); poll_all(); CHECK(ready == 1 && broadcasts == 0);
|
|
CHECK(!ble_advertise(transport, "abc", 3)); poll_all(); CHECK(advertisement_count == 2);
|
|
CHECK(!ble_advertise(transport, "def", 3)); poll_all(); CHECK(advertisement_count == 3);
|
|
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);
|
|
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);
|
|
}
|
|
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};
|
|
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(!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);
|
|
CHECK(!ble_channel_tx_put(transport, links[2], BLE_CHANNEL_INVITE, frame(0xc3))); poll_all();
|
|
CHECK(recv(invite1, data, sizeof(data), MSG_DONTWAIT) < 0 && errno == EAGAIN);
|
|
CHECK(recv(fd2, data, sizeof(data), MSG_DONTWAIT) < 0 && errno == EAGAIN);
|
|
for (int offset = 0; offset < 31; offset += 13) {
|
|
CHECK(recv(invite2, data, sizeof(data), MSG_DONTWAIT) == (offset == 26 ? 12 : 20));
|
|
CHECK(data[6] == offset && data[7] == 0xc3);
|
|
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);
|
|
}
|
|
CHECK(send(invite2, "\1", 1, MSG_NOSIGNAL) == 1); poll_all();
|
|
}
|
|
close(invite1); close(invite2);
|
|
close(fd1); poll_all(); close(fd2); poll_all();
|
|
/* Настоящий клиент backend: HELLO/DATA используют command, INVITE использует request. */
|
|
struct ble_endpoint ep = {REMOTE};
|
|
CHECK(ble_connect(transport, &ep, 3)); poll_all();
|
|
/* Кеш UUID доступен сразу, но до ServicesResolved нельзя объявлять GATT готовым с прежним MTU23. */
|
|
CHECK(ready_links == 2 && commands == 0);
|
|
remote_resolved = 1;
|
|
CHECK(sd_bus_emit_properties_changed(bus, REMOTE, "org.bluez.Device1", "ServicesResolved", NULL) >= 0);
|
|
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. */
|
|
ble_destroy(transport); uasync_destroy(ua, 0);
|
|
for (int i = 0; i < 4; 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)");
|
|
return 0;
|
|
}
|