Files
utun2/lib/ble/tests/test_ble_bluez.c
T

436 lines
29 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Private D-Bus + настоящий backend: DATA/ACK без ATT ACK, адресные INVITE indications, два клиента. */
#include "ble_backend.h"
#include "ble_identity.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"
#define REMOTE_SERVICE REMOTE "/service"
#define ROTATED ADAPTER "/dev_D"
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, closed_links;
static int advertisement_uuid_count, advertisement_queries;
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, remote_connected = 1;
static unsigned remote_connects;
static unsigned rotated_connects;
static uint64_t remote_identity;
static unsigned profile_callbacks, profile_reads;
static const char full_name[] = "Полное имя устройства GATT";
static char service_uuid[37];
struct reply { sd_bus_slot* slot; int done, fd, failed; uint8_t bytes[64]; size_t size; };
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 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++; /* ACK notification не требует ATT confirmation. */
}
}
}
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 advertisement_properties(sd_bus_message* m, void* arg, sd_bus_error* e) {
(void)arg; (void)e; CHECK(!sd_bus_message_is_method_error(m, NULL));
CHECK(sd_bus_message_enter_container(m, 'a', "{sv}") > 0); const char *name, *type; int r;
advertisement_uuid_count = 0; advertisement_queries++;
while ((r = sd_bus_message_enter_container(m, 'e', "sv")) > 0) {
CHECK(sd_bus_message_read(m, "s", &name) > 0 && sd_bus_message_peek_type(m, NULL, &type) > 0);
CHECK(sd_bus_message_enter_container(m, 'v', type) > 0);
CHECK(strcmp(name, "ManufacturerData")); /* Node endpoint объявляется одним service advertising. */
if (!strcmp(name, "ServiceUUIDs")) {
CHECK(sd_bus_message_enter_container(m, 'a', "s") > 0); const char* uuid;
while ((r = sd_bus_message_read(m, "s", &uuid)) > 0) {
uint64_t node; CHECK(!ble_service_uuid_decode(uuid, &node) && node == 200); advertisement_uuid_count++;
}
CHECK(r >= 0 && sd_bus_message_exit_container(m) >= 0);
} else if (!strcmp(name, "Type")) {
const char* value; CHECK(sd_bus_message_read(m, "s", &value) > 0 && !strcmp(value, "peripheral"));
} else CHECK(sd_bus_message_skip(m, type) >= 0);
CHECK(sd_bus_message_exit_container(m) >= 0 && sd_bus_message_exit_container(m) >= 0);
}
CHECK(r >= 0); return 0;
}
static int advertise(sd_bus_message* m, void* arg, sd_bus_error* e) {
(void)arg; (void)e; advertisement_count++; const char* path;
CHECK(sd_bus_message_read(m, "o", &path) > 0);
CHECK(sd_bus_call_method_async(bus, NULL, application, path, "org.freedesktop.DBus.Properties", "GetAll",
advertisement_properties, NULL, "s", "org.bluez.LEAdvertisement1") >= 0);
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 int power_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)arg; (void)e;
int unavailable = getenv("BLE_TEST_POWER_UNAVAILABLE") != NULL;
if (!strcmp(name, "SupportedFeatures"))
return unavailable ? sd_bus_message_append(m, "as", 0) : sd_bus_message_append(m, "as", 2, "HardwareOffload", "CanSetTxPower");
return unavailable ? sd_bus_message_append(m, "a{sv}", 0) :
sd_bus_message_append(m, "a{sv}", 2, "MaxAdvLen", "y", 251, "MaxTxPower", "n", 4);
}
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_PROPERTY("SupportedFeatures", "as", power_property, 0, 0),
SD_BUS_PROPERTY("SupportedCapabilities", "a{sv}", power_property, 0, 0),
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, "/name") ? BLE_NAME_UUID : strstr(path, "/node") ? BLE_NODE_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) {
(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;
int ack = strstr(sd_bus_message_get_path(m), "/ack") != NULL;
CHECK(!strcmp(type, invitation ? "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 && !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_SERVICE "/tx", "org.freedesktop.DBus.Properties", "PropertiesChanged") >= 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;
}
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)i; (void)arg; (void)e;
int rotated = !strcmp(path, ROTATED);
if (!strcmp(name, "UUIDs")) {
char uuid[37]; CHECK(!ble_service_uuid_encode(uuid, rotated || !remote_identity ? 3 : remote_identity));
return sd_bus_message_append(m, "as", 1, uuid);
}
if (!strcmp(name, "ManufacturerData")) {
if (!remote_identity) return sd_bus_message_append(m, "a{qv}", 1, 0xffff, "ay", 3, 'a', 'b', 'c');
uint8_t bytes[BLE_BROADCAST_MAX]; int len = ble_identity_encode(bytes, rotated ? 3 : remote_identity, "");
int r = sd_bus_message_open_container(m, 'a', "{qv}");
if (r >= 0) r = sd_bus_message_open_container(m, 'e', "qv");
if (r >= 0) r = sd_bus_message_append(m, "q", 0xffff);
if (r >= 0) r = sd_bus_message_open_container(m, 'v', "ay");
if (r >= 0) r = sd_bus_message_append_array(m, 'y', bytes, len);
if (r >= 0) r = sd_bus_message_close_container(m);
if (r >= 0) r = sd_bus_message_close_container(m);
if (r >= 0) r = sd_bus_message_close_container(m);
return r;
}
if (rotated) return sd_bus_message_append(m, "b", 0);
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;
if (!strcmp(sd_bus_message_get_path(m), ROTATED)) {
rotated_connects++; return sd_bus_reply_method_errorf(m, "org.bluez.Error.Failed", "ACL already exists at old address");
}
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", "", "", 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),
SD_BUS_PROPERTY("UUIDs", "as", remote_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_VTABLE_END
};
static int service_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)path; (void)i; (void)name; (void)arg; (void)e; return sd_bus_message_append(m, "s", service_uuid);
}
static const sd_bus_vtable remote_service_vtable[] = {
SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("UUID", "s", service_property, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END
};
static int remote_read(sd_bus_message* m, void* arg, sd_bus_error* error) {
(void)arg; (void)error; uint8_t node[8]; ble_node_encode(node, 3);
int is_node = strstr(sd_bus_message_get_path(m), "/node") != NULL;
sd_bus_message* reply = NULL; CHECK(sd_bus_message_new_method_return(m, &reply) >= 0);
CHECK(sd_bus_message_append_array(reply, 'y', is_node ? (const void*)node : (const void*)full_name,
is_node ? sizeof(node) : strlen(full_name)) >= 0);
CHECK(sd_bus_send(bus, reply, NULL) >= 0); sd_bus_message_unref(reply); profile_reads++; return 1;
}
static void got_profile(void* arg, uint64_t node, const char* name) {
(void)arg; CHECK(node == 3 && !strcmp(name, full_name)); profile_callbacks++;
}
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("ReadValue", "a{sv}", "ay", remote_read, SD_BUS_VTABLE_UNPRIVILEGED),
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++; }
if (state == BLE_CLOSED) closed_links++;
}
static void broadcast(void* arg, const struct ble_endpoint* ep, const uint8_t* data, size_t len, int rssi) {
(void)arg; struct ble_identity identity; CHECK(strstr(ep->id, "dev_A") || !strcmp(ep->id, REMOTE));
CHECK(!ble_identity_decode(&identity, data, len) && identity.node_id == 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);
}
if (!reply->failed && !strcmp(sd_bus_message_get_signature(m, 1), "ay")) {
const void* bytes; CHECK(sd_bus_message_read_array(m, 'y', &bytes, &reply->size) >= 0);
CHECK(reply->size <= sizeof(reply->bytes)); memcpy(reply->bytes, bytes, reply->size);
}
reply->done = 1; return 0;
}
static int acquire(const char* device, int channel) {
struct reply reply = {.fd=-1};
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;
}
static struct reply read_local_profile(const char* path, unsigned offset) {
struct reply reply = {0};
CHECK(sd_bus_call_method_async(bus, &reply.slot, application, path, "org.bluez.GattCharacteristic1",
"ReadValue", replied, &reply, "a{sv}", 3, "device", "o", ADAPTER "/dev_A", "mtu", "q", 23,
"offset", "q", offset) >= 0);
poll_all(); CHECK(reply.done && !reply.failed); sd_bus_slot_unref(reply.slot); return reply;
}
static void write_value(const char* device, const uint8_t* data, size_t len, int channel) {
struct reply reply = {0}; sd_bus_message* m = NULL;
const char* path = channel == 2 ? ROOT "/service0/ack" : channel ? ROOT "/service0/invite" : ROOT "/service0/rx";
CHECK(sd_bus_message_new_method_call(bus, &m, application, path, "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(const char* path, int with_uuid) {
sd_bus_message* m = NULL;
CHECK(sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.Properties", "PropertiesChanged") >= 0);
if (with_uuid) CHECK(sd_bus_message_append(m, "sa{sv}as", "org.bluez.Device1", 2,
"UUIDs", "as", 1, service_uuid, "RSSI", "n", -42, 0) >= 0);
else CHECK(sd_bus_message_append(m, "sa{sv}as", "org.bluez.Device1", 1, "RSSI", "n", -42, 0) >= 0);
unsigned before = broadcasts;
CHECK(sd_bus_send(bus, m, NULL) >= 0); sd_bus_message_unref(m); poll_all(); CHECK(broadcasts == before + 1);
}
static void hello(const char* device, int fd, uint8_t 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) {
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[9] = {0};
CHECK(!ble_service_uuid_encode(service_uuid, 3));
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);
CHECK(sd_bus_add_object_vtable(bus, &remote[6], REMOTE_SERVICE, "org.bluez.GattService1", remote_service_vtable, NULL) >= 0);
const char* paths[] = {REMOTE_SERVICE "/rx", REMOTE_SERVICE "/tx", REMOTE_SERVICE "/invite", REMOTE_SERVICE "/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);
CHECK(sd_bus_add_object_vtable(bus, &remote[7], REMOTE_SERVICE "/name", "org.bluez.GattCharacteristic1", remote_vtable, NULL) >= 0);
CHECK(sd_bus_add_object_vtable(bus, &remote[8], REMOTE_SERVICE "/node", "org.bluez.GattCharacteristic1", remote_vtable, NULL) >= 0);
ua = uasync_create(); CHECK(ua);
struct ble_config config = {0}; config.node_id = 200;
strcpy(config.name, full_name); config.callbacks.profile = got_profile;
memcpy(config.initial_advertisement, "abc", 3); config.initial_advertisement_size = 3;
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);
struct ble_advertising_power power = ble_get_advertising_power(transport);
CHECK(power.max_dbm == (getenv("BLE_TEST_POWER_UNAVAILABLE") ? 127 : 4));
CHECK(power.can_set == (getenv("BLE_TEST_POWER_UNAVAILABLE") ? 0 : 1));
CHECK(advertisement_count == 1); /* Один connectable advertising с UUID узла. */
CHECK(advertisement_queries == 1 && advertisement_uuid_count == 1);
/* Read Long при MTU23 возвращает один снимок даже при смене имени между частями. */
struct reply part = read_local_profile(ROOT "/service0/name", 0);
CHECK(part.size == 22 && !memcmp(part.bytes, full_name, 22));
CHECK(!ble_set_name(transport, "Новое имя"));
part = read_local_profile(ROOT "/service0/name", 22);
CHECK(part.size == 22 && !memcmp(part.bytes, full_name + 22, part.size));
part = read_local_profile(ROOT "/service0/name", 44);
CHECK(part.size == strlen(full_name) - 44 && !memcmp(part.bytes, full_name + 44, part.size));
part = read_local_profile(ROOT "/service0/name", 0);
CHECK(part.size == strlen("Новое имя") && !memcmp(part.bytes, "Новое имя", part.size));
part = read_local_profile(ROOT "/service0/node", 0);
CHECK(ble_node_decode(part.bytes, part.size) == 200);
CHECK(!ble_advertise(transport, "abc", 3)); poll_all(); CHECK(advertisement_count == 1);
CHECK(!ble_advertise(transport, "def", 3)); poll_all(); CHECK(advertisement_count == 1); /* UUID/видимость прежние. */
announce(REMOTE, 0); /* UUID был лишь в начальном snapshot; живой RSSI делает наблюдение свежим. */
announce(ADAPTER "/dev_A", 1);
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 += 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[] = {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 + 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);
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 += 11) {
CHECK(recv(fd1, data, sizeof(data), MSG_DONTWAIT) == (part == 22 ? 18 : 20));
CHECK(data[8] == part && data[9] == 0xd4);
}
/* Flow ACK последнего DATA-фрагмента, независимо от ATT confirmation канала INVITE. */
write_value(ADAPTER "/dev_A", data, 2, 2);
CHECK(send(invite2, "\1", 1, MSG_NOSIGNAL) == 1); poll_all();
}
CHECK(ack_values >= 2);
/* Повторный AcquireNotify до HUP старого fd: новая сессия A, действующая сессия B не затронута. */
ble_link_id old_a = links[1], old_b = links[2];
int closed_before = closed_links;
int old_ack = ack_fds[0]; ack_fds[0] = -1; /* poll_all не должен читать EOF закрываемой подписки. */
int new_fd1 = acquire(ADAPTER "/dev_A", 0);
CHECK(closed_links == closed_before + 1 && ready_links == 2);
CHECK(recv(invite1, data, sizeof(data), MSG_DONTWAIT) == 0);
close(old_ack);
close(invite1); close(fd1); fd1 = new_fd1;
invite1 = acquire(ADAPTER "/dev_A", 1);
CHECK(ready_links == 2);
ack_fds[0] = acquire(ADAPTER "/dev_A", 2);
hello(ADAPTER "/dev_A", fd1, 1);
CHECK(ready_links == 3 && links[1] != old_a && links[2] == old_b);
CHECK(!ble_tx_put(transport, old_b, frame(0xf1))); poll_all();
for (int offset = 0; offset < 31; offset += 11) {
CHECK(recv(fd2, data, sizeof(data), MSG_DONTWAIT) == (offset == 22 ? 18 : 20));
CHECK(data[8] == offset && data[9] == 0xf1);
}
write_value(ADAPTER "/dev_B", data, 2, 2);
write_value(ADAPTER "/dev_A", incoming, sizeof(incoming), 0);
e = ble_rx_get(transport, links[1]); CHECK(e && e->len == 3 && !memcmp(e->dgram, incoming + 9, 3));
queue_dgram_free(e); queue_entry_free(e);
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. */
struct ble_endpoint ep = {REMOTE};
CHECK(ble_connect(transport, &ep, 3)); poll_all();
/* Кеш UUID доступен сразу, но до ServicesResolved нельзя объявлять GATT готовым с прежним MTU23. */
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);
/* Новый RPA того же узла не требует второго ACL; чужой ID на старом ACL не подходит. */
ble_disconnect(transport, links[3]); poll_all(); remote_resolved = 0; remote_identity = 2;
CHECK(sd_bus_add_object_vtable(bus, &remote[5], ROTATED, "org.bluez.Device1", device_vtable, NULL) >= 0);
struct ble_endpoint rotated = {ROTATED};
CHECK(ble_connect(transport, &rotated, 3)); poll_all();
CHECK(rotated_connects == 1 && ready_links == 6 && commands == 4);
remote_identity = 3;
CHECK(ble_connect(transport, &rotated, 3)); poll_all();
CHECK(rotated_connects == 1 && remote_connects == 1 && ready_links == 6 && commands == 4);
remote_resolved = 1;
CHECK(sd_bus_emit_properties_changed(bus, REMOTE, "org.bluez.Device1", "ServicesResolved", NULL) >= 0);
poll_all(); CHECK(ready_links == 7 && commands == 5 && rotated_connects == 1 && remote_connects == 1);
unsigned closed = closed_links;
CHECK(!ble_advertise(transport, NULL, 0)); poll_all();
CHECK(advertisement_uuid_count == 0 && closed_links == closed && ble_tx_ready(transport, links[3]));
ble_set_incoming(transport, 0); CHECK(!ble_advertise(transport, "abc", 3)); poll_all();
CHECK(advertisement_uuid_count == 1 && !ble_backend_incoming(transport, &rotated));
CHECK(profile_callbacks >= 4 && profile_reads == profile_callbacks * 2);
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 < 9; 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 (existing/new ACL, DATA/ACK commands, INVITE indications, two clients, teardown)");
return 0;
}