From 4c88a557a764a209fea24b307d08cc3fce820231 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 01/73] Vendor BlueZ's bluetooth.h verbatim --- .../include/bluetooth/bluetooth.h | 531 ++++++++++++++++++ 1 file changed, 531 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/bluetooth.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/bluetooth.h b/Sources/CBluetoothLinuxABI/include/bluetooth/bluetooth.h new file mode 100644 index 0000000..88a5d8b --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/bluetooth.h @@ -0,0 +1,531 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright 2023 NXP + * + * + */ + +#ifndef __BLUETOOTH_H +#define __BLUETOOTH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef AF_BLUETOOTH +#define AF_BLUETOOTH 31 +#define PF_BLUETOOTH AF_BLUETOOTH +#endif + +#define BTPROTO_L2CAP 0 +#define BTPROTO_HCI 1 +#define BTPROTO_SCO 2 +#define BTPROTO_RFCOMM 3 +#define BTPROTO_BNEP 4 +#define BTPROTO_CMTP 5 +#define BTPROTO_HIDP 6 +#define BTPROTO_AVDTP 7 +#define BTPROTO_ISO 8 + +#define SOL_HCI 0 +#define SOL_L2CAP 6 +#define SOL_SCO 17 +#define SOL_RFCOMM 18 + +#ifndef SOL_BLUETOOTH +#define SOL_BLUETOOTH 274 +#endif + +#define BT_SECURITY 4 +struct bt_security { + uint8_t level; + uint8_t key_size; +}; +#define BT_SECURITY_SDP 0 +#define BT_SECURITY_LOW 1 +#define BT_SECURITY_MEDIUM 2 +#define BT_SECURITY_HIGH 3 +#define BT_SECURITY_FIPS 4 + +#define BT_DEFER_SETUP 7 + +#define BT_FLUSHABLE 8 + +#define BT_FLUSHABLE_OFF 0 +#define BT_FLUSHABLE_ON 1 + +#define BT_POWER 9 +struct bt_power { + uint8_t force_active; +}; +#define BT_POWER_FORCE_ACTIVE_OFF 0 +#define BT_POWER_FORCE_ACTIVE_ON 1 + +#define BT_CHANNEL_POLICY 10 + +/* BR/EDR only (default policy) + * AMP controllers cannot be used. + * Channel move requests from the remote device are denied. + * If the L2CAP channel is currently using AMP, move the channel to BR/EDR. + */ +#define BT_CHANNEL_POLICY_BREDR_ONLY 0 + +/* BR/EDR Preferred + * Allow use of AMP controllers. + * If the L2CAP channel is currently on AMP, move it to BR/EDR. + * Channel move requests from the remote device are allowed. + */ +#define BT_CHANNEL_POLICY_BREDR_PREFERRED 1 + +/* AMP Preferred + * Allow use of AMP controllers + * If the L2CAP channel is currently on BR/EDR and AMP controller + * resources are available, initiate a channel move to AMP. + * Channel move requests from the remote device are allowed. + * If the L2CAP socket has not been connected yet, try to create + * and configure the channel directly on an AMP controller rather + * than BR/EDR. + */ +#define BT_CHANNEL_POLICY_AMP_PREFERRED 2 + +#define BT_VOICE 11 +struct bt_voice { + uint16_t setting; +}; + +#define BT_SNDMTU 12 +#define BT_RCVMTU 13 + +#define BT_VOICE_TRANSPARENT 0x0003 +#define BT_VOICE_CVSD_16BIT 0x0060 +#define BT_VOICE_TRANSPARENT_16BIT 0x0063 + +#define BT_PHY 14 + +#define BT_PHY_BR_1M_1SLOT 0x00000001 +#define BT_PHY_BR_1M_3SLOT 0x00000002 +#define BT_PHY_BR_1M_5SLOT 0x00000004 +#define BT_PHY_EDR_2M_1SLOT 0x00000008 +#define BT_PHY_EDR_2M_3SLOT 0x00000010 +#define BT_PHY_EDR_2M_5SLOT 0x00000020 +#define BT_PHY_EDR_3M_1SLOT 0x00000040 +#define BT_PHY_EDR_3M_3SLOT 0x00000080 +#define BT_PHY_EDR_3M_5SLOT 0x00000100 +#define BT_PHY_LE_1M_TX 0x00000200 +#define BT_PHY_LE_1M_RX 0x00000400 +#define BT_PHY_LE_2M_TX 0x00000800 +#define BT_PHY_LE_2M_RX 0x00001000 +#define BT_PHY_LE_CODED_TX 0x00002000 +#define BT_PHY_LE_CODED_RX 0x00004000 + +#define BT_MODE 15 + +#define BT_MODE_BASIC 0x00 +#define BT_MODE_ERTM 0x01 +#define BT_MODE_STREAMING 0x02 +#define BT_MODE_LE_FLOWCTL 0x03 +#define BT_MODE_EXT_FLOWCTL 0x04 + +#define BT_PKT_STATUS 16 + +#define BT_SCM_PKT_STATUS 0x03 +#define BT_SCM_ERROR 0x04 + +#define BT_ISO_QOS 17 + +#define BT_ISO_QOS_CIG_UNSET 0xff +#define BT_ISO_QOS_CIS_UNSET 0xff + +#define BT_ISO_QOS_BIG_UNSET 0xff +#define BT_ISO_QOS_BIS_UNSET 0xff + +#define BT_ISO_SYNC_TIMEOUT 0x07d0 /* 20 secs */ + +/* For an ISO Broadcaster, this value is used to compute + * the desired Periodic Advertising Interval as a function + * of SDU interval, based on the formula: + * + * PA_Interval = SDU_Interval * sync_factor + * + * This is useful for adjusting how frequent to send PA + * announcements for Broadcast Sinks to discover, depending + * on scenario. + */ +#define BT_ISO_SYNC_FACTOR 0x01 + +#define BT_ISO_QOS_GROUP_UNSET 0xff +#define BT_ISO_QOS_STREAM_UNSET 0xff + +struct bt_iso_io_qos { + uint32_t interval; + uint16_t latency; + uint16_t sdu; + uint8_t phy; + uint8_t rtn; +}; + +struct bt_iso_ucast_qos { + uint8_t cig; + uint8_t cis; + uint8_t sca; + uint8_t packing; + uint8_t framing; + struct bt_iso_io_qos in; + struct bt_iso_io_qos out; +}; + +struct bt_iso_bcast_qos { + uint8_t big; + uint8_t bis; + uint8_t sync_factor; + uint8_t packing; + uint8_t framing; + struct bt_iso_io_qos in; + struct bt_iso_io_qos out; + uint8_t encryption; + uint8_t bcode[16]; + uint8_t options; + uint16_t skip; + uint16_t sync_timeout; + uint8_t sync_cte_type; + uint8_t mse; + uint16_t timeout; +}; + +/* (HCI_MAX_PER_AD_LENGTH - EIR_SERVICE_DATA_LENGTH) */ +#define BASE_MAX_LENGTH 248 +struct bt_iso_base { + uint8_t base_len; + uint8_t base[BASE_MAX_LENGTH]; +}; + +struct bt_iso_qos { + union { + struct bt_iso_ucast_qos ucast; + struct bt_iso_bcast_qos bcast; + }; +}; + +#define BT_CODEC 19 +struct bt_codec { + uint8_t id; + uint16_t cid; + uint16_t vid; + uint8_t data_path_id; + uint8_t num_caps; + struct codec_caps { + uint8_t len; + uint8_t data[]; + } caps[]; +} __attribute__((packed)); + +struct bt_codecs { + uint8_t num_codecs; + struct bt_codec codecs[]; +} __attribute__((packed)); + + +/* Connection and socket states */ +enum { + BT_CONNECTED = 1, /* Equal to TCP_ESTABLISHED to make net code happy */ + BT_OPEN, + BT_BOUND, + BT_LISTEN, + BT_CONNECT, + BT_CONNECT2, + BT_CONFIG, + BT_DISCONN, + BT_CLOSED +}; + +#define BT_ISO_BASE 20 + +#define BT_PKT_SEQNUM 22 + +#define BT_SCM_PKT_SEQNUM 0x05 + +/* Byte order conversions */ +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htobs(d) (d) +#define htobl(d) (d) +#define htobll(d) (d) +#define btohs(d) (d) +#define btohl(d) (d) +#define btohll(d) (d) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define htobs(d) bswap_16(d) +#define htobl(d) bswap_32(d) +#define htobll(d) bswap_64(d) +#define btohs(d) bswap_16(d) +#define btohl(d) bswap_32(d) +#define btohll(d) bswap_64(d) +#else +#error "Unknown byte order" +#endif + +/* Bluetooth unaligned access */ +#define bt_get_unaligned(ptr) \ +__extension__ ({ \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v; \ +}) + +#define bt_put_unaligned(val, ptr) \ +do { \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v = (val); \ +} while(0) + +#if __BYTE_ORDER == __LITTLE_ENDIAN +static inline uint64_t bt_get_le64(const void *ptr) +{ + return bt_get_unaligned((const uint64_t *) ptr); +} + +static inline uint64_t bt_get_be64(const void *ptr) +{ + return bswap_64(bt_get_unaligned((const uint64_t *) ptr)); +} + +static inline uint32_t bt_get_le32(const void *ptr) +{ + return bt_get_unaligned((const uint32_t *) ptr); +} + +static inline uint32_t bt_get_be32(const void *ptr) +{ + return bswap_32(bt_get_unaligned((const uint32_t *) ptr)); +} + +static inline uint16_t bt_get_le16(const void *ptr) +{ + return bt_get_unaligned((const uint16_t *) ptr); +} + +static inline uint16_t bt_get_be16(const void *ptr) +{ + return bswap_16(bt_get_unaligned((const uint16_t *) ptr)); +} + +static inline void bt_put_le64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint64_t *) ptr); +} + +static inline void bt_put_be64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(bswap_64(val), (uint64_t *) ptr); +} + +static inline void bt_put_le32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint32_t *) ptr); +} + +static inline void bt_put_be32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(bswap_32(val), (uint32_t *) ptr); +} + +static inline void bt_put_le16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint16_t *) ptr); +} + +static inline void bt_put_be16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(bswap_16(val), (uint16_t *) ptr); +} + +#elif __BYTE_ORDER == __BIG_ENDIAN +static inline uint64_t bt_get_le64(const void *ptr) +{ + return bswap_64(bt_get_unaligned((const uint64_t *) ptr)); +} + +static inline uint64_t bt_get_be64(const void *ptr) +{ + return bt_get_unaligned((const uint64_t *) ptr); +} + +static inline uint32_t bt_get_le32(const void *ptr) +{ + return bswap_32(bt_get_unaligned((const uint32_t *) ptr)); +} + +static inline uint32_t bt_get_be32(const void *ptr) +{ + return bt_get_unaligned((const uint32_t *) ptr); +} + +static inline uint16_t bt_get_le16(const void *ptr) +{ + return bswap_16(bt_get_unaligned((const uint16_t *) ptr)); +} + +static inline uint16_t bt_get_be16(const void *ptr) +{ + return bt_get_unaligned((const uint16_t *) ptr); +} + +static inline void bt_put_le64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(bswap_64(val), (uint64_t *) ptr); +} + +static inline void bt_put_be64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint64_t *) ptr); +} + +static inline void bt_put_le32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(bswap_32(val), (uint32_t *) ptr); +} + +static inline void bt_put_be32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint32_t *) ptr); +} + +static inline void bt_put_le16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(bswap_16(val), (uint16_t *) ptr); +} + +static inline void bt_put_be16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint16_t *) ptr); +} +#else +#error "Unknown byte order" +#endif + +/* BD Address */ +typedef struct { + uint8_t b[6]; +} __attribute__((packed)) bdaddr_t; + +/* BD Address type */ +#define BDADDR_BREDR 0x00 +#define BDADDR_LE_PUBLIC 0x01 +#define BDADDR_LE_RANDOM 0x02 + +#define BDADDR_ANY (&(bdaddr_t) {{0, 0, 0, 0, 0, 0}}) +#define BDADDR_ALL (&(bdaddr_t) {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}) +#define BDADDR_LOCAL (&(bdaddr_t) {{0, 0, 0, 0xff, 0xff, 0xff}}) + +/* Copy, swap, convert BD Address */ +static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t *ba2) +{ + return memcmp(ba1, ba2, sizeof(bdaddr_t)); +} +static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src) +{ + memcpy(dst, src, sizeof(bdaddr_t)); +} + +void baswap(bdaddr_t *dst, const bdaddr_t *src); +bdaddr_t *strtoba(const char *str); +char *batostr(const bdaddr_t *ba); +int ba2str(const bdaddr_t *ba, char *str); +int ba2strlc(const bdaddr_t *ba, char *str); +int str2ba(const char *str, bdaddr_t *ba); +int ba2oui(const bdaddr_t *ba, char *oui); +int bachk(const char *str); + +int baprintf(const char *format, ...); +int bafprintf(FILE *stream, const char *format, ...); +int basprintf(char *str, const char *format, ...); +int basnprintf(char *str, size_t size, const char *format, ...); + +void *bt_malloc(size_t size); +void *bt_malloc0(size_t size); +void bt_free(void *ptr); + +int bt_error(uint16_t code); +const char *bt_compidtostr(int id); + +typedef struct { + uint8_t data[3]; +} uint24_t; + +typedef struct { + uint8_t data[16]; +} uint128_t; + +static inline void bswap_128(const void *src, void *dst) +{ + const uint8_t *s = (const uint8_t *) src; + uint8_t *d = (uint8_t *) dst; + int i; + + for (i = 0; i < 16; i++) + d[15 - i] = s[i]; +} + +#if __BYTE_ORDER == __BIG_ENDIAN + +#define ntoh64(x) (x) + +static inline void ntoh128(const uint128_t *src, uint128_t *dst) +{ + memcpy(dst, src, sizeof(uint128_t)); +} + +static inline void btoh128(const uint128_t *src, uint128_t *dst) +{ + bswap_128(src, dst); +} + +#else + +static inline uint64_t ntoh64(uint64_t n) +{ + uint64_t h; + uint64_t tmp = ntohl(n & 0x00000000ffffffff); + + h = ntohl(n >> 32); + h |= tmp << 32; + + return h; +} + +static inline void ntoh128(const uint128_t *src, uint128_t *dst) +{ + bswap_128(src, dst); +} + +static inline void btoh128(const uint128_t *src, uint128_t *dst) +{ + memcpy(dst, src, sizeof(uint128_t)); +} + +#endif + +#define hton64(x) ntoh64(x) +#define hton128(x, y) ntoh128(x, y) +#define htob128(x, y) btoh128(x, y) + +#ifdef __cplusplus +} +#endif + +#endif /* __BLUETOOTH_H */ From 8f7b3d98ba0c1838e18b3d9419351d66d3cb3e36 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 02/73] Vendor BlueZ's hci.h verbatim --- .../include/bluetooth/hci.h | 2459 +++++++++++++++++ 1 file changed, 2459 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/hci.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/hci.h b/Sources/CBluetoothLinuxABI/include/bluetooth/hci.h new file mode 100644 index 0000000..732477e --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/hci.h @@ -0,0 +1,2459 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + */ + +#ifndef __HCI_H +#define __HCI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +#define HCI_MAX_DEV 16 + +#define HCI_MAX_AMP_SIZE (1492 + 4) +#define HCI_MAX_ACL_SIZE 1024 +#define HCI_MAX_SCO_SIZE 255 +#define HCI_MAX_EVENT_SIZE 260 +#define HCI_MAX_FRAME_SIZE (HCI_MAX_AMP_SIZE + 4) + +/* HCI dev events */ +#define HCI_DEV_REG 1 +#define HCI_DEV_UNREG 2 +#define HCI_DEV_UP 3 +#define HCI_DEV_DOWN 4 +#define HCI_DEV_SUSPEND 5 +#define HCI_DEV_RESUME 6 + +/* HCI bus types */ +#define HCI_VIRTUAL 0 +#define HCI_USB 1 +#define HCI_PCCARD 2 +#define HCI_UART 3 +#define HCI_RS232 4 +#define HCI_PCI 5 +#define HCI_SDIO 6 +#define HCI_SPI 7 +#define HCI_I2C 8 +#define HCI_SMD 9 +#define HCI_VIRTIO 10 +#define HCI_IPC 11 + +/* HCI controller types */ +#define HCI_PRIMARY 0x00 +#define HCI_AMP 0x01 +#define HCI_BREDR HCI_PRIMARY + +/* HCI device flags */ +enum { + HCI_UP, + HCI_INIT, + HCI_RUNNING, + + HCI_PSCAN, + HCI_ISCAN, + HCI_AUTH, + HCI_ENCRYPT, + HCI_INQUIRY, + + HCI_RAW, +}; + +/* LE address type */ +enum { + LE_PUBLIC_ADDRESS = 0x00, + LE_RANDOM_ADDRESS = 0x01 +}; + +/* HCI ioctl defines */ +#define HCIDEVUP _IOW('H', 201, int) +#define HCIDEVDOWN _IOW('H', 202, int) +#define HCIDEVRESET _IOW('H', 203, int) +#define HCIDEVRESTAT _IOW('H', 204, int) + +#define HCIGETDEVLIST _IOR('H', 210, int) +#define HCIGETDEVINFO _IOR('H', 211, int) +#define HCIGETCONNLIST _IOR('H', 212, int) +#define HCIGETCONNINFO _IOR('H', 213, int) +#define HCIGETAUTHINFO _IOR('H', 215, int) + +#define HCISETRAW _IOW('H', 220, int) +#define HCISETSCAN _IOW('H', 221, int) +#define HCISETAUTH _IOW('H', 222, int) +#define HCISETENCRYPT _IOW('H', 223, int) +#define HCISETPTYPE _IOW('H', 224, int) +#define HCISETLINKPOL _IOW('H', 225, int) +#define HCISETLINKMODE _IOW('H', 226, int) +#define HCISETACLMTU _IOW('H', 227, int) +#define HCISETSCOMTU _IOW('H', 228, int) + +#define HCIBLOCKADDR _IOW('H', 230, int) +#define HCIUNBLOCKADDR _IOW('H', 231, int) + +#define HCIINQUIRY _IOR('H', 240, int) + +#ifndef __NO_HCI_DEFS + +/* HCI Packet types */ +#define HCI_COMMAND_PKT 0x01 +#define HCI_ACLDATA_PKT 0x02 +#define HCI_SCODATA_PKT 0x03 +#define HCI_EVENT_PKT 0x04 +#define HCI_ISODATA_PKT 0x05 +#define HCI_VENDOR_PKT 0xff + +/* HCI Packet types */ +#define HCI_2DH1 0x0002 +#define HCI_3DH1 0x0004 +#define HCI_DM1 0x0008 +#define HCI_DH1 0x0010 +#define HCI_2DH3 0x0100 +#define HCI_3DH3 0x0200 +#define HCI_DM3 0x0400 +#define HCI_DH3 0x0800 +#define HCI_2DH5 0x1000 +#define HCI_3DH5 0x2000 +#define HCI_DM5 0x4000 +#define HCI_DH5 0x8000 + +#define HCI_HV1 0x0020 +#define HCI_HV2 0x0040 +#define HCI_HV3 0x0080 + +#define HCI_EV3 0x0008 +#define HCI_EV4 0x0010 +#define HCI_EV5 0x0020 +#define HCI_2EV3 0x0040 +#define HCI_3EV3 0x0080 +#define HCI_2EV5 0x0100 +#define HCI_3EV5 0x0200 + +#define SCO_PTYPE_MASK (HCI_HV1 | HCI_HV2 | HCI_HV3) +#define ACL_PTYPE_MASK (HCI_DM1 | HCI_DH1 | HCI_DM3 | HCI_DH3 | HCI_DM5 | HCI_DH5) + +/* HCI Error codes */ +#define HCI_UNKNOWN_COMMAND 0x01 +#define HCI_NO_CONNECTION 0x02 +#define HCI_HARDWARE_FAILURE 0x03 +#define HCI_PAGE_TIMEOUT 0x04 +#define HCI_AUTHENTICATION_FAILURE 0x05 +#define HCI_PIN_OR_KEY_MISSING 0x06 +#define HCI_MEMORY_FULL 0x07 +#define HCI_CONNECTION_TIMEOUT 0x08 +#define HCI_MAX_NUMBER_OF_CONNECTIONS 0x09 +#define HCI_MAX_NUMBER_OF_SCO_CONNECTIONS 0x0a +#define HCI_ACL_CONNECTION_EXISTS 0x0b +#define HCI_COMMAND_DISALLOWED 0x0c +#define HCI_REJECTED_LIMITED_RESOURCES 0x0d +#define HCI_REJECTED_SECURITY 0x0e +#define HCI_REJECTED_PERSONAL 0x0f +#define HCI_HOST_TIMEOUT 0x10 +#define HCI_UNSUPPORTED_FEATURE 0x11 +#define HCI_INVALID_PARAMETERS 0x12 +#define HCI_OE_USER_ENDED_CONNECTION 0x13 +#define HCI_OE_LOW_RESOURCES 0x14 +#define HCI_OE_POWER_OFF 0x15 +#define HCI_CONNECTION_TERMINATED 0x16 +#define HCI_REPEATED_ATTEMPTS 0x17 +#define HCI_PAIRING_NOT_ALLOWED 0x18 +#define HCI_UNKNOWN_LMP_PDU 0x19 +#define HCI_UNSUPPORTED_REMOTE_FEATURE 0x1a +#define HCI_SCO_OFFSET_REJECTED 0x1b +#define HCI_SCO_INTERVAL_REJECTED 0x1c +#define HCI_AIR_MODE_REJECTED 0x1d +#define HCI_INVALID_LMP_PARAMETERS 0x1e +#define HCI_UNSPECIFIED_ERROR 0x1f +#define HCI_UNSUPPORTED_LMP_PARAMETER_VALUE 0x20 +#define HCI_ROLE_CHANGE_NOT_ALLOWED 0x21 +#define HCI_LMP_RESPONSE_TIMEOUT 0x22 +#define HCI_LMP_ERROR_TRANSACTION_COLLISION 0x23 +#define HCI_LMP_PDU_NOT_ALLOWED 0x24 +#define HCI_ENCRYPTION_MODE_NOT_ACCEPTED 0x25 +#define HCI_UNIT_LINK_KEY_USED 0x26 +#define HCI_QOS_NOT_SUPPORTED 0x27 +#define HCI_INSTANT_PASSED 0x28 +#define HCI_PAIRING_NOT_SUPPORTED 0x29 +#define HCI_TRANSACTION_COLLISION 0x2a +#define HCI_QOS_UNACCEPTABLE_PARAMETER 0x2c +#define HCI_QOS_REJECTED 0x2d +#define HCI_CLASSIFICATION_NOT_SUPPORTED 0x2e +#define HCI_INSUFFICIENT_SECURITY 0x2f +#define HCI_PARAMETER_OUT_OF_RANGE 0x30 +#define HCI_ROLE_SWITCH_PENDING 0x32 +#define HCI_SLOT_VIOLATION 0x34 +#define HCI_ROLE_SWITCH_FAILED 0x35 +#define HCI_EIR_TOO_LARGE 0x36 +#define HCI_SIMPLE_PAIRING_NOT_SUPPORTED 0x37 +#define HCI_HOST_BUSY_PAIRING 0x38 + +/* ACL flags */ +#define ACL_START_NO_FLUSH 0x00 +#define ACL_CONT 0x01 +#define ACL_START 0x02 +#define ACL_ACTIVE_BCAST 0x04 +#define ACL_PICO_BCAST 0x08 + +/* Baseband links */ +#define SCO_LINK 0x00 +#define ACL_LINK 0x01 +#define ESCO_LINK 0x02 + +/* LMP features */ +#define LMP_3SLOT 0x01 +#define LMP_5SLOT 0x02 +#define LMP_ENCRYPT 0x04 +#define LMP_SOFFSET 0x08 +#define LMP_TACCURACY 0x10 +#define LMP_RSWITCH 0x20 +#define LMP_HOLD 0x40 +#define LMP_SNIFF 0x80 + +#define LMP_PARK 0x01 +#define LMP_RSSI 0x02 +#define LMP_QUALITY 0x04 +#define LMP_SCO 0x08 +#define LMP_HV2 0x10 +#define LMP_HV3 0x20 +#define LMP_ULAW 0x40 +#define LMP_ALAW 0x80 + +#define LMP_CVSD 0x01 +#define LMP_PSCHEME 0x02 +#define LMP_PCONTROL 0x04 +#define LMP_TRSP_SCO 0x08 +#define LMP_BCAST_ENC 0x80 + +#define LMP_EDR_ACL_2M 0x02 +#define LMP_EDR_ACL_3M 0x04 +#define LMP_ENH_ISCAN 0x08 +#define LMP_ILACE_ISCAN 0x10 +#define LMP_ILACE_PSCAN 0x20 +#define LMP_RSSI_INQ 0x40 +#define LMP_ESCO 0x80 + +#define LMP_EV4 0x01 +#define LMP_EV5 0x02 +#define LMP_AFH_CAP_SLV 0x08 +#define LMP_AFH_CLS_SLV 0x10 +#define LMP_NO_BREDR 0x20 +#define LMP_LE 0x40 +#define LMP_EDR_3SLOT 0x80 + +#define LMP_EDR_5SLOT 0x01 +#define LMP_SNIFF_SUBR 0x02 +#define LMP_PAUSE_ENC 0x04 +#define LMP_AFH_CAP_MST 0x08 +#define LMP_AFH_CLS_MST 0x10 +#define LMP_EDR_ESCO_2M 0x20 +#define LMP_EDR_ESCO_3M 0x40 +#define LMP_EDR_3S_ESCO 0x80 + +#define LMP_EXT_INQ 0x01 +#define LMP_LE_BREDR 0x02 +#define LMP_SIMPLE_PAIR 0x08 +#define LMP_ENCAPS_PDU 0x10 +#define LMP_ERR_DAT_REP 0x20 +#define LMP_NFLUSH_PKTS 0x40 + +#define LMP_LSTO 0x01 +#define LMP_INQ_TX_PWR 0x02 +#define LMP_EPC 0x04 +#define LMP_EXT_FEAT 0x80 + +/* Extended LMP features */ +#define LMP_HOST_SSP 0x01 +#define LMP_HOST_LE 0x02 +#define LMP_HOST_LE_BREDR 0x04 + +/* Link policies */ +#define HCI_LP_RSWITCH 0x0001 +#define HCI_LP_HOLD 0x0002 +#define HCI_LP_SNIFF 0x0004 +#define HCI_LP_PARK 0x0008 + +/* Link mode */ +#define HCI_LM_ACCEPT 0x8000 +#define HCI_LM_MASTER 0x0001 +#define HCI_LM_AUTH 0x0002 +#define HCI_LM_ENCRYPT 0x0004 +#define HCI_LM_TRUSTED 0x0008 +#define HCI_LM_RELIABLE 0x0010 +#define HCI_LM_SECURE 0x0020 + +/* Link Key types */ +#define HCI_LK_COMBINATION 0x00 +#define HCI_LK_LOCAL_UNIT 0x01 +#define HCI_LK_REMOTE_UNIT 0x02 +#define HCI_LK_DEBUG_COMBINATION 0x03 +#define HCI_LK_UNAUTH_COMBINATION 0x04 +#define HCI_LK_AUTH_COMBINATION 0x05 +#define HCI_LK_CHANGED_COMBINATION 0x06 +#define HCI_LK_INVALID 0xFF + +/* ----- HCI Commands ----- */ + +/* Link Control */ +#define OGF_LINK_CTL 0x01 + +#define OCF_INQUIRY 0x0001 +typedef struct { + uint8_t lap[3]; + uint8_t length; /* 1.28s units */ + uint8_t num_rsp; +} __attribute__ ((packed)) inquiry_cp; +#define INQUIRY_CP_SIZE 5 + +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) status_bdaddr_rp; +#define STATUS_BDADDR_RP_SIZE 7 + +#define OCF_INQUIRY_CANCEL 0x0002 + +#define OCF_PERIODIC_INQUIRY 0x0003 +typedef struct { + uint16_t max_period; /* 1.28s units */ + uint16_t min_period; /* 1.28s units */ + uint8_t lap[3]; + uint8_t length; /* 1.28s units */ + uint8_t num_rsp; +} __attribute__ ((packed)) periodic_inquiry_cp; +#define PERIODIC_INQUIRY_CP_SIZE 9 + +#define OCF_EXIT_PERIODIC_INQUIRY 0x0004 + +#define OCF_CREATE_CONN 0x0005 +typedef struct { + bdaddr_t bdaddr; + uint16_t pkt_type; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; + uint8_t role_switch; +} __attribute__ ((packed)) create_conn_cp; +#define CREATE_CONN_CP_SIZE 13 + +#define OCF_DISCONNECT 0x0006 +typedef struct { + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)) disconnect_cp; +#define DISCONNECT_CP_SIZE 3 + +#define OCF_ADD_SCO 0x0007 +typedef struct { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)) add_sco_cp; +#define ADD_SCO_CP_SIZE 4 + +#define OCF_CREATE_CONN_CANCEL 0x0008 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) create_conn_cancel_cp; +#define CREATE_CONN_CANCEL_CP_SIZE 6 + +#define OCF_ACCEPT_CONN_REQ 0x0009 +typedef struct { + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) accept_conn_req_cp; +#define ACCEPT_CONN_REQ_CP_SIZE 7 + +#define OCF_REJECT_CONN_REQ 0x000A +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) reject_conn_req_cp; +#define REJECT_CONN_REQ_CP_SIZE 7 + +#define OCF_LINK_KEY_REPLY 0x000B +typedef struct { + bdaddr_t bdaddr; + uint8_t link_key[16]; +} __attribute__ ((packed)) link_key_reply_cp; +#define LINK_KEY_REPLY_CP_SIZE 22 + +#define OCF_LINK_KEY_NEG_REPLY 0x000C + +#define OCF_PIN_CODE_REPLY 0x000D +typedef struct { + bdaddr_t bdaddr; + uint8_t pin_len; + uint8_t pin_code[16]; +} __attribute__ ((packed)) pin_code_reply_cp; +#define PIN_CODE_REPLY_CP_SIZE 23 + +#define OCF_PIN_CODE_NEG_REPLY 0x000E + +#define OCF_SET_CONN_PTYPE 0x000F +typedef struct { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)) set_conn_ptype_cp; +#define SET_CONN_PTYPE_CP_SIZE 4 + +#define OCF_AUTH_REQUESTED 0x0011 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) auth_requested_cp; +#define AUTH_REQUESTED_CP_SIZE 2 + +#define OCF_SET_CONN_ENCRYPT 0x0013 +typedef struct { + uint16_t handle; + uint8_t encrypt; +} __attribute__ ((packed)) set_conn_encrypt_cp; +#define SET_CONN_ENCRYPT_CP_SIZE 3 + +#define OCF_CHANGE_CONN_LINK_KEY 0x0015 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) change_conn_link_key_cp; +#define CHANGE_CONN_LINK_KEY_CP_SIZE 2 + +#define OCF_MASTER_LINK_KEY 0x0017 +typedef struct { + uint8_t key_flag; +} __attribute__ ((packed)) master_link_key_cp; +#define MASTER_LINK_KEY_CP_SIZE 1 + +#define OCF_REMOTE_NAME_REQ 0x0019 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; +} __attribute__ ((packed)) remote_name_req_cp; +#define REMOTE_NAME_REQ_CP_SIZE 10 + +#define OCF_REMOTE_NAME_REQ_CANCEL 0x001A +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) remote_name_req_cancel_cp; +#define REMOTE_NAME_REQ_CANCEL_CP_SIZE 6 + +#define OCF_READ_REMOTE_FEATURES 0x001B +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_remote_features_cp; +#define READ_REMOTE_FEATURES_CP_SIZE 2 + +#define OCF_READ_REMOTE_EXT_FEATURES 0x001C +typedef struct { + uint16_t handle; + uint8_t page_num; +} __attribute__ ((packed)) read_remote_ext_features_cp; +#define READ_REMOTE_EXT_FEATURES_CP_SIZE 3 + +#define OCF_READ_REMOTE_VERSION 0x001D +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_remote_version_cp; +#define READ_REMOTE_VERSION_CP_SIZE 2 + +#define OCF_READ_CLOCK_OFFSET 0x001F +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_clock_offset_cp; +#define READ_CLOCK_OFFSET_CP_SIZE 2 + +#define OCF_READ_LMP_HANDLE 0x0020 + +#define OCF_SETUP_SYNC_CONN 0x0028 +typedef struct { + uint16_t handle; + uint32_t tx_bandwith; + uint32_t rx_bandwith; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)) setup_sync_conn_cp; +#define SETUP_SYNC_CONN_CP_SIZE 17 + +#define OCF_ACCEPT_SYNC_CONN_REQ 0x0029 +typedef struct { + bdaddr_t bdaddr; + uint32_t tx_bandwith; + uint32_t rx_bandwith; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)) accept_sync_conn_req_cp; +#define ACCEPT_SYNC_CONN_REQ_CP_SIZE 21 + +#define OCF_REJECT_SYNC_CONN_REQ 0x002A +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) reject_sync_conn_req_cp; +#define REJECT_SYNC_CONN_REQ_CP_SIZE 7 + +#define OCF_IO_CAPABILITY_REPLY 0x002B +typedef struct { + bdaddr_t bdaddr; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __attribute__ ((packed)) io_capability_reply_cp; +#define IO_CAPABILITY_REPLY_CP_SIZE 9 + +#define OCF_USER_CONFIRM_REPLY 0x002C +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) user_confirm_reply_cp; +#define USER_CONFIRM_REPLY_CP_SIZE 6 + +#define OCF_USER_CONFIRM_NEG_REPLY 0x002D + +#define OCF_USER_PASSKEY_REPLY 0x002E +typedef struct { + bdaddr_t bdaddr; + uint32_t passkey; +} __attribute__ ((packed)) user_passkey_reply_cp; +#define USER_PASSKEY_REPLY_CP_SIZE 10 + +#define OCF_USER_PASSKEY_NEG_REPLY 0x002F + +#define OCF_REMOTE_OOB_DATA_REPLY 0x0030 +typedef struct { + bdaddr_t bdaddr; + uint8_t hash[16]; + uint8_t randomizer[16]; +} __attribute__ ((packed)) remote_oob_data_reply_cp; +#define REMOTE_OOB_DATA_REPLY_CP_SIZE 38 + +#define OCF_REMOTE_OOB_DATA_NEG_REPLY 0x0033 + +#define OCF_IO_CAPABILITY_NEG_REPLY 0x0034 +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) io_capability_neg_reply_cp; +#define IO_CAPABILITY_NEG_REPLY_CP_SIZE 7 + +#define OCF_CREATE_PHYSICAL_LINK 0x0035 +typedef struct { + uint8_t handle; + uint8_t key_length; + uint8_t key_type; + uint8_t key[32]; +} __attribute__ ((packed)) create_physical_link_cp; +#define CREATE_PHYSICAL_LINK_CP_SIZE 35 + +#define OCF_ACCEPT_PHYSICAL_LINK 0x0036 +typedef struct { + uint8_t handle; + uint8_t key_length; + uint8_t key_type; + uint8_t key[32]; +} __attribute__ ((packed)) accept_physical_link_cp; +#define ACCEPT_PHYSICAL_LINK_CP_SIZE 35 + +#define OCF_DISCONNECT_PHYSICAL_LINK 0x0037 +typedef struct { + uint8_t handle; + uint8_t reason; +} __attribute__ ((packed)) disconnect_physical_link_cp; +#define DISCONNECT_PHYSICAL_LINK_CP_SIZE 2 + +#define OCF_CREATE_LOGICAL_LINK 0x0038 +typedef struct { + uint8_t handle; + uint8_t tx_flow[16]; + uint8_t rx_flow[16]; +} __attribute__ ((packed)) create_logical_link_cp; +#define CREATE_LOGICAL_LINK_CP_SIZE 33 + +#define OCF_ACCEPT_LOGICAL_LINK 0x0039 + +#define OCF_DISCONNECT_LOGICAL_LINK 0x003A +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) disconnect_logical_link_cp; +#define DISCONNECT_LOGICAL_LINK_CP_SIZE 2 + +#define OCF_LOGICAL_LINK_CANCEL 0x003B +typedef struct { + uint8_t handle; + uint8_t tx_flow_id; +} __attribute__ ((packed)) cancel_logical_link_cp; +#define LOGICAL_LINK_CANCEL_CP_SIZE 2 +typedef struct { + uint8_t status; + uint8_t handle; + uint8_t tx_flow_id; +} __attribute__ ((packed)) cancel_logical_link_rp; +#define LOGICAL_LINK_CANCEL_RP_SIZE 3 + +#define OCF_FLOW_SPEC_MODIFY 0x003C + +/* Link Policy */ +#define OGF_LINK_POLICY 0x02 + +#define OCF_HOLD_MODE 0x0001 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)) hold_mode_cp; +#define HOLD_MODE_CP_SIZE 6 + +#define OCF_SNIFF_MODE 0x0003 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; + uint16_t attempt; + uint16_t timeout; +} __attribute__ ((packed)) sniff_mode_cp; +#define SNIFF_MODE_CP_SIZE 10 + +#define OCF_EXIT_SNIFF_MODE 0x0004 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) exit_sniff_mode_cp; +#define EXIT_SNIFF_MODE_CP_SIZE 2 + +#define OCF_PARK_MODE 0x0005 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)) park_mode_cp; +#define PARK_MODE_CP_SIZE 6 + +#define OCF_EXIT_PARK_MODE 0x0006 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) exit_park_mode_cp; +#define EXIT_PARK_MODE_CP_SIZE 2 + +#define OCF_QOS_SETUP 0x0007 +typedef struct { + uint8_t service_type; /* 1 = best effort */ + uint32_t token_rate; /* Byte per seconds */ + uint32_t peak_bandwidth; /* Byte per seconds */ + uint32_t latency; /* Microseconds */ + uint32_t delay_variation; /* Microseconds */ +} __attribute__ ((packed)) hci_qos; +#define HCI_QOS_CP_SIZE 17 +typedef struct { + uint16_t handle; + uint8_t flags; /* Reserved */ + hci_qos qos; +} __attribute__ ((packed)) qos_setup_cp; +#define QOS_SETUP_CP_SIZE (3 + HCI_QOS_CP_SIZE) + +#define OCF_ROLE_DISCOVERY 0x0009 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) role_discovery_cp; +#define ROLE_DISCOVERY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t role; +} __attribute__ ((packed)) role_discovery_rp; +#define ROLE_DISCOVERY_RP_SIZE 4 + +#define OCF_SWITCH_ROLE 0x000B +typedef struct { + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) switch_role_cp; +#define SWITCH_ROLE_CP_SIZE 7 + +#define OCF_READ_LINK_POLICY 0x000C +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_link_policy_cp; +#define READ_LINK_POLICY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)) read_link_policy_rp; +#define READ_LINK_POLICY_RP_SIZE 5 + +#define OCF_WRITE_LINK_POLICY 0x000D +typedef struct { + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)) write_link_policy_cp; +#define WRITE_LINK_POLICY_CP_SIZE 4 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) write_link_policy_rp; +#define WRITE_LINK_POLICY_RP_SIZE 3 + +#define OCF_READ_DEFAULT_LINK_POLICY 0x000E + +#define OCF_WRITE_DEFAULT_LINK_POLICY 0x000F + +#define OCF_FLOW_SPECIFICATION 0x0010 + +#define OCF_SNIFF_SUBRATING 0x0011 +typedef struct { + uint16_t handle; + uint16_t max_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)) sniff_subrating_cp; +#define SNIFF_SUBRATING_CP_SIZE 8 + +/* Host Controller and Baseband */ +#define OGF_HOST_CTL 0x03 + +#define OCF_SET_EVENT_MASK 0x0001 +typedef struct { + uint8_t mask[8]; +} __attribute__ ((packed)) set_event_mask_cp; +#define SET_EVENT_MASK_CP_SIZE 8 + +#define OCF_RESET 0x0003 + +#define OCF_SET_EVENT_FLT 0x0005 +typedef struct { + uint8_t flt_type; + uint8_t cond_type; + uint8_t condition[]; +} __attribute__ ((packed)) set_event_flt_cp; +#define SET_EVENT_FLT_CP_SIZE 2 + +/* Filter types */ +#define FLT_CLEAR_ALL 0x00 +#define FLT_INQ_RESULT 0x01 +#define FLT_CONN_SETUP 0x02 +/* INQ_RESULT Condition types */ +#define INQ_RESULT_RETURN_ALL 0x00 +#define INQ_RESULT_RETURN_CLASS 0x01 +#define INQ_RESULT_RETURN_BDADDR 0x02 +/* CONN_SETUP Condition types */ +#define CONN_SETUP_ALLOW_ALL 0x00 +#define CONN_SETUP_ALLOW_CLASS 0x01 +#define CONN_SETUP_ALLOW_BDADDR 0x02 +/* CONN_SETUP Conditions */ +#define CONN_SETUP_AUTO_OFF 0x01 +#define CONN_SETUP_AUTO_ON 0x02 + +#define OCF_FLUSH 0x0008 + +#define OCF_READ_PIN_TYPE 0x0009 +typedef struct { + uint8_t status; + uint8_t pin_type; +} __attribute__ ((packed)) read_pin_type_rp; +#define READ_PIN_TYPE_RP_SIZE 2 + +#define OCF_WRITE_PIN_TYPE 0x000A +typedef struct { + uint8_t pin_type; +} __attribute__ ((packed)) write_pin_type_cp; +#define WRITE_PIN_TYPE_CP_SIZE 1 + +#define OCF_CREATE_NEW_UNIT_KEY 0x000B + +#define OCF_READ_STORED_LINK_KEY 0x000D +typedef struct { + bdaddr_t bdaddr; + uint8_t read_all; +} __attribute__ ((packed)) read_stored_link_key_cp; +#define READ_STORED_LINK_KEY_CP_SIZE 7 +typedef struct { + uint8_t status; + uint16_t max_keys; + uint16_t num_keys; +} __attribute__ ((packed)) read_stored_link_key_rp; +#define READ_STORED_LINK_KEY_RP_SIZE 5 + +#define OCF_WRITE_STORED_LINK_KEY 0x0011 +typedef struct { + uint8_t num_keys; + /* variable length part */ +} __attribute__ ((packed)) write_stored_link_key_cp; +#define WRITE_STORED_LINK_KEY_CP_SIZE 1 +typedef struct { + uint8_t status; + uint8_t num_keys; +} __attribute__ ((packed)) write_stored_link_key_rp; +#define READ_WRITE_LINK_KEY_RP_SIZE 2 + +#define OCF_DELETE_STORED_LINK_KEY 0x0012 +typedef struct { + bdaddr_t bdaddr; + uint8_t delete_all; +} __attribute__ ((packed)) delete_stored_link_key_cp; +#define DELETE_STORED_LINK_KEY_CP_SIZE 7 +typedef struct { + uint8_t status; + uint16_t num_keys; +} __attribute__ ((packed)) delete_stored_link_key_rp; +#define DELETE_STORED_LINK_KEY_RP_SIZE 3 + +#define HCI_MAX_NAME_LENGTH 248 + +#define OCF_CHANGE_LOCAL_NAME 0x0013 +typedef struct { + uint8_t name[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) change_local_name_cp; +#define CHANGE_LOCAL_NAME_CP_SIZE 248 + +#define OCF_READ_LOCAL_NAME 0x0014 +typedef struct { + uint8_t status; + uint8_t name[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) read_local_name_rp; +#define READ_LOCAL_NAME_RP_SIZE 249 + +#define OCF_READ_CONN_ACCEPT_TIMEOUT 0x0015 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_conn_accept_timeout_rp; +#define READ_CONN_ACCEPT_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_CONN_ACCEPT_TIMEOUT 0x0016 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_conn_accept_timeout_cp; +#define WRITE_CONN_ACCEPT_TIMEOUT_CP_SIZE 2 + +#define OCF_READ_PAGE_TIMEOUT 0x0017 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_page_timeout_rp; +#define READ_PAGE_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_PAGE_TIMEOUT 0x0018 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_page_timeout_cp; +#define WRITE_PAGE_TIMEOUT_CP_SIZE 2 + +#define OCF_READ_SCAN_ENABLE 0x0019 +typedef struct { + uint8_t status; + uint8_t enable; +} __attribute__ ((packed)) read_scan_enable_rp; +#define READ_SCAN_ENABLE_RP_SIZE 2 + +#define OCF_WRITE_SCAN_ENABLE 0x001A + #define SCAN_DISABLED 0x00 + #define SCAN_INQUIRY 0x01 + #define SCAN_PAGE 0x02 + +#define OCF_READ_PAGE_ACTIVITY 0x001B +typedef struct { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) read_page_activity_rp; +#define READ_PAGE_ACTIVITY_RP_SIZE 5 + +#define OCF_WRITE_PAGE_ACTIVITY 0x001C +typedef struct { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) write_page_activity_cp; +#define WRITE_PAGE_ACTIVITY_CP_SIZE 4 + +#define OCF_READ_INQ_ACTIVITY 0x001D +typedef struct { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) read_inq_activity_rp; +#define READ_INQ_ACTIVITY_RP_SIZE 5 + +#define OCF_WRITE_INQ_ACTIVITY 0x001E +typedef struct { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) write_inq_activity_cp; +#define WRITE_INQ_ACTIVITY_CP_SIZE 4 + +#define OCF_READ_AUTH_ENABLE 0x001F + +#define OCF_WRITE_AUTH_ENABLE 0x0020 + #define AUTH_DISABLED 0x00 + #define AUTH_ENABLED 0x01 + +#define OCF_READ_ENCRYPT_MODE 0x0021 + +#define OCF_WRITE_ENCRYPT_MODE 0x0022 + #define ENCRYPT_DISABLED 0x00 + #define ENCRYPT_P2P 0x01 + #define ENCRYPT_BOTH 0x02 + +#define OCF_READ_CLASS_OF_DEV 0x0023 +typedef struct { + uint8_t status; + uint8_t dev_class[3]; +} __attribute__ ((packed)) read_class_of_dev_rp; +#define READ_CLASS_OF_DEV_RP_SIZE 4 + +#define OCF_WRITE_CLASS_OF_DEV 0x0024 +typedef struct { + uint8_t dev_class[3]; +} __attribute__ ((packed)) write_class_of_dev_cp; +#define WRITE_CLASS_OF_DEV_CP_SIZE 3 + +#define OCF_READ_VOICE_SETTING 0x0025 +typedef struct { + uint8_t status; + uint16_t voice_setting; +} __attribute__ ((packed)) read_voice_setting_rp; +#define READ_VOICE_SETTING_RP_SIZE 3 + +#define OCF_WRITE_VOICE_SETTING 0x0026 +typedef struct { + uint16_t voice_setting; +} __attribute__ ((packed)) write_voice_setting_cp; +#define WRITE_VOICE_SETTING_CP_SIZE 2 + +#define OCF_READ_AUTOMATIC_FLUSH_TIMEOUT 0x0027 + +#define OCF_WRITE_AUTOMATIC_FLUSH_TIMEOUT 0x0028 + +#define OCF_READ_NUM_BROADCAST_RETRANS 0x0029 + +#define OCF_WRITE_NUM_BROADCAST_RETRANS 0x002A + +#define OCF_READ_HOLD_MODE_ACTIVITY 0x002B + +#define OCF_WRITE_HOLD_MODE_ACTIVITY 0x002C + +#define OCF_READ_TRANSMIT_POWER_LEVEL 0x002D +typedef struct { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)) read_transmit_power_level_cp; +#define READ_TRANSMIT_POWER_LEVEL_CP_SIZE 3 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t level; +} __attribute__ ((packed)) read_transmit_power_level_rp; +#define READ_TRANSMIT_POWER_LEVEL_RP_SIZE 4 + +#define OCF_READ_SYNC_FLOW_ENABLE 0x002E + +#define OCF_WRITE_SYNC_FLOW_ENABLE 0x002F + +#define OCF_SET_CONTROLLER_TO_HOST_FC 0x0031 + +#define OCF_HOST_BUFFER_SIZE 0x0033 +typedef struct { + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)) host_buffer_size_cp; +#define HOST_BUFFER_SIZE_CP_SIZE 7 + +#define OCF_HOST_NUM_COMP_PKTS 0x0035 +typedef struct { + uint8_t num_hndl; + /* variable length part */ +} __attribute__ ((packed)) host_num_comp_pkts_cp; +#define HOST_NUM_COMP_PKTS_CP_SIZE 1 + +#define OCF_READ_LINK_SUPERVISION_TIMEOUT 0x0036 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)) read_link_supervision_timeout_rp; +#define READ_LINK_SUPERVISION_TIMEOUT_RP_SIZE 5 + +#define OCF_WRITE_LINK_SUPERVISION_TIMEOUT 0x0037 +typedef struct { + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)) write_link_supervision_timeout_cp; +#define WRITE_LINK_SUPERVISION_TIMEOUT_CP_SIZE 4 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) write_link_supervision_timeout_rp; +#define WRITE_LINK_SUPERVISION_TIMEOUT_RP_SIZE 3 + +#define OCF_READ_NUM_SUPPORTED_IAC 0x0038 + +#define MAX_IAC_LAP 0x40 +#define OCF_READ_CURRENT_IAC_LAP 0x0039 +typedef struct { + uint8_t status; + uint8_t num_current_iac; + uint8_t lap[MAX_IAC_LAP][3]; +} __attribute__ ((packed)) read_current_iac_lap_rp; +#define READ_CURRENT_IAC_LAP_RP_SIZE 2+3*MAX_IAC_LAP + +#define OCF_WRITE_CURRENT_IAC_LAP 0x003A +typedef struct { + uint8_t num_current_iac; + uint8_t lap[MAX_IAC_LAP][3]; +} __attribute__ ((packed)) write_current_iac_lap_cp; +#define WRITE_CURRENT_IAC_LAP_CP_SIZE 1+3*MAX_IAC_LAP + +#define OCF_READ_PAGE_SCAN_PERIOD_MODE 0x003B + +#define OCF_WRITE_PAGE_SCAN_PERIOD_MODE 0x003C + +#define OCF_READ_PAGE_SCAN_MODE 0x003D + +#define OCF_WRITE_PAGE_SCAN_MODE 0x003E + +#define OCF_SET_AFH_CLASSIFICATION 0x003F +typedef struct { + uint8_t map[10]; +} __attribute__ ((packed)) set_afh_classification_cp; +#define SET_AFH_CLASSIFICATION_CP_SIZE 10 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) set_afh_classification_rp; +#define SET_AFH_CLASSIFICATION_RP_SIZE 1 + +#define OCF_READ_INQUIRY_SCAN_TYPE 0x0042 +typedef struct { + uint8_t status; + uint8_t type; +} __attribute__ ((packed)) read_inquiry_scan_type_rp; +#define READ_INQUIRY_SCAN_TYPE_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_SCAN_TYPE 0x0043 +typedef struct { + uint8_t type; +} __attribute__ ((packed)) write_inquiry_scan_type_cp; +#define WRITE_INQUIRY_SCAN_TYPE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_scan_type_rp; +#define WRITE_INQUIRY_SCAN_TYPE_RP_SIZE 1 + +#define OCF_READ_INQUIRY_MODE 0x0044 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_inquiry_mode_rp; +#define READ_INQUIRY_MODE_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_MODE 0x0045 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_inquiry_mode_cp; +#define WRITE_INQUIRY_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_mode_rp; +#define WRITE_INQUIRY_MODE_RP_SIZE 1 + +#define OCF_READ_PAGE_SCAN_TYPE 0x0046 + +#define OCF_WRITE_PAGE_SCAN_TYPE 0x0047 + #define PAGE_SCAN_TYPE_STANDARD 0x00 + #define PAGE_SCAN_TYPE_INTERLACED 0x01 + +#define OCF_READ_AFH_MODE 0x0048 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_afh_mode_rp; +#define READ_AFH_MODE_RP_SIZE 2 + +#define OCF_WRITE_AFH_MODE 0x0049 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_afh_mode_cp; +#define WRITE_AFH_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_afh_mode_rp; +#define WRITE_AFH_MODE_RP_SIZE 1 + +#define HCI_MAX_EIR_LENGTH 240 + +#define OCF_READ_EXT_INQUIRY_RESPONSE 0x0051 +typedef struct { + uint8_t status; + uint8_t fec; + uint8_t data[HCI_MAX_EIR_LENGTH]; +} __attribute__ ((packed)) read_ext_inquiry_response_rp; +#define READ_EXT_INQUIRY_RESPONSE_RP_SIZE 242 + +#define OCF_WRITE_EXT_INQUIRY_RESPONSE 0x0052 +typedef struct { + uint8_t fec; + uint8_t data[HCI_MAX_EIR_LENGTH]; +} __attribute__ ((packed)) write_ext_inquiry_response_cp; +#define WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE 241 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_ext_inquiry_response_rp; +#define WRITE_EXT_INQUIRY_RESPONSE_RP_SIZE 1 + +#define OCF_REFRESH_ENCRYPTION_KEY 0x0053 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) refresh_encryption_key_cp; +#define REFRESH_ENCRYPTION_KEY_CP_SIZE 2 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) refresh_encryption_key_rp; +#define REFRESH_ENCRYPTION_KEY_RP_SIZE 1 + +#define OCF_READ_SIMPLE_PAIRING_MODE 0x0055 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_simple_pairing_mode_rp; +#define READ_SIMPLE_PAIRING_MODE_RP_SIZE 2 + +#define OCF_WRITE_SIMPLE_PAIRING_MODE 0x0056 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_simple_pairing_mode_cp; +#define WRITE_SIMPLE_PAIRING_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_simple_pairing_mode_rp; +#define WRITE_SIMPLE_PAIRING_MODE_RP_SIZE 1 + +#define OCF_READ_LOCAL_OOB_DATA 0x0057 +typedef struct { + uint8_t status; + uint8_t hash[16]; + uint8_t randomizer[16]; +} __attribute__ ((packed)) read_local_oob_data_rp; +#define READ_LOCAL_OOB_DATA_RP_SIZE 33 + +#define OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL 0x0058 +typedef struct { + uint8_t status; + int8_t level; +} __attribute__ ((packed)) read_inq_response_tx_power_level_rp; +#define READ_INQ_RESPONSE_TX_POWER_LEVEL_RP_SIZE 2 + +#define OCF_READ_INQUIRY_TRANSMIT_POWER_LEVEL 0x0058 +typedef struct { + uint8_t status; + int8_t level; +} __attribute__ ((packed)) read_inquiry_transmit_power_level_rp; +#define READ_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL 0x0059 +typedef struct { + int8_t level; +} __attribute__ ((packed)) write_inquiry_transmit_power_level_cp; +#define WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_transmit_power_level_rp; +#define WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE 1 + +#define OCF_READ_DEFAULT_ERROR_DATA_REPORTING 0x005A +typedef struct { + uint8_t status; + uint8_t reporting; +} __attribute__ ((packed)) read_default_error_data_reporting_rp; +#define READ_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE 2 + +#define OCF_WRITE_DEFAULT_ERROR_DATA_REPORTING 0x005B +typedef struct { + uint8_t reporting; +} __attribute__ ((packed)) write_default_error_data_reporting_cp; +#define WRITE_DEFAULT_ERROR_DATA_REPORTING_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_default_error_data_reporting_rp; +#define WRITE_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE 1 + +#define OCF_ENHANCED_FLUSH 0x005F +typedef struct { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)) enhanced_flush_cp; +#define ENHANCED_FLUSH_CP_SIZE 3 + +#define OCF_SEND_KEYPRESS_NOTIFY 0x0060 +typedef struct { + bdaddr_t bdaddr; + uint8_t type; +} __attribute__ ((packed)) send_keypress_notify_cp; +#define SEND_KEYPRESS_NOTIFY_CP_SIZE 7 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) send_keypress_notify_rp; +#define SEND_KEYPRESS_NOTIFY_RP_SIZE 1 + +#define OCF_READ_LOGICAL_LINK_ACCEPT_TIMEOUT 0x0061 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_log_link_accept_timeout_rp; +#define READ_LOGICAL_LINK_ACCEPT_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT 0x0062 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_log_link_accept_timeout_cp; +#define WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT_CP_SIZE 2 + +#define OCF_SET_EVENT_MASK_PAGE_2 0x0063 + +#define OCF_READ_LOCATION_DATA 0x0064 + +#define OCF_WRITE_LOCATION_DATA 0x0065 + +#define OCF_READ_FLOW_CONTROL_MODE 0x0066 + +#define OCF_WRITE_FLOW_CONTROL_MODE 0x0067 + +#define OCF_READ_ENHANCED_TRANSMIT_POWER_LEVEL 0x0068 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t level_gfsk; + int8_t level_dqpsk; + int8_t level_8dpsk; +} __attribute__ ((packed)) read_enhanced_transmit_power_level_rp; +#define READ_ENHANCED_TRANSMIT_POWER_LEVEL_RP_SIZE 6 + +#define OCF_READ_BEST_EFFORT_FLUSH_TIMEOUT 0x0069 +typedef struct { + uint8_t status; + uint32_t timeout; +} __attribute__ ((packed)) read_best_effort_flush_timeout_rp; +#define READ_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE 5 + +#define OCF_WRITE_BEST_EFFORT_FLUSH_TIMEOUT 0x006A +typedef struct { + uint16_t handle; + uint32_t timeout; +} __attribute__ ((packed)) write_best_effort_flush_timeout_cp; +#define WRITE_BEST_EFFORT_FLUSH_TIMEOUT_CP_SIZE 6 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_best_effort_flush_timeout_rp; +#define WRITE_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE 1 + +#define OCF_READ_LE_HOST_SUPPORTED 0x006C +typedef struct { + uint8_t status; + uint8_t le; + uint8_t simul; +} __attribute__ ((packed)) read_le_host_supported_rp; +#define READ_LE_HOST_SUPPORTED_RP_SIZE 3 + +#define OCF_WRITE_LE_HOST_SUPPORTED 0x006D +typedef struct { + uint8_t le; + uint8_t simul; +} __attribute__ ((packed)) write_le_host_supported_cp; +#define WRITE_LE_HOST_SUPPORTED_CP_SIZE 2 + +/* Informational Parameters */ +#define OGF_INFO_PARAM 0x04 + +#define OCF_READ_LOCAL_VERSION 0x0001 +typedef struct { + uint8_t status; + uint8_t hci_ver; + uint16_t hci_rev; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)) read_local_version_rp; +#define READ_LOCAL_VERSION_RP_SIZE 9 + +#define OCF_READ_LOCAL_COMMANDS 0x0002 +typedef struct { + uint8_t status; + uint8_t commands[64]; +} __attribute__ ((packed)) read_local_commands_rp; +#define READ_LOCAL_COMMANDS_RP_SIZE 65 + +#define OCF_READ_LOCAL_FEATURES 0x0003 +typedef struct { + uint8_t status; + uint8_t features[8]; +} __attribute__ ((packed)) read_local_features_rp; +#define READ_LOCAL_FEATURES_RP_SIZE 9 + +#define OCF_READ_LOCAL_EXT_FEATURES 0x0004 +typedef struct { + uint8_t page_num; +} __attribute__ ((packed)) read_local_ext_features_cp; +#define READ_LOCAL_EXT_FEATURES_CP_SIZE 1 +typedef struct { + uint8_t status; + uint8_t page_num; + uint8_t max_page_num; + uint8_t features[8]; +} __attribute__ ((packed)) read_local_ext_features_rp; +#define READ_LOCAL_EXT_FEATURES_RP_SIZE 11 + +#define OCF_READ_BUFFER_SIZE 0x0005 +typedef struct { + uint8_t status; + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)) read_buffer_size_rp; +#define READ_BUFFER_SIZE_RP_SIZE 8 + +#define OCF_READ_COUNTRY_CODE 0x0007 + +#define OCF_READ_BD_ADDR 0x0009 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) read_bd_addr_rp; +#define READ_BD_ADDR_RP_SIZE 7 + +#define OCF_READ_DATA_BLOCK_SIZE 0x000A +typedef struct { + uint8_t status; + uint16_t max_acl_len; + uint16_t data_block_len; + uint16_t num_blocks; +} __attribute__ ((packed)) read_data_block_size_rp; + +/* Status params */ +#define OGF_STATUS_PARAM 0x05 + +#define OCF_READ_FAILED_CONTACT_COUNTER 0x0001 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t counter; +} __attribute__ ((packed)) read_failed_contact_counter_rp; +#define READ_FAILED_CONTACT_COUNTER_RP_SIZE 4 + +#define OCF_RESET_FAILED_CONTACT_COUNTER 0x0002 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) reset_failed_contact_counter_rp; +#define RESET_FAILED_CONTACT_COUNTER_RP_SIZE 3 + +#define OCF_READ_LINK_QUALITY 0x0003 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t link_quality; +} __attribute__ ((packed)) read_link_quality_rp; +#define READ_LINK_QUALITY_RP_SIZE 4 + +#define OCF_READ_RSSI 0x0005 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t rssi; +} __attribute__ ((packed)) read_rssi_rp; +#define READ_RSSI_RP_SIZE 4 + +#define OCF_READ_AFH_MAP 0x0006 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint8_t map[10]; +} __attribute__ ((packed)) read_afh_map_rp; +#define READ_AFH_MAP_RP_SIZE 14 + +#define OCF_READ_CLOCK 0x0007 +typedef struct { + uint16_t handle; + uint8_t which_clock; +} __attribute__ ((packed)) read_clock_cp; +#define READ_CLOCK_CP_SIZE 3 +typedef struct { + uint8_t status; + uint16_t handle; + uint32_t clock; + uint16_t accuracy; +} __attribute__ ((packed)) read_clock_rp; +#define READ_CLOCK_RP_SIZE 9 + +#define OCF_READ_LOCAL_AMP_INFO 0x0009 +typedef struct { + uint8_t status; + uint8_t amp_status; + uint32_t total_bandwidth; + uint32_t max_guaranteed_bandwidth; + uint32_t min_latency; + uint32_t max_pdu_size; + uint8_t controller_type; + uint16_t pal_caps; + uint16_t max_amp_assoc_length; + uint32_t max_flush_timeout; + uint32_t best_effort_flush_timeout; +} __attribute__ ((packed)) read_local_amp_info_rp; +#define READ_LOCAL_AMP_INFO_RP_SIZE 31 + +#define OCF_READ_LOCAL_AMP_ASSOC 0x000A +typedef struct { + uint8_t handle; + uint16_t length_so_far; + uint16_t assoc_length; +} __attribute__ ((packed)) read_local_amp_assoc_cp; +#define READ_LOCAL_AMP_ASSOC_CP_SIZE 5 +typedef struct { + uint8_t status; + uint8_t handle; + uint16_t length; + uint8_t fragment[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) read_local_amp_assoc_rp; +#define READ_LOCAL_AMP_ASSOC_RP_SIZE 252 + +#define OCF_WRITE_REMOTE_AMP_ASSOC 0x000B +typedef struct { + uint8_t handle; + uint16_t length_so_far; + uint16_t remaining_length; + uint8_t fragment[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) write_remote_amp_assoc_cp; +#define WRITE_REMOTE_AMP_ASSOC_CP_SIZE 253 +typedef struct { + uint8_t status; + uint8_t handle; +} __attribute__ ((packed)) write_remote_amp_assoc_rp; +#define WRITE_REMOTE_AMP_ASSOC_RP_SIZE 2 + +/* Testing commands */ +#define OGF_TESTING_CMD 0x3e + +#define OCF_READ_LOOPBACK_MODE 0x0001 + +#define OCF_WRITE_LOOPBACK_MODE 0x0002 + +#define OCF_ENABLE_DEVICE_UNDER_TEST_MODE 0x0003 + +#define OCF_WRITE_SIMPLE_PAIRING_DEBUG_MODE 0x0004 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_simple_pairing_debug_mode_cp; +#define WRITE_SIMPLE_PAIRING_DEBUG_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_simple_pairing_debug_mode_rp; +#define WRITE_SIMPLE_PAIRING_DEBUG_MODE_RP_SIZE 1 + +/* LE commands */ +#define OGF_LE_CTL 0x08 + +#define OCF_LE_SET_EVENT_MASK 0x0001 +typedef struct { + uint8_t mask[8]; +} __attribute__ ((packed)) le_set_event_mask_cp; +#define LE_SET_EVENT_MASK_CP_SIZE 8 + +#define OCF_LE_READ_BUFFER_SIZE 0x0002 +typedef struct { + uint8_t status; + uint16_t pkt_len; + uint8_t max_pkt; +} __attribute__ ((packed)) le_read_buffer_size_rp; +#define LE_READ_BUFFER_SIZE_RP_SIZE 4 + +#define OCF_LE_READ_LOCAL_SUPPORTED_FEATURES 0x0003 +typedef struct { + uint8_t status; + uint8_t features[8]; +} __attribute__ ((packed)) le_read_local_supported_features_rp; +#define LE_READ_LOCAL_SUPPORTED_FEATURES_RP_SIZE 9 + +#define OCF_LE_SET_RANDOM_ADDRESS 0x0005 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) le_set_random_address_cp; +#define LE_SET_RANDOM_ADDRESS_CP_SIZE 6 + +#define OCF_LE_SET_ADVERTISING_PARAMETERS 0x0006 +typedef struct { + uint16_t min_interval; + uint16_t max_interval; + uint8_t advtype; + uint8_t own_bdaddr_type; + uint8_t direct_bdaddr_type; + bdaddr_t direct_bdaddr; + uint8_t chan_map; + uint8_t filter; +} __attribute__ ((packed)) le_set_advertising_parameters_cp; +#define LE_SET_ADVERTISING_PARAMETERS_CP_SIZE 15 + +#define OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER 0x0007 +typedef struct { + uint8_t status; + int8_t level; +} __attribute__ ((packed)) le_read_advertising_channel_tx_power_rp; +#define LE_READ_ADVERTISING_CHANNEL_TX_POWER_RP_SIZE 2 + +#define OCF_LE_SET_ADVERTISING_DATA 0x0008 +typedef struct { + uint8_t length; + uint8_t data[31]; +} __attribute__ ((packed)) le_set_advertising_data_cp; +#define LE_SET_ADVERTISING_DATA_CP_SIZE 32 + +#define OCF_LE_SET_SCAN_RESPONSE_DATA 0x0009 +typedef struct { + uint8_t length; + uint8_t data[31]; +} __attribute__ ((packed)) le_set_scan_response_data_cp; +#define LE_SET_SCAN_RESPONSE_DATA_CP_SIZE 32 + +#define OCF_LE_SET_ADVERTISE_ENABLE 0x000A +typedef struct { + uint8_t enable; +} __attribute__ ((packed)) le_set_advertise_enable_cp; +#define LE_SET_ADVERTISE_ENABLE_CP_SIZE 1 + +#define OCF_LE_SET_SCAN_PARAMETERS 0x000B +typedef struct { + uint8_t type; + uint16_t interval; + uint16_t window; + uint8_t own_bdaddr_type; + uint8_t filter; +} __attribute__ ((packed)) le_set_scan_parameters_cp; +#define LE_SET_SCAN_PARAMETERS_CP_SIZE 7 + +#define OCF_LE_SET_SCAN_ENABLE 0x000C +typedef struct { + uint8_t enable; + uint8_t filter_dup; +} __attribute__ ((packed)) le_set_scan_enable_cp; +#define LE_SET_SCAN_ENABLE_CP_SIZE 2 + +#define OCF_LE_CREATE_CONN 0x000D +typedef struct { + uint16_t interval; + uint16_t window; + uint8_t initiator_filter; + uint8_t peer_bdaddr_type; + bdaddr_t peer_bdaddr; + uint8_t own_bdaddr_type; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t supervision_timeout; + uint16_t min_ce_length; + uint16_t max_ce_length; +} __attribute__ ((packed)) le_create_connection_cp; +#define LE_CREATE_CONN_CP_SIZE 25 + +#define OCF_LE_CREATE_CONN_CANCEL 0x000E + +#define OCF_LE_READ_WHITE_LIST_SIZE 0x000F +typedef struct { + uint8_t status; + uint8_t size; +} __attribute__ ((packed)) le_read_white_list_size_rp; +#define LE_READ_WHITE_LIST_SIZE_RP_SIZE 2 + +#define OCF_LE_CLEAR_WHITE_LIST 0x0010 + +#define OCF_LE_ADD_DEVICE_TO_WHITE_LIST 0x0011 +typedef struct { + uint8_t bdaddr_type; + bdaddr_t bdaddr; +} __attribute__ ((packed)) le_add_device_to_white_list_cp; +#define LE_ADD_DEVICE_TO_WHITE_LIST_CP_SIZE 7 + +#define OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST 0x0012 +typedef struct { + uint8_t bdaddr_type; + bdaddr_t bdaddr; +} __attribute__ ((packed)) le_remove_device_from_white_list_cp; +#define LE_REMOVE_DEVICE_FROM_WHITE_LIST_CP_SIZE 7 + +#define OCF_LE_CONN_UPDATE 0x0013 +typedef struct { + uint16_t handle; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t supervision_timeout; + uint16_t min_ce_length; + uint16_t max_ce_length; +} __attribute__ ((packed)) le_connection_update_cp; +#define LE_CONN_UPDATE_CP_SIZE 14 + +#define OCF_LE_SET_HOST_CHANNEL_CLASSIFICATION 0x0014 +typedef struct { + uint8_t map[5]; +} __attribute__ ((packed)) le_set_host_channel_classification_cp; +#define LE_SET_HOST_CHANNEL_CLASSIFICATION_CP_SIZE 5 + +#define OCF_LE_READ_CHANNEL_MAP 0x0015 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) le_read_channel_map_cp; +#define LE_READ_CHANNEL_MAP_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t map[5]; +} __attribute__ ((packed)) le_read_channel_map_rp; +#define LE_READ_CHANNEL_MAP_RP_SIZE 8 + +#define OCF_LE_READ_REMOTE_USED_FEATURES 0x0016 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) le_read_remote_used_features_cp; +#define LE_READ_REMOTE_USED_FEATURES_CP_SIZE 2 + +#define OCF_LE_ENCRYPT 0x0017 +typedef struct { + uint8_t key[16]; + uint8_t plaintext[16]; +} __attribute__ ((packed)) le_encrypt_cp; +#define LE_ENCRYPT_CP_SIZE 32 +typedef struct { + uint8_t status; + uint8_t data[16]; +} __attribute__ ((packed)) le_encrypt_rp; +#define LE_ENCRYPT_RP_SIZE 17 + +#define OCF_LE_RAND 0x0018 +typedef struct { + uint8_t status; + uint64_t random; +} __attribute__ ((packed)) le_rand_rp; +#define LE_RAND_RP_SIZE 9 + +#define OCF_LE_START_ENCRYPTION 0x0019 +typedef struct { + uint16_t handle; + uint64_t random; + uint16_t diversifier; + uint8_t key[16]; +} __attribute__ ((packed)) le_start_encryption_cp; +#define LE_START_ENCRYPTION_CP_SIZE 28 + +#define OCF_LE_LTK_REPLY 0x001A +typedef struct { + uint16_t handle; + uint8_t key[16]; +} __attribute__ ((packed)) le_ltk_reply_cp; +#define LE_LTK_REPLY_CP_SIZE 18 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) le_ltk_reply_rp; +#define LE_LTK_REPLY_RP_SIZE 3 + +#define OCF_LE_LTK_NEG_REPLY 0x001B +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) le_ltk_neg_reply_cp; +#define LE_LTK_NEG_REPLY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) le_ltk_neg_reply_rp; +#define LE_LTK_NEG_REPLY_RP_SIZE 3 + +#define OCF_LE_READ_SUPPORTED_STATES 0x001C +typedef struct { + uint8_t status; + uint64_t states; +} __attribute__ ((packed)) le_read_supported_states_rp; +#define LE_READ_SUPPORTED_STATES_RP_SIZE 9 + +#define OCF_LE_RECEIVER_TEST 0x001D +typedef struct { + uint8_t frequency; +} __attribute__ ((packed)) le_receiver_test_cp; +#define LE_RECEIVER_TEST_CP_SIZE 1 + +#define OCF_LE_TRANSMITTER_TEST 0x001E +typedef struct { + uint8_t frequency; + uint8_t length; + uint8_t payload; +} __attribute__ ((packed)) le_transmitter_test_cp; +#define LE_TRANSMITTER_TEST_CP_SIZE 3 + +#define OCF_LE_TEST_END 0x001F +typedef struct { + uint8_t status; + uint16_t num_pkts; +} __attribute__ ((packed)) le_test_end_rp; +#define LE_TEST_END_RP_SIZE 3 + +#define OCF_LE_ADD_DEVICE_TO_RESOLV_LIST 0x0027 +typedef struct { + uint8_t bdaddr_type; + bdaddr_t bdaddr; + uint8_t peer_irk[16]; + uint8_t local_irk[16]; +} __attribute__ ((packed)) le_add_device_to_resolv_list_cp; +#define LE_ADD_DEVICE_TO_RESOLV_LIST_CP_SIZE 39 + +#define OCF_LE_REMOVE_DEVICE_FROM_RESOLV_LIST 0x0028 +typedef struct { + uint8_t bdaddr_type; + bdaddr_t bdaddr; +} __attribute__ ((packed)) le_remove_device_from_resolv_list_cp; +#define LE_REMOVE_DEVICE_FROM_RESOLV_LIST_CP_SIZE 7 + +#define OCF_LE_CLEAR_RESOLV_LIST 0x0029 + +#define OCF_LE_READ_RESOLV_LIST_SIZE 0x002A +typedef struct { + uint8_t status; + uint8_t size; +} __attribute__ ((packed)) le_read_resolv_list_size_rp; +#define LE_READ_RESOLV_LIST_SIZE_RP_SIZE 2 + +#define OCF_LE_SET_ADDRESS_RESOLUTION_ENABLE 0x002D +typedef struct { + uint8_t enable; +} __attribute__ ((packed)) le_set_address_resolution_enable_cp; +#define LE_SET_ADDRESS_RESOLUTION_ENABLE_CP_SIZE 1 + +/* Vendor specific commands */ +#define OGF_VENDOR_CMD 0x3f + +/* ---- HCI Events ---- */ + +#define EVT_INQUIRY_COMPLETE 0x01 + +#define EVT_INQUIRY_RESULT 0x02 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +} __attribute__ ((packed)) inquiry_info; +#define INQUIRY_INFO_SIZE 14 + +#define EVT_CONN_COMPLETE 0x03 +typedef struct { + uint8_t status; + uint16_t handle; + bdaddr_t bdaddr; + uint8_t link_type; + uint8_t encr_mode; +} __attribute__ ((packed)) evt_conn_complete; +#define EVT_CONN_COMPLETE_SIZE 11 + +#define EVT_CONN_REQUEST 0x04 +typedef struct { + bdaddr_t bdaddr; + uint8_t dev_class[3]; + uint8_t link_type; +} __attribute__ ((packed)) evt_conn_request; +#define EVT_CONN_REQUEST_SIZE 10 + +#define EVT_DISCONN_COMPLETE 0x05 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)) evt_disconn_complete; +#define EVT_DISCONN_COMPLETE_SIZE 4 + +#define EVT_AUTH_COMPLETE 0x06 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_auth_complete; +#define EVT_AUTH_COMPLETE_SIZE 3 + +#define EVT_REMOTE_NAME_REQ_COMPLETE 0x07 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; + uint8_t name[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) evt_remote_name_req_complete; +#define EVT_REMOTE_NAME_REQ_COMPLETE_SIZE 255 + +#define EVT_ENCRYPT_CHANGE 0x08 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t encrypt; +} __attribute__ ((packed)) evt_encrypt_change; +#define EVT_ENCRYPT_CHANGE_SIZE 4 + +#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE 0x09 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_change_conn_link_key_complete; +#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE_SIZE 3 + +#define EVT_MASTER_LINK_KEY_COMPLETE 0x0A +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t key_flag; +} __attribute__ ((packed)) evt_master_link_key_complete; +#define EVT_MASTER_LINK_KEY_COMPLETE_SIZE 4 + +#define EVT_READ_REMOTE_FEATURES_COMPLETE 0x0B +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __attribute__ ((packed)) evt_read_remote_features_complete; +#define EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE 11 + +#define EVT_READ_REMOTE_VERSION_COMPLETE 0x0C +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)) evt_read_remote_version_complete; +#define EVT_READ_REMOTE_VERSION_COMPLETE_SIZE 8 + +#define EVT_QOS_SETUP_COMPLETE 0x0D +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t flags; /* Reserved */ + hci_qos qos; +} __attribute__ ((packed)) evt_qos_setup_complete; +#define EVT_QOS_SETUP_COMPLETE_SIZE (4 + HCI_QOS_CP_SIZE) + +#define EVT_CMD_COMPLETE 0x0E +typedef struct { + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)) evt_cmd_complete; +#define EVT_CMD_COMPLETE_SIZE 3 + +#define EVT_CMD_STATUS 0x0F +typedef struct { + uint8_t status; + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)) evt_cmd_status; +#define EVT_CMD_STATUS_SIZE 4 + +#define EVT_HARDWARE_ERROR 0x10 +typedef struct { + uint8_t code; +} __attribute__ ((packed)) evt_hardware_error; +#define EVT_HARDWARE_ERROR_SIZE 1 + +#define EVT_FLUSH_OCCURRED 0x11 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_flush_occured; +#define EVT_FLUSH_OCCURRED_SIZE 2 + +#define EVT_ROLE_CHANGE 0x12 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) evt_role_change; +#define EVT_ROLE_CHANGE_SIZE 8 + +#define EVT_NUM_COMP_PKTS 0x13 +typedef struct { + uint8_t num_hndl; + /* variable length part */ +} __attribute__ ((packed)) evt_num_comp_pkts; +#define EVT_NUM_COMP_PKTS_SIZE 1 + +#define EVT_MODE_CHANGE 0x14 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint16_t interval; +} __attribute__ ((packed)) evt_mode_change; +#define EVT_MODE_CHANGE_SIZE 6 + +#define EVT_RETURN_LINK_KEYS 0x15 +typedef struct { + uint8_t num_keys; + /* variable length part */ +} __attribute__ ((packed)) evt_return_link_keys; +#define EVT_RETURN_LINK_KEYS_SIZE 1 + +#define EVT_PIN_CODE_REQ 0x16 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_pin_code_req; +#define EVT_PIN_CODE_REQ_SIZE 6 + +#define EVT_LINK_KEY_REQ 0x17 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_link_key_req; +#define EVT_LINK_KEY_REQ_SIZE 6 + +#define EVT_LINK_KEY_NOTIFY 0x18 +typedef struct { + bdaddr_t bdaddr; + uint8_t link_key[16]; + uint8_t key_type; +} __attribute__ ((packed)) evt_link_key_notify; +#define EVT_LINK_KEY_NOTIFY_SIZE 23 + +#define EVT_LOOPBACK_COMMAND 0x19 + +#define EVT_DATA_BUFFER_OVERFLOW 0x1A +typedef struct { + uint8_t link_type; +} __attribute__ ((packed)) evt_data_buffer_overflow; +#define EVT_DATA_BUFFER_OVERFLOW_SIZE 1 + +#define EVT_MAX_SLOTS_CHANGE 0x1B +typedef struct { + uint16_t handle; + uint8_t max_slots; +} __attribute__ ((packed)) evt_max_slots_change; +#define EVT_MAX_SLOTS_CHANGE_SIZE 3 + +#define EVT_READ_CLOCK_OFFSET_COMPLETE 0x1C +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t clock_offset; +} __attribute__ ((packed)) evt_read_clock_offset_complete; +#define EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE 5 + +#define EVT_CONN_PTYPE_CHANGED 0x1D +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t ptype; +} __attribute__ ((packed)) evt_conn_ptype_changed; +#define EVT_CONN_PTYPE_CHANGED_SIZE 5 + +#define EVT_QOS_VIOLATION 0x1E +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_qos_violation; +#define EVT_QOS_VIOLATION_SIZE 2 + +#define EVT_PSCAN_REP_MODE_CHANGE 0x20 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; +} __attribute__ ((packed)) evt_pscan_rep_mode_change; +#define EVT_PSCAN_REP_MODE_CHANGE_SIZE 7 + +#define EVT_FLOW_SPEC_COMPLETE 0x21 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t flags; + uint8_t direction; + hci_qos qos; +} __attribute__ ((packed)) evt_flow_spec_complete; +#define EVT_FLOW_SPEC_COMPLETE_SIZE (5 + HCI_QOS_CP_SIZE) + +#define EVT_INQUIRY_RESULT_WITH_RSSI 0x22 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; +} __attribute__ ((packed)) inquiry_info_with_rssi; +#define INQUIRY_INFO_WITH_RSSI_SIZE 14 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; +} __attribute__ ((packed)) inquiry_info_with_rssi_and_pscan_mode; +#define INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE 15 + +#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE 0x23 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t page_num; + uint8_t max_page_num; + uint8_t features[8]; +} __attribute__ ((packed)) evt_read_remote_ext_features_complete; +#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE 13 + +#define EVT_SYNC_CONN_COMPLETE 0x2C +typedef struct { + uint8_t status; + uint16_t handle; + bdaddr_t bdaddr; + uint8_t link_type; + uint8_t trans_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; + uint8_t air_mode; +} __attribute__ ((packed)) evt_sync_conn_complete; +#define EVT_SYNC_CONN_COMPLETE_SIZE 17 + +#define EVT_SYNC_CONN_CHANGED 0x2D +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t trans_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; +} __attribute__ ((packed)) evt_sync_conn_changed; +#define EVT_SYNC_CONN_CHANGED_SIZE 9 + +#define EVT_SNIFF_SUBRATING 0x2E +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t max_tx_latency; + uint16_t max_rx_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)) evt_sniff_subrating; +#define EVT_SNIFF_SUBRATING_SIZE 11 + +#define EVT_EXTENDED_INQUIRY_RESULT 0x2F +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[HCI_MAX_EIR_LENGTH]; +} __attribute__ ((packed)) extended_inquiry_info; +#define EXTENDED_INQUIRY_INFO_SIZE 254 + +#define EVT_ENCRYPTION_KEY_REFRESH_COMPLETE 0x30 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_encryption_key_refresh_complete; +#define EVT_ENCRYPTION_KEY_REFRESH_COMPLETE_SIZE 3 + +#define EVT_IO_CAPABILITY_REQUEST 0x31 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_io_capability_request; +#define EVT_IO_CAPABILITY_REQUEST_SIZE 6 + +#define EVT_IO_CAPABILITY_RESPONSE 0x32 +typedef struct { + bdaddr_t bdaddr; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __attribute__ ((packed)) evt_io_capability_response; +#define EVT_IO_CAPABILITY_RESPONSE_SIZE 9 + +#define EVT_USER_CONFIRM_REQUEST 0x33 +typedef struct { + bdaddr_t bdaddr; + uint32_t passkey; +} __attribute__ ((packed)) evt_user_confirm_request; +#define EVT_USER_CONFIRM_REQUEST_SIZE 10 + +#define EVT_USER_PASSKEY_REQUEST 0x34 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_user_passkey_request; +#define EVT_USER_PASSKEY_REQUEST_SIZE 6 + +#define EVT_REMOTE_OOB_DATA_REQUEST 0x35 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_remote_oob_data_request; +#define EVT_REMOTE_OOB_DATA_REQUEST_SIZE 6 + +#define EVT_SIMPLE_PAIRING_COMPLETE 0x36 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_simple_pairing_complete; +#define EVT_SIMPLE_PAIRING_COMPLETE_SIZE 7 + +#define EVT_LINK_SUPERVISION_TIMEOUT_CHANGED 0x38 +typedef struct { + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)) evt_link_supervision_timeout_changed; +#define EVT_LINK_SUPERVISION_TIMEOUT_CHANGED_SIZE 4 + +#define EVT_ENHANCED_FLUSH_COMPLETE 0x39 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_enhanced_flush_complete; +#define EVT_ENHANCED_FLUSH_COMPLETE_SIZE 2 + +#define EVT_USER_PASSKEY_NOTIFY 0x3B +typedef struct { + bdaddr_t bdaddr; + uint32_t passkey; +} __attribute__ ((packed)) evt_user_passkey_notify; +#define EVT_USER_PASSKEY_NOTIFY_SIZE 10 + +#define EVT_KEYPRESS_NOTIFY 0x3C +typedef struct { + bdaddr_t bdaddr; + uint8_t type; +} __attribute__ ((packed)) evt_keypress_notify; +#define EVT_KEYPRESS_NOTIFY_SIZE 7 + +#define EVT_REMOTE_HOST_FEATURES_NOTIFY 0x3D +typedef struct { + bdaddr_t bdaddr; + uint8_t features[8]; +} __attribute__ ((packed)) evt_remote_host_features_notify; +#define EVT_REMOTE_HOST_FEATURES_NOTIFY_SIZE 14 + +#define EVT_LE_META_EVENT 0x3E +typedef struct { + uint8_t subevent; + uint8_t data[]; +} __attribute__ ((packed)) evt_le_meta_event; +#define EVT_LE_META_EVENT_SIZE 1 + +#define EVT_LE_CONN_COMPLETE 0x01 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t role; + uint8_t peer_bdaddr_type; + bdaddr_t peer_bdaddr; + uint16_t interval; + uint16_t latency; + uint16_t supervision_timeout; + uint8_t master_clock_accuracy; +} __attribute__ ((packed)) evt_le_connection_complete; +#define EVT_LE_CONN_COMPLETE_SIZE 18 + +#define EVT_LE_ADVERTISING_REPORT 0x02 +typedef struct { + uint8_t evt_type; + uint8_t bdaddr_type; + bdaddr_t bdaddr; + uint8_t length; + uint8_t data[]; +} __attribute__ ((packed)) le_advertising_info; +#define LE_ADVERTISING_INFO_SIZE 9 + +#define EVT_LE_CONN_UPDATE_COMPLETE 0x03 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t interval; + uint16_t latency; + uint16_t supervision_timeout; +} __attribute__ ((packed)) evt_le_connection_update_complete; +#define EVT_LE_CONN_UPDATE_COMPLETE_SIZE 9 + +#define EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE 0x04 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __attribute__ ((packed)) evt_le_read_remote_used_features_complete; +#define EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE_SIZE 11 + +#define EVT_LE_LTK_REQUEST 0x05 +typedef struct { + uint16_t handle; + uint64_t random; + uint16_t diversifier; +} __attribute__ ((packed)) evt_le_long_term_key_request; +#define EVT_LE_LTK_REQUEST_SIZE 12 + +#define EVT_PHYSICAL_LINK_COMPLETE 0x40 +typedef struct { + uint8_t status; + uint8_t handle; +} __attribute__ ((packed)) evt_physical_link_complete; +#define EVT_PHYSICAL_LINK_COMPLETE_SIZE 2 + +#define EVT_CHANNEL_SELECTED 0x41 + +#define EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE 0x42 +typedef struct { + uint8_t status; + uint8_t handle; + uint8_t reason; +} __attribute__ ((packed)) evt_disconn_physical_link_complete; +#define EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE_SIZE 3 + +#define EVT_PHYSICAL_LINK_LOSS_EARLY_WARNING 0x43 +typedef struct { + uint8_t handle; + uint8_t reason; +} __attribute__ ((packed)) evt_physical_link_loss_warning; +#define EVT_PHYSICAL_LINK_LOSS_WARNING_SIZE 2 + +#define EVT_PHYSICAL_LINK_RECOVERY 0x44 +typedef struct { + uint8_t handle; +} __attribute__ ((packed)) evt_physical_link_recovery; +#define EVT_PHYSICAL_LINK_RECOVERY_SIZE 1 + +#define EVT_LOGICAL_LINK_COMPLETE 0x45 +typedef struct { + uint8_t status; + uint16_t log_handle; + uint8_t handle; + uint8_t tx_flow_id; +} __attribute__ ((packed)) evt_logical_link_complete; +#define EVT_LOGICAL_LINK_COMPLETE_SIZE 5 + +#define EVT_DISCONNECT_LOGICAL_LINK_COMPLETE 0x46 + +#define EVT_FLOW_SPEC_MODIFY_COMPLETE 0x47 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_flow_spec_modify_complete; +#define EVT_FLOW_SPEC_MODIFY_COMPLETE_SIZE 3 + +#define EVT_NUMBER_COMPLETED_BLOCKS 0x48 +typedef struct { + uint16_t handle; + uint16_t num_cmplt_pkts; + uint16_t num_cmplt_blks; +} __attribute__ ((packed)) cmplt_handle; +typedef struct { + uint16_t total_num_blocks; + uint8_t num_handles; + cmplt_handle handles[]; +} __attribute__ ((packed)) evt_num_completed_blocks; + +#define EVT_AMP_STATUS_CHANGE 0x4D +typedef struct { + uint8_t status; + uint8_t amp_status; +} __attribute__ ((packed)) evt_amp_status_change; +#define EVT_AMP_STATUS_CHANGE_SIZE 2 + +#define EVT_TESTING 0xFE + +#define EVT_VENDOR 0xFF + +/* Internal events generated by BlueZ stack */ +#define EVT_STACK_INTERNAL 0xFD +typedef struct { + uint16_t type; + uint8_t data[]; +} __attribute__ ((packed)) evt_stack_internal; +#define EVT_STACK_INTERNAL_SIZE 2 + +#define EVT_SI_DEVICE 0x01 +typedef struct { + uint16_t event; + uint16_t dev_id; +} __attribute__ ((packed)) evt_si_device; +#define EVT_SI_DEVICE_SIZE 4 + +/* -------- HCI Packet structures -------- */ +#define HCI_TYPE_LEN 1 + +typedef struct { + uint16_t opcode; /* OCF & OGF */ + uint8_t plen; +} __attribute__ ((packed)) hci_command_hdr; +#define HCI_COMMAND_HDR_SIZE 3 + +typedef struct { + uint8_t evt; + uint8_t plen; +} __attribute__ ((packed)) hci_event_hdr; +#define HCI_EVENT_HDR_SIZE 2 + +typedef struct { + uint16_t handle; /* Handle & Flags(PB, BC) */ + uint16_t dlen; +} __attribute__ ((packed)) hci_acl_hdr; +#define HCI_ACL_HDR_SIZE 4 + +typedef struct { + uint16_t handle; + uint8_t dlen; +} __attribute__ ((packed)) hci_sco_hdr; +#define HCI_SCO_HDR_SIZE 3 + +typedef struct { + uint16_t device; + uint16_t type; + uint16_t plen; +} __attribute__ ((packed)) hci_msg_hdr; +#define HCI_MSG_HDR_SIZE 6 + +typedef struct { + uint16_t handle; + uint16_t dlen; +} __attribute__ ((packed)) hci_iso_hdr; +#define HCI_ISO_HDR_SIZE 4 + +/* Command opcode pack/unpack */ +#define cmd_opcode_pack(ogf, ocf) (uint16_t)((ocf & 0x03ff)|(ogf << 10)) +#define cmd_opcode_ogf(op) (op >> 10) +#define cmd_opcode_ocf(op) (op & 0x03ff) + +/* ACL handle and flags pack/unpack */ +#define acl_handle_pack(h, f) (uint16_t)((h & 0x0fff)|(f << 12)) +#define acl_handle(h) (h & 0x0fff) +#define acl_flags(h) (h >> 12) + +/* ISO handle and flags pack/unpack */ +#define iso_flags_pb(f) (f & 0x0003) +#define iso_flags_ts(f) ((f >> 2) & 0x0001) +#define iso_flags_pack(pb, ts) ((pb & 0x03) | ((ts & 0x01) << 2)) + +#endif /* _NO_HCI_DEFS */ + +/* HCI Socket options */ +#define HCI_DATA_DIR 1 +#define HCI_FILTER 2 +#define HCI_TIME_STAMP 3 + +/* HCI CMSG flags */ +#define HCI_CMSG_DIR 0x0001 +#define HCI_CMSG_TSTAMP 0x0002 + +struct sockaddr_hci { + sa_family_t hci_family; + unsigned short hci_dev; + unsigned short hci_channel; +}; +#define HCI_DEV_NONE 0xffff + +#define HCI_CHANNEL_RAW 0 +#define HCI_CHANNEL_USER 1 +#define HCI_CHANNEL_MONITOR 2 +#define HCI_CHANNEL_CONTROL 3 +#define HCI_CHANNEL_LOGGING 4 + +struct hci_filter { + uint32_t type_mask; + uint32_t event_mask[2]; + uint16_t opcode; +}; + +#define HCI_FLT_TYPE_BITS 31 +#define HCI_FLT_EVENT_BITS 63 +#define HCI_FLT_OGF_BITS 63 +#define HCI_FLT_OCF_BITS 127 + +/* Ioctl requests structures */ +struct hci_dev_stats { + uint32_t err_rx; + uint32_t err_tx; + uint32_t cmd_tx; + uint32_t evt_rx; + uint32_t acl_tx; + uint32_t acl_rx; + uint32_t sco_tx; + uint32_t sco_rx; + uint32_t byte_rx; + uint32_t byte_tx; +}; + +struct hci_dev_info { + uint16_t dev_id; + char name[8]; + + bdaddr_t bdaddr; + + uint32_t flags; + uint8_t type; + + uint8_t features[8]; + + uint32_t pkt_type; + uint32_t link_policy; + uint32_t link_mode; + + uint16_t acl_mtu; + uint16_t acl_pkts; + uint16_t sco_mtu; + uint16_t sco_pkts; + + struct hci_dev_stats stat; +}; + +struct hci_conn_info { + uint16_t handle; + bdaddr_t bdaddr; + uint8_t type; + uint8_t out; + uint16_t state; + uint32_t link_mode; +}; + +struct hci_dev_req { + uint16_t dev_id; + uint32_t dev_opt; +}; + +struct hci_dev_list_req { + uint16_t dev_num; + struct hci_dev_req dev_req[]; /* hci_dev_req structures */ +}; + +struct hci_conn_list_req { + uint16_t dev_id; + uint16_t conn_num; + struct hci_conn_info conn_info[]; +}; + +struct hci_conn_info_req { + bdaddr_t bdaddr; + uint8_t type; + struct hci_conn_info conn_info[]; +}; + +struct hci_auth_info_req { + bdaddr_t bdaddr; + uint8_t type; +}; + +struct hci_inquiry_req { + uint16_t dev_id; + uint16_t flags; + uint8_t lap[3]; + uint8_t length; + uint8_t num_rsp; +}; +#define IREQ_CACHE_FLUSH 0x0001 + +#ifdef __cplusplus +} +#endif + +#endif /* __HCI_H */ From 5c05b7f981bfd9b9d4c5f08d968c6b7690a930c2 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 03/73] Vendor BlueZ's hci_lib.h verbatim --- .../include/bluetooth/hci_lib.h | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/hci_lib.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/hci_lib.h b/Sources/CBluetoothLinuxABI/include/bluetooth/hci_lib.h new file mode 100644 index 0000000..eeb5141 --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/hci_lib.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + */ + +#ifndef __HCI_LIB_H +#define __HCI_LIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +struct hci_request { + uint16_t ogf; + uint16_t ocf; + int event; + void *cparam; + int clen; + void *rparam; + int rlen; +}; + +struct hci_version { + uint16_t manufacturer; + uint8_t hci_ver; + uint16_t hci_rev; + uint8_t lmp_ver; + uint16_t lmp_subver; +}; + +int hci_open_dev(int dev_id); +int hci_close_dev(int dd); +int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param); +int hci_send_req(int dd, struct hci_request *req, int timeout); + +int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to); +int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to); + +int hci_inquiry(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info **ii, long flags); +int hci_devinfo(int dev_id, struct hci_dev_info *di); +int hci_devba(int dev_id, bdaddr_t *bdaddr); +int hci_devid(const char *str); + +int hci_read_local_name(int dd, int len, char *name, int to); +int hci_write_local_name(int dd, const char *name, int to); +int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to); +int hci_read_remote_name_with_clock_offset(int dd, const bdaddr_t *bdaddr, uint8_t pscan_rep_mode, uint16_t clkoffset, int len, char *name, int to); +int hci_read_remote_name_cancel(int dd, const bdaddr_t *bdaddr, int to); +int hci_read_remote_version(int dd, uint16_t handle, struct hci_version *ver, int to); +int hci_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to); +int hci_read_remote_ext_features(int dd, uint16_t handle, uint8_t page, uint8_t *max_page, uint8_t *features, int to); +int hci_read_clock_offset(int dd, uint16_t handle, uint16_t *clkoffset, int to); +int hci_read_local_version(int dd, struct hci_version *ver, int to); +int hci_read_local_commands(int dd, uint8_t *commands, int to); +int hci_read_local_features(int dd, uint8_t *features, int to); +int hci_read_local_ext_features(int dd, uint8_t page, uint8_t *max_page, uint8_t *features, int to); +int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to); +int hci_read_class_of_dev(int dd, uint8_t *cls, int to); +int hci_write_class_of_dev(int dd, uint32_t cls, int to); +int hci_read_voice_setting(int dd, uint16_t *vs, int to); +int hci_write_voice_setting(int dd, uint16_t vs, int to); +int hci_read_current_iac_lap(int dd, uint8_t *num_iac, uint8_t *lap, int to); +int hci_write_current_iac_lap(int dd, uint8_t num_iac, uint8_t *lap, int to); +int hci_read_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to); +int hci_write_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t *key, int to); +int hci_delete_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to); +int hci_authenticate_link(int dd, uint16_t handle, int to); +int hci_encrypt_link(int dd, uint16_t handle, uint8_t encrypt, int to); +int hci_change_link_key(int dd, uint16_t handle, int to); +int hci_switch_role(int dd, bdaddr_t *bdaddr, uint8_t role, int to); +int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval, uint16_t min_interval, int to); +int hci_exit_park_mode(int dd, uint16_t handle, int to); +int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to); +int hci_write_inquiry_scan_type(int dd, uint8_t type, int to); +int hci_read_inquiry_mode(int dd, uint8_t *mode, int to); +int hci_write_inquiry_mode(int dd, uint8_t mode, int to); +int hci_read_afh_mode(int dd, uint8_t *mode, int to); +int hci_write_afh_mode(int dd, uint8_t mode, int to); +int hci_read_ext_inquiry_response(int dd, uint8_t *fec, uint8_t *data, int to); +int hci_write_ext_inquiry_response(int dd, uint8_t fec, uint8_t *data, int to); +int hci_read_simple_pairing_mode(int dd, uint8_t *mode, int to); +int hci_write_simple_pairing_mode(int dd, uint8_t mode, int to); +int hci_read_local_oob_data(int dd, uint8_t *hash, uint8_t *randomizer, int to); +int hci_read_inq_response_tx_power_level(int dd, int8_t *level, int to); +int hci_read_inquiry_transmit_power_level(int dd, int8_t *level, int to); +int hci_write_inquiry_transmit_power_level(int dd, int8_t level, int to); +int hci_read_transmit_power_level(int dd, uint16_t handle, uint8_t type, int8_t *level, int to); +int hci_read_link_policy(int dd, uint16_t handle, uint16_t *policy, int to); +int hci_write_link_policy(int dd, uint16_t handle, uint16_t policy, int to); +int hci_read_link_supervision_timeout(int dd, uint16_t handle, uint16_t *timeout, int to); +int hci_write_link_supervision_timeout(int dd, uint16_t handle, uint16_t timeout, int to); +int hci_set_afh_classification(int dd, uint8_t *map, int to); +int hci_read_link_quality(int dd, uint16_t handle, uint8_t *link_quality, int to); +int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to); +int hci_read_afh_map(int dd, uint16_t handle, uint8_t *mode, uint8_t *map, int to); +int hci_read_clock(int dd, uint16_t handle, uint8_t which, uint32_t *clock, uint16_t *accuracy, int to); + +int hci_le_set_scan_enable(int dev_id, uint8_t enable, uint8_t filter_dup, int to); +int hci_le_set_scan_parameters(int dev_id, uint8_t type, uint16_t interval, + uint16_t window, uint8_t own_type, + uint8_t filter, int to); +int hci_le_set_advertise_enable(int dev_id, uint8_t enable, int to); +int hci_le_create_conn(int dd, uint16_t interval, uint16_t window, + uint8_t initiator_filter, uint8_t peer_bdaddr_type, + bdaddr_t peer_bdaddr, uint8_t own_bdaddr_type, + uint16_t min_interval, uint16_t max_interval, + uint16_t latency, uint16_t supervision_timeout, + uint16_t min_ce_length, uint16_t max_ce_length, + uint16_t *handle, int to); +int hci_le_conn_update(int dd, uint16_t handle, uint16_t min_interval, + uint16_t max_interval, uint16_t latency, + uint16_t supervision_timeout, int to); +int hci_le_add_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to); +int hci_le_rm_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to); +int hci_le_read_white_list_size(int dd, uint8_t *size, int to); +int hci_le_clear_white_list(int dd, int to); +int hci_le_add_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, + uint8_t *peer_irk, uint8_t *local_irk, int to); +int hci_le_rm_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to); +int hci_le_clear_resolving_list(int dd, int to); +int hci_le_read_resolving_list_size(int dd, uint8_t *size, int to); +int hci_le_set_address_resolution_enable(int dev_id, uint8_t enable, int to); +int hci_le_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to); + +int hci_for_each_dev(int flag, int(*func)(int dd, int dev_id, long arg), long arg); +int hci_get_route(bdaddr_t *bdaddr); + +const char *hci_bustostr(int bus); +char *hci_typetostr(int type); +const char *hci_dtypetostr(int type); +char *hci_dflagstostr(uint32_t flags); +char *hci_ptypetostr(unsigned int ptype); +int hci_strtoptype(char *str, unsigned int *val); +char *hci_scoptypetostr(unsigned int ptype); +int hci_strtoscoptype(char *str, unsigned int *val); +char *hci_lptostr(unsigned int ptype); +int hci_strtolp(char *str, unsigned int *val); +char *hci_lmtostr(unsigned int ptype); +int hci_strtolm(char *str, unsigned int *val); + +char *hci_cmdtostr(unsigned int cmd); +char *hci_commandstostr(const uint8_t *commands, const char *pref, int width); + +char *hci_vertostr(unsigned int ver); +int hci_strtover(char *str, unsigned int *ver); +char *lmp_vertostr(unsigned int ver); +int lmp_strtover(char *str, unsigned int *ver); +char *pal_vertostr(unsigned int ver); +int pal_strtover(char *str, unsigned int *ver); + +char *lmp_featurestostr(uint8_t *features, char *pref, int width); + +static inline void hci_set_bit(int nr, void *addr) +{ + *((uint32_t *) addr + (nr >> 5)) |= (1 << (nr & 31)); +} + +static inline void hci_clear_bit(int nr, void *addr) +{ + *((uint32_t *) addr + (nr >> 5)) &= ~(1 << (nr & 31)); +} + +static inline int hci_test_bit(int nr, void *addr) +{ + return *((uint32_t *) addr + (nr >> 5)) & (1 << (nr & 31)); +} + +/* HCI filter tools */ +static inline void hci_filter_clear(struct hci_filter *f) +{ + memset(f, 0, sizeof(*f)); +} +static inline void hci_filter_set_ptype(int t, struct hci_filter *f) +{ + hci_set_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); +} +static inline void hci_filter_clear_ptype(int t, struct hci_filter *f) +{ + hci_clear_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); +} +static inline int hci_filter_test_ptype(int t, struct hci_filter *f) +{ + return hci_test_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); +} +static inline void hci_filter_all_ptypes(struct hci_filter *f) +{ + memset((void *) &f->type_mask, 0xff, sizeof(f->type_mask)); +} +static inline void hci_filter_set_event(int e, struct hci_filter *f) +{ + hci_set_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask); +} +static inline void hci_filter_clear_event(int e, struct hci_filter *f) +{ + hci_clear_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask); +} +static inline int hci_filter_test_event(int e, struct hci_filter *f) +{ + return hci_test_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask); +} +static inline void hci_filter_all_events(struct hci_filter *f) +{ + memset((void *) f->event_mask, 0xff, sizeof(f->event_mask)); +} +static inline void hci_filter_set_opcode(int opcode, struct hci_filter *f) +{ + f->opcode = opcode; +} +static inline void hci_filter_clear_opcode(struct hci_filter *f) +{ + f->opcode = 0; +} +static inline int hci_filter_test_opcode(int opcode, struct hci_filter *f) +{ + return (f->opcode == opcode); +} + +#ifdef __cplusplus +} +#endif + +#endif /* __HCI_LIB_H */ From 59632e14c58eaf168f0d9bb02b1c4ee2c671678e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 04/73] Vendor BlueZ's sdp.h verbatim --- .../include/bluetooth/sdp.h | 529 ++++++++++++++++++ 1 file changed, 529 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/sdp.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/sdp.h b/Sources/CBluetoothLinuxABI/include/bluetooth/sdp.h new file mode 100644 index 0000000..6f05d43 --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/sdp.h @@ -0,0 +1,529 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + */ + +#ifndef __SDP_H +#define __SDP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define SDP_UNIX_PATH "/var/run/sdp" +#define SDP_RESPONSE_TIMEOUT 20 +#define SDP_REQ_BUFFER_SIZE 2048 +#define SDP_RSP_BUFFER_SIZE 65535 +#define SDP_PDU_CHUNK_SIZE 1024 + +/* + * All definitions are based on Bluetooth Assigned Numbers + * of the Bluetooth Specification + */ +#define SDP_PSM 0x0001 + +/* + * Protocol UUIDs + */ +#define SDP_UUID 0x0001 +#define UDP_UUID 0x0002 +#define RFCOMM_UUID 0x0003 +#define TCP_UUID 0x0004 +#define TCS_BIN_UUID 0x0005 +#define TCS_AT_UUID 0x0006 +#define ATT_UUID 0x0007 +#define OBEX_UUID 0x0008 +#define IP_UUID 0x0009 +#define FTP_UUID 0x000a +#define HTTP_UUID 0x000c +#define WSP_UUID 0x000e +#define BNEP_UUID 0x000f +#define UPNP_UUID 0x0010 +#define HIDP_UUID 0x0011 +#define HCRP_CTRL_UUID 0x0012 +#define HCRP_DATA_UUID 0x0014 +#define HCRP_NOTE_UUID 0x0016 +#define AVCTP_UUID 0x0017 +#define AVDTP_UUID 0x0019 +#define CMTP_UUID 0x001b +#define UDI_UUID 0x001d +#define MCAP_CTRL_UUID 0x001e +#define MCAP_DATA_UUID 0x001f +#define L2CAP_UUID 0x0100 + +/* + * Service class identifiers of standard services and service groups + */ +#define SDP_SERVER_SVCLASS_ID 0x1000 +#define BROWSE_GRP_DESC_SVCLASS_ID 0x1001 +#define PUBLIC_BROWSE_GROUP 0x1002 +#define SERIAL_PORT_SVCLASS_ID 0x1101 +#define LAN_ACCESS_SVCLASS_ID 0x1102 +#define DIALUP_NET_SVCLASS_ID 0x1103 +#define IRMC_SYNC_SVCLASS_ID 0x1104 +#define OBEX_OBJPUSH_SVCLASS_ID 0x1105 +#define OBEX_FILETRANS_SVCLASS_ID 0x1106 +#define IRMC_SYNC_CMD_SVCLASS_ID 0x1107 +#define HEADSET_SVCLASS_ID 0x1108 +#define CORDLESS_TELEPHONY_SVCLASS_ID 0x1109 +#define AUDIO_SOURCE_SVCLASS_ID 0x110a +#define AUDIO_SINK_SVCLASS_ID 0x110b +#define AV_REMOTE_TARGET_SVCLASS_ID 0x110c +#define ADVANCED_AUDIO_SVCLASS_ID 0x110d +#define AV_REMOTE_SVCLASS_ID 0x110e +#define AV_REMOTE_CONTROLLER_SVCLASS_ID 0x110f +#define INTERCOM_SVCLASS_ID 0x1110 +#define FAX_SVCLASS_ID 0x1111 +#define HEADSET_AGW_SVCLASS_ID 0x1112 +#define WAP_SVCLASS_ID 0x1113 +#define WAP_CLIENT_SVCLASS_ID 0x1114 +#define PANU_SVCLASS_ID 0x1115 +#define NAP_SVCLASS_ID 0x1116 +#define GN_SVCLASS_ID 0x1117 +#define DIRECT_PRINTING_SVCLASS_ID 0x1118 +#define REFERENCE_PRINTING_SVCLASS_ID 0x1119 +#define IMAGING_SVCLASS_ID 0x111a +#define IMAGING_RESPONDER_SVCLASS_ID 0x111b +#define IMAGING_ARCHIVE_SVCLASS_ID 0x111c +#define IMAGING_REFOBJS_SVCLASS_ID 0x111d +#define HANDSFREE_SVCLASS_ID 0x111e +#define HANDSFREE_AGW_SVCLASS_ID 0x111f +#define DIRECT_PRT_REFOBJS_SVCLASS_ID 0x1120 +#define REFLECTED_UI_SVCLASS_ID 0x1121 +#define BASIC_PRINTING_SVCLASS_ID 0x1122 +#define PRINTING_STATUS_SVCLASS_ID 0x1123 +#define HID_SVCLASS_ID 0x1124 +#define HCR_SVCLASS_ID 0x1125 +#define HCR_PRINT_SVCLASS_ID 0x1126 +#define HCR_SCAN_SVCLASS_ID 0x1127 +#define CIP_SVCLASS_ID 0x1128 +#define VIDEO_CONF_GW_SVCLASS_ID 0x1129 +#define UDI_MT_SVCLASS_ID 0x112a +#define UDI_TA_SVCLASS_ID 0x112b +#define AV_SVCLASS_ID 0x112c +#define SAP_SVCLASS_ID 0x112d +#define PBAP_PCE_SVCLASS_ID 0x112e +#define PBAP_PSE_SVCLASS_ID 0x112f +#define PBAP_SVCLASS_ID 0x1130 +#define MAP_MSE_SVCLASS_ID 0x1132 +#define MAP_MCE_SVCLASS_ID 0x1133 +#define MAP_SVCLASS_ID 0x1134 +#define GNSS_SVCLASS_ID 0x1135 +#define GNSS_SERVER_SVCLASS_ID 0x1136 +#define MPS_SC_SVCLASS_ID 0x113A +#define MPS_SVCLASS_ID 0x113B +#define PNP_INFO_SVCLASS_ID 0x1200 +#define GENERIC_NETWORKING_SVCLASS_ID 0x1201 +#define GENERIC_FILETRANS_SVCLASS_ID 0x1202 +#define GENERIC_AUDIO_SVCLASS_ID 0x1203 +#define GENERIC_TELEPHONY_SVCLASS_ID 0x1204 +#define UPNP_SVCLASS_ID 0x1205 +#define UPNP_IP_SVCLASS_ID 0x1206 +#define UPNP_PAN_SVCLASS_ID 0x1300 +#define UPNP_LAP_SVCLASS_ID 0x1301 +#define UPNP_L2CAP_SVCLASS_ID 0x1302 +#define VIDEO_SOURCE_SVCLASS_ID 0x1303 +#define VIDEO_SINK_SVCLASS_ID 0x1304 +#define VIDEO_DISTRIBUTION_SVCLASS_ID 0x1305 +#define HDP_SVCLASS_ID 0x1400 +#define HDP_SOURCE_SVCLASS_ID 0x1401 +#define HDP_SINK_SVCLASS_ID 0x1402 +#define GENERIC_ACCESS_SVCLASS_ID 0x1800 +#define GENERIC_ATTRIB_SVCLASS_ID 0x1801 +#define APPLE_AGENT_SVCLASS_ID 0x2112 + +/* + * Standard profile descriptor identifiers; note these + * may be identical to some of the service classes defined above + */ +#define SDP_SERVER_PROFILE_ID SDP_SERVER_SVCLASS_ID +#define BROWSE_GRP_DESC_PROFILE_ID BROWSE_GRP_DESC_SVCLASS_ID +#define SERIAL_PORT_PROFILE_ID SERIAL_PORT_SVCLASS_ID +#define LAN_ACCESS_PROFILE_ID LAN_ACCESS_SVCLASS_ID +#define DIALUP_NET_PROFILE_ID DIALUP_NET_SVCLASS_ID +#define IRMC_SYNC_PROFILE_ID IRMC_SYNC_SVCLASS_ID +#define OBEX_OBJPUSH_PROFILE_ID OBEX_OBJPUSH_SVCLASS_ID +#define OBEX_FILETRANS_PROFILE_ID OBEX_FILETRANS_SVCLASS_ID +#define IRMC_SYNC_CMD_PROFILE_ID IRMC_SYNC_CMD_SVCLASS_ID +#define HEADSET_PROFILE_ID HEADSET_SVCLASS_ID +#define CORDLESS_TELEPHONY_PROFILE_ID CORDLESS_TELEPHONY_SVCLASS_ID +#define AUDIO_SOURCE_PROFILE_ID AUDIO_SOURCE_SVCLASS_ID +#define AUDIO_SINK_PROFILE_ID AUDIO_SINK_SVCLASS_ID +#define AV_REMOTE_TARGET_PROFILE_ID AV_REMOTE_TARGET_SVCLASS_ID +#define ADVANCED_AUDIO_PROFILE_ID ADVANCED_AUDIO_SVCLASS_ID +#define AV_REMOTE_PROFILE_ID AV_REMOTE_SVCLASS_ID +#define INTERCOM_PROFILE_ID INTERCOM_SVCLASS_ID +#define FAX_PROFILE_ID FAX_SVCLASS_ID +#define HEADSET_AGW_PROFILE_ID HEADSET_AGW_SVCLASS_ID +#define WAP_PROFILE_ID WAP_SVCLASS_ID +#define WAP_CLIENT_PROFILE_ID WAP_CLIENT_SVCLASS_ID +#define PANU_PROFILE_ID PANU_SVCLASS_ID +#define NAP_PROFILE_ID NAP_SVCLASS_ID +#define GN_PROFILE_ID GN_SVCLASS_ID +#define DIRECT_PRINTING_PROFILE_ID DIRECT_PRINTING_SVCLASS_ID +#define REFERENCE_PRINTING_PROFILE_ID REFERENCE_PRINTING_SVCLASS_ID +#define IMAGING_PROFILE_ID IMAGING_SVCLASS_ID +#define IMAGING_RESPONDER_PROFILE_ID IMAGING_RESPONDER_SVCLASS_ID +#define IMAGING_ARCHIVE_PROFILE_ID IMAGING_ARCHIVE_SVCLASS_ID +#define IMAGING_REFOBJS_PROFILE_ID IMAGING_REFOBJS_SVCLASS_ID +#define HANDSFREE_PROFILE_ID HANDSFREE_SVCLASS_ID +#define HANDSFREE_AGW_PROFILE_ID HANDSFREE_AGW_SVCLASS_ID +#define DIRECT_PRT_REFOBJS_PROFILE_ID DIRECT_PRT_REFOBJS_SVCLASS_ID +#define REFLECTED_UI_PROFILE_ID REFLECTED_UI_SVCLASS_ID +#define BASIC_PRINTING_PROFILE_ID BASIC_PRINTING_SVCLASS_ID +#define PRINTING_STATUS_PROFILE_ID PRINTING_STATUS_SVCLASS_ID +#define HID_PROFILE_ID HID_SVCLASS_ID +#define HCR_PROFILE_ID HCR_SCAN_SVCLASS_ID +#define HCR_PRINT_PROFILE_ID HCR_PRINT_SVCLASS_ID +#define HCR_SCAN_PROFILE_ID HCR_SCAN_SVCLASS_ID +#define CIP_PROFILE_ID CIP_SVCLASS_ID +#define VIDEO_CONF_GW_PROFILE_ID VIDEO_CONF_GW_SVCLASS_ID +#define UDI_MT_PROFILE_ID UDI_MT_SVCLASS_ID +#define UDI_TA_PROFILE_ID UDI_TA_SVCLASS_ID +#define AV_PROFILE_ID AV_SVCLASS_ID +#define SAP_PROFILE_ID SAP_SVCLASS_ID +#define PBAP_PCE_PROFILE_ID PBAP_PCE_SVCLASS_ID +#define PBAP_PSE_PROFILE_ID PBAP_PSE_SVCLASS_ID +#define PBAP_PROFILE_ID PBAP_SVCLASS_ID +#define MAP_PROFILE_ID MAP_SVCLASS_ID +#define PNP_INFO_PROFILE_ID PNP_INFO_SVCLASS_ID +#define GENERIC_NETWORKING_PROFILE_ID GENERIC_NETWORKING_SVCLASS_ID +#define GENERIC_FILETRANS_PROFILE_ID GENERIC_FILETRANS_SVCLASS_ID +#define GENERIC_AUDIO_PROFILE_ID GENERIC_AUDIO_SVCLASS_ID +#define GENERIC_TELEPHONY_PROFILE_ID GENERIC_TELEPHONY_SVCLASS_ID +#define UPNP_PROFILE_ID UPNP_SVCLASS_ID +#define UPNP_IP_PROFILE_ID UPNP_IP_SVCLASS_ID +#define UPNP_PAN_PROFILE_ID UPNP_PAN_SVCLASS_ID +#define UPNP_LAP_PROFILE_ID UPNP_LAP_SVCLASS_ID +#define UPNP_L2CAP_PROFILE_ID UPNP_L2CAP_SVCLASS_ID +#define VIDEO_SOURCE_PROFILE_ID VIDEO_SOURCE_SVCLASS_ID +#define VIDEO_SINK_PROFILE_ID VIDEO_SINK_SVCLASS_ID +#define VIDEO_DISTRIBUTION_PROFILE_ID VIDEO_DISTRIBUTION_SVCLASS_ID +#define HDP_PROFILE_ID HDP_SVCLASS_ID +#define HDP_SOURCE_PROFILE_ID HDP_SOURCE_SVCLASS_ID +#define HDP_SINK_PROFILE_ID HDP_SINK_SVCLASS_ID +#define GENERIC_ACCESS_PROFILE_ID GENERIC_ACCESS_SVCLASS_ID +#define GENERIC_ATTRIB_PROFILE_ID GENERIC_ATTRIB_SVCLASS_ID +#define APPLE_AGENT_PROFILE_ID APPLE_AGENT_SVCLASS_ID +#define MPS_PROFILE_ID MPS_SC_SVCLASS_ID + +/* + * Compatibility macros for the old MDP acronym + */ +#define MDP_SVCLASS_ID HDP_SVCLASS_ID +#define MDP_SOURCE_SVCLASS_ID HDP_SOURCE_SVCLASS_ID +#define MDP_SINK_SVCLASS_ID HDP_SINK_SVCLASS_ID +#define MDP_PROFILE_ID HDP_PROFILE_ID +#define MDP_SOURCE_PROFILE_ID HDP_SOURCE_PROFILE_ID +#define MDP_SINK_PROFILE_ID HDP_SINK_PROFILE_ID + +/* + * Attribute identifier codes + */ +#define SDP_SERVER_RECORD_HANDLE 0x0000 + +/* + * Possible values for attribute-id are listed below. + * See SDP Spec, section "Service Attribute Definitions" for more details. + */ +#define SDP_ATTR_RECORD_HANDLE 0x0000 +#define SDP_ATTR_SVCLASS_ID_LIST 0x0001 +#define SDP_ATTR_RECORD_STATE 0x0002 +#define SDP_ATTR_SERVICE_ID 0x0003 +#define SDP_ATTR_PROTO_DESC_LIST 0x0004 +#define SDP_ATTR_BROWSE_GRP_LIST 0x0005 +#define SDP_ATTR_LANG_BASE_ATTR_ID_LIST 0x0006 +#define SDP_ATTR_SVCINFO_TTL 0x0007 +#define SDP_ATTR_SERVICE_AVAILABILITY 0x0008 +#define SDP_ATTR_PFILE_DESC_LIST 0x0009 +#define SDP_ATTR_DOC_URL 0x000a +#define SDP_ATTR_CLNT_EXEC_URL 0x000b +#define SDP_ATTR_ICON_URL 0x000c +#define SDP_ATTR_ADD_PROTO_DESC_LIST 0x000d + +#define SDP_ATTR_GROUP_ID 0x0200 +#define SDP_ATTR_IP_SUBNET 0x0200 +#define SDP_ATTR_VERSION_NUM_LIST 0x0200 +#define SDP_ATTR_SUPPORTED_FEATURES_LIST 0x0200 +#define SDP_ATTR_GOEP_L2CAP_PSM 0x0200 +#define SDP_ATTR_SVCDB_STATE 0x0201 + +#define SDP_ATTR_MPSD_SCENARIOS 0x0200 +#define SDP_ATTR_MPMD_SCENARIOS 0x0201 +#define SDP_ATTR_MPS_DEPENDENCIES 0x0202 + +#define SDP_ATTR_SERVICE_VERSION 0x0300 +#define SDP_ATTR_EXTERNAL_NETWORK 0x0301 +#define SDP_ATTR_SUPPORTED_DATA_STORES_LIST 0x0301 +#define SDP_ATTR_DATA_EXCHANGE_SPEC 0x0301 +#define SDP_ATTR_NETWORK 0x0301 +#define SDP_ATTR_FAX_CLASS1_SUPPORT 0x0302 +#define SDP_ATTR_REMOTE_AUDIO_VOLUME_CONTROL 0x0302 +#define SDP_ATTR_MCAP_SUPPORTED_PROCEDURES 0x0302 +#define SDP_ATTR_FAX_CLASS20_SUPPORT 0x0303 +#define SDP_ATTR_SUPPORTED_FORMATS_LIST 0x0303 +#define SDP_ATTR_FAX_CLASS2_SUPPORT 0x0304 +#define SDP_ATTR_AUDIO_FEEDBACK_SUPPORT 0x0305 +#define SDP_ATTR_NETWORK_ADDRESS 0x0306 +#define SDP_ATTR_WAP_GATEWAY 0x0307 +#define SDP_ATTR_HOMEPAGE_URL 0x0308 +#define SDP_ATTR_WAP_STACK_TYPE 0x0309 +#define SDP_ATTR_SECURITY_DESC 0x030a +#define SDP_ATTR_NET_ACCESS_TYPE 0x030b +#define SDP_ATTR_MAX_NET_ACCESSRATE 0x030c +#define SDP_ATTR_IP4_SUBNET 0x030d +#define SDP_ATTR_IP6_SUBNET 0x030e +#define SDP_ATTR_SUPPORTED_CAPABILITIES 0x0310 +#define SDP_ATTR_SUPPORTED_FEATURES 0x0311 +#define SDP_ATTR_SUPPORTED_FUNCTIONS 0x0312 +#define SDP_ATTR_TOTAL_IMAGING_DATA_CAPACITY 0x0313 +#define SDP_ATTR_SUPPORTED_REPOSITORIES 0x0314 +#define SDP_ATTR_MAS_INSTANCE_ID 0x0315 +#define SDP_ATTR_SUPPORTED_MESSAGE_TYPES 0x0316 +#define SDP_ATTR_PBAP_SUPPORTED_FEATURES 0x0317 +#define SDP_ATTR_MAP_SUPPORTED_FEATURES 0x0317 + +#define SDP_ATTR_SPECIFICATION_ID 0x0200 +#define SDP_ATTR_VENDOR_ID 0x0201 +#define SDP_ATTR_PRODUCT_ID 0x0202 +#define SDP_ATTR_VERSION 0x0203 +#define SDP_ATTR_PRIMARY_RECORD 0x0204 +#define SDP_ATTR_VENDOR_ID_SOURCE 0x0205 + +#define SDP_ATTR_HID_DEVICE_RELEASE_NUMBER 0x0200 +#define SDP_ATTR_HID_PARSER_VERSION 0x0201 +#define SDP_ATTR_HID_DEVICE_SUBCLASS 0x0202 +#define SDP_ATTR_HID_COUNTRY_CODE 0x0203 +#define SDP_ATTR_HID_VIRTUAL_CABLE 0x0204 +#define SDP_ATTR_HID_RECONNECT_INITIATE 0x0205 +#define SDP_ATTR_HID_DESCRIPTOR_LIST 0x0206 +#define SDP_ATTR_HID_LANG_ID_BASE_LIST 0x0207 +#define SDP_ATTR_HID_SDP_DISABLE 0x0208 +#define SDP_ATTR_HID_BATTERY_POWER 0x0209 +#define SDP_ATTR_HID_REMOTE_WAKEUP 0x020a +#define SDP_ATTR_HID_PROFILE_VERSION 0x020b +#define SDP_ATTR_HID_SUPERVISION_TIMEOUT 0x020c +#define SDP_ATTR_HID_NORMALLY_CONNECTABLE 0x020d +#define SDP_ATTR_HID_BOOT_DEVICE 0x020e + +/* + * These identifiers are based on the SDP spec stating that + * "base attribute id of the primary (universal) language must be 0x0100" + * + * Other languages should have their own offset; e.g.: + * #define XXXLangBase yyyy + * #define AttrServiceName_XXX 0x0000+XXXLangBase + */ +#define SDP_PRIMARY_LANG_BASE 0x0100 + +#define SDP_ATTR_SVCNAME_PRIMARY 0x0000 + SDP_PRIMARY_LANG_BASE +#define SDP_ATTR_SVCDESC_PRIMARY 0x0001 + SDP_PRIMARY_LANG_BASE +#define SDP_ATTR_PROVNAME_PRIMARY 0x0002 + SDP_PRIMARY_LANG_BASE + +/* + * The Data representation in SDP PDUs (pps 339, 340 of BT SDP Spec) + * These are the exact data type+size descriptor values + * that go into the PDU buffer. + * + * The datatype (leading 5bits) + size descriptor (last 3 bits) + * is 8 bits. The size descriptor is critical to extract the + * right number of bytes for the data value from the PDU. + * + * For most basic types, the datatype+size descriptor is + * straightforward. However for constructed types and strings, + * the size of the data is in the next "n" bytes following the + * 8 bits (datatype+size) descriptor. Exactly what the "n" is + * specified in the 3 bits of the data size descriptor. + * + * TextString and URLString can be of size 2^{8, 16, 32} bytes + * DataSequence and DataSequenceAlternates can be of size 2^{8, 16, 32} + * The size are computed post-facto in the API and are not known apriori + */ +#define SDP_DATA_NIL 0x00 +#define SDP_UINT8 0x08 +#define SDP_UINT16 0x09 +#define SDP_UINT32 0x0A +#define SDP_UINT64 0x0B +#define SDP_UINT128 0x0C +#define SDP_INT8 0x10 +#define SDP_INT16 0x11 +#define SDP_INT32 0x12 +#define SDP_INT64 0x13 +#define SDP_INT128 0x14 +#define SDP_UUID_UNSPEC 0x18 +#define SDP_UUID16 0x19 +#define SDP_UUID32 0x1A +#define SDP_UUID128 0x1C +#define SDP_TEXT_STR_UNSPEC 0x20 +#define SDP_TEXT_STR8 0x25 +#define SDP_TEXT_STR16 0x26 +#define SDP_TEXT_STR32 0x27 +#define SDP_BOOL 0x28 +#define SDP_SEQ_UNSPEC 0x30 +#define SDP_SEQ8 0x35 +#define SDP_SEQ16 0x36 +#define SDP_SEQ32 0x37 +#define SDP_ALT_UNSPEC 0x38 +#define SDP_ALT8 0x3D +#define SDP_ALT16 0x3E +#define SDP_ALT32 0x3F +#define SDP_URL_STR_UNSPEC 0x40 +#define SDP_URL_STR8 0x45 +#define SDP_URL_STR16 0x46 +#define SDP_URL_STR32 0x47 + +/* + * The PDU identifiers of SDP packets between client and server + */ +#define SDP_ERROR_RSP 0x01 +#define SDP_SVC_SEARCH_REQ 0x02 +#define SDP_SVC_SEARCH_RSP 0x03 +#define SDP_SVC_ATTR_REQ 0x04 +#define SDP_SVC_ATTR_RSP 0x05 +#define SDP_SVC_SEARCH_ATTR_REQ 0x06 +#define SDP_SVC_SEARCH_ATTR_RSP 0x07 + +/* + * Some additions to support service registration. + * These are outside the scope of the Bluetooth specification + */ +#define SDP_SVC_REGISTER_REQ 0x75 +#define SDP_SVC_REGISTER_RSP 0x76 +#define SDP_SVC_UPDATE_REQ 0x77 +#define SDP_SVC_UPDATE_RSP 0x78 +#define SDP_SVC_REMOVE_REQ 0x79 +#define SDP_SVC_REMOVE_RSP 0x80 + +/* + * SDP Error codes + */ +#define SDP_INVALID_VERSION 0x0001 +#define SDP_INVALID_RECORD_HANDLE 0x0002 +#define SDP_INVALID_SYNTAX 0x0003 +#define SDP_INVALID_PDU_SIZE 0x0004 +#define SDP_INVALID_CSTATE 0x0005 + +/* + * SDP PDU + */ +typedef struct { + uint8_t pdu_id; + uint16_t tid; + uint16_t plen; +} __attribute__ ((packed)) sdp_pdu_hdr_t; + +/* + * Common definitions for attributes in the SDP. + * Should the type of any of these change, you need only make a change here. + */ + +typedef struct { + uint8_t type; + union { + uint16_t uuid16; + uint32_t uuid32; + uint128_t uuid128; + } value; +} uuid_t; + +#define SDP_IS_UUID(x) ((x) == SDP_UUID16 || (x) == SDP_UUID32 || \ + (x) == SDP_UUID128) +#define SDP_IS_ALT(x) ((x) == SDP_ALT8 || (x) == SDP_ALT16 || (x) == SDP_ALT32) +#define SDP_IS_SEQ(x) ((x) == SDP_SEQ8 || (x) == SDP_SEQ16 || (x) == SDP_SEQ32) +#define SDP_IS_TEXT_STR(x) ((x) == SDP_TEXT_STR8 || (x) == SDP_TEXT_STR16 || \ + (x) == SDP_TEXT_STR32) + +typedef struct _sdp_list sdp_list_t; +struct _sdp_list { + sdp_list_t *next; + void *data; +}; + +/* + * User-visible strings can be in many languages + * in addition to the universal language. + * + * Language meta-data includes language code in ISO639 + * followed by the encoding format. The third field in this + * structure is the attribute offset for the language. + * User-visible strings in the specified language can be + * obtained at this offset. + */ +typedef struct { + uint16_t code_ISO639; + uint16_t encoding; + uint16_t base_offset; +} sdp_lang_attr_t; + +/* + * Profile descriptor is the Bluetooth profile metadata. If a + * service conforms to a well-known profile, then its profile + * identifier (UUID) is an attribute of the service. In addition, + * if the profile has a version number it is specified here. + */ +typedef struct { + uuid_t uuid; + uint16_t version; +} sdp_profile_desc_t; + +typedef struct { + uint8_t major; + uint8_t minor; +} sdp_version_t; + +typedef struct { + uint8_t *data; + uint32_t data_size; + uint32_t buf_size; +} sdp_buf_t; + +typedef struct { + uint32_t handle; + + /* Search pattern: a sequence of all UUIDs seen in this record */ + sdp_list_t *pattern; + sdp_list_t *attrlist; + + /* Main service class for Extended Inquiry Response */ + uuid_t svclass; +} sdp_record_t; + +typedef struct sdp_data_struct sdp_data_t; +struct sdp_data_struct { + uint8_t dtd; + uint16_t attrId; + union { + int8_t int8; + int16_t int16; + int32_t int32; + int64_t int64; + uint128_t int128; + uint8_t uint8; + uint16_t uint16; + uint32_t uint32; + uint64_t uint64; + uint128_t uint128; + uuid_t uuid; + char *str; + sdp_data_t *dataseq; + } val; + sdp_data_t *next; + int unitSize; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __SDP_H */ From 249d1a4d203d95852443a4ce395d516fceec4b0d Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 05/73] Vendor BlueZ's sdp_lib.h verbatim --- .../include/bluetooth/sdp_lib.h | 628 ++++++++++++++++++ 1 file changed, 628 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/sdp_lib.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/sdp_lib.h b/Sources/CBluetoothLinuxABI/include/bluetooth/sdp_lib.h new file mode 100644 index 0000000..7a48ad5 --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/sdp_lib.h @@ -0,0 +1,628 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + */ + +#ifndef __SDP_LIB_H +#define __SDP_LIB_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * SDP lists + */ +typedef void(*sdp_list_func_t)(void *, void *); +typedef void(*sdp_free_func_t)(void *); +typedef int (*sdp_comp_func_t)(const void *, const void *); + +sdp_list_t *sdp_list_append(sdp_list_t *list, void *d); +sdp_list_t *sdp_list_remove(sdp_list_t *list, void *d); +sdp_list_t *sdp_list_insert_sorted(sdp_list_t *list, void *data, sdp_comp_func_t f); +void sdp_list_free(sdp_list_t *list, sdp_free_func_t f); + +static inline int sdp_list_len(const sdp_list_t *list) +{ + int n = 0; + for (; list; list = list->next) + n++; + return n; +} + +static inline sdp_list_t *sdp_list_find(sdp_list_t *list, void *u, sdp_comp_func_t f) +{ + for (; list; list = list->next) + if (f(list->data, u) == 0) + return list; + return NULL; +} + +static inline void sdp_list_foreach(sdp_list_t *list, sdp_list_func_t f, void *u) +{ + for (; list; list = list->next) + f(list->data, u); +} + +/* + * Values of the flags parameter to sdp_record_register + */ +#define SDP_RECORD_PERSIST 0x01 +#define SDP_DEVICE_RECORD 0x02 + +/* + * Values of the flags parameter to sdp_connect + */ +#define SDP_RETRY_IF_BUSY 0x01 +#define SDP_WAIT_ON_CLOSE 0x02 +#define SDP_NON_BLOCKING 0x04 +#define SDP_LARGE_MTU 0x08 + +/* + * a session with an SDP server + */ +typedef struct { + int sock; + int state; + int local; + int flags; + uint16_t tid; /* Current transaction ID */ + void *priv; +} sdp_session_t; + +typedef enum { + /* + * Attributes are specified as individual elements + */ + SDP_ATTR_REQ_INDIVIDUAL = 1, + /* + * Attributes are specified as a range + */ + SDP_ATTR_REQ_RANGE +} sdp_attrreq_type_t; + +/* + * When the pdu_id(type) is a sdp error response, check the status value + * to figure out the error reason. For status values 0x0001-0x0006 check + * Bluetooth SPEC. If the status is 0xffff, call sdp_get_error function + * to get the real reason: + * - wrong transaction ID(EPROTO) + * - wrong PDU id or(EPROTO) + * - I/O error + */ +typedef void sdp_callback_t(uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *udata); + +/* + * create an L2CAP connection to a Bluetooth device + * + * INPUT: + * + * bdaddr_t *src: + * Address of the local device to use to make the connection + * (or BDADDR_ANY) + * + * bdaddr_t *dst: + * Address of the SDP server device + */ +sdp_session_t *sdp_connect(const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags); +int sdp_close(sdp_session_t *session); +int sdp_get_socket(const sdp_session_t *session); + +/* + * SDP transaction: functions for asynchronous search. + */ +sdp_session_t *sdp_create(int sk, uint32_t flags); +int sdp_get_error(sdp_session_t *session); +int sdp_process(sdp_session_t *session); +int sdp_set_notify(sdp_session_t *session, sdp_callback_t *func, void *udata); + +int sdp_service_search_async(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num); +int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list); +int sdp_service_search_attr_async(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list); + +uint16_t sdp_gen_tid(sdp_session_t *session); + +/* + * find all devices in the piconet + */ +int sdp_general_inquiry(inquiry_info *ii, int dev_num, int duration, uint8_t *found); + +/* flexible extraction of basic attributes - Jean II */ +int sdp_get_int_attr(const sdp_record_t *rec, uint16_t attr, int *value); +int sdp_get_string_attr(const sdp_record_t *rec, uint16_t attr, char *value, + size_t valuelen); + +/* + * Basic sdp data functions + */ +sdp_data_t *sdp_data_alloc(uint8_t dtd, const void *value); +sdp_data_t *sdp_data_alloc_with_length(uint8_t dtd, const void *value, uint32_t length); +void sdp_data_free(sdp_data_t *data); +sdp_data_t *sdp_data_get(const sdp_record_t *rec, uint16_t attr_id); + +sdp_data_t *sdp_seq_alloc(void **dtds, void **values, int len); +sdp_data_t *sdp_seq_alloc_with_length(void **dtds, void **values, int *length, int len); +sdp_data_t *sdp_seq_append(sdp_data_t *seq, sdp_data_t *data); + +int sdp_attr_add(sdp_record_t *rec, uint16_t attr, sdp_data_t *data); +void sdp_attr_remove(sdp_record_t *rec, uint16_t attr); +void sdp_attr_replace(sdp_record_t *rec, uint16_t attr, sdp_data_t *data); +int sdp_set_uuidseq_attr(sdp_record_t *rec, uint16_t attr, sdp_list_t *seq); +int sdp_get_uuidseq_attr(const sdp_record_t *rec, uint16_t attr, sdp_list_t **seqp); + +/* + * NOTE that none of the functions below will update the SDP server, + * unless the {register, update}sdp_record_t() function is invoked. + * All functions which return an integer value, return 0 on success + * or -1 on failure. + */ + +/* + * Create an attribute and add it to the service record's attribute list. + * This consists of the data type descriptor of the attribute, + * the value of the attribute and the attribute identifier. + */ +int sdp_attr_add_new(sdp_record_t *rec, uint16_t attr, uint8_t dtd, const void *p); + +/* + * Set the information attributes of the service record. + * The set of attributes comprises service name, description + * and provider name + */ +void sdp_set_info_attr(sdp_record_t *rec, const char *name, const char *prov, const char *desc); + +/* + * Set the ServiceClassID attribute to the sequence specified by seq. + * Note that the identifiers need to be in sorted order from the most + * specific to the most generic service class that this service + * conforms to. + */ +static inline int sdp_set_service_classes(sdp_record_t *rec, sdp_list_t *seq) +{ + return sdp_set_uuidseq_attr(rec, SDP_ATTR_SVCLASS_ID_LIST, seq); +} + +/* + * Get the service classes to which the service conforms. + * + * When set, the list contains elements of ServiceClassIdentifer(uint16_t) + * ordered from most specific to most generic + */ +static inline int sdp_get_service_classes(const sdp_record_t *rec, sdp_list_t **seqp) +{ + return sdp_get_uuidseq_attr(rec, SDP_ATTR_SVCLASS_ID_LIST, seqp); +} + +/* + * Set the BrowseGroupList attribute to the list specified by seq. + * + * A service can belong to one or more service groups + * and the list comprises such group identifiers (UUIDs) + */ +static inline int sdp_set_browse_groups(sdp_record_t *rec, sdp_list_t *seq) +{ + return sdp_set_uuidseq_attr(rec, SDP_ATTR_BROWSE_GRP_LIST, seq); +} + +/* + * Set the access protocols of the record to those specified in proto + */ +int sdp_set_access_protos(sdp_record_t *rec, const sdp_list_t *proto); + +/* + * Set the additional access protocols of the record to those specified in proto + */ +int sdp_set_add_access_protos(sdp_record_t *rec, const sdp_list_t *proto); + +/* + * Get protocol port (i.e. PSM for L2CAP, Channel for RFCOMM) + */ +int sdp_get_proto_port(const sdp_list_t *list, int proto); + +/* + * Get protocol descriptor. + */ +sdp_data_t *sdp_get_proto_desc(sdp_list_t *list, int proto); + +/* + * Set the LanguageBase attributes to the values specified in list + * (a linked list of sdp_lang_attr_t objects, one for each language in + * which user-visible attributes are present). + */ +int sdp_set_lang_attr(sdp_record_t *rec, const sdp_list_t *list); + +/* + * Set the ServiceInfoTimeToLive attribute of the service. + * This is the number of seconds that this record is guaranteed + * not to change after being obtained by a client. + */ +static inline int sdp_set_service_ttl(sdp_record_t *rec, uint32_t ttl) +{ + return sdp_attr_add_new(rec, SDP_ATTR_SVCINFO_TTL, SDP_UINT32, &ttl); +} + +/* + * Set the ServiceRecordState attribute of a service. This is + * guaranteed to change if there is any kind of modification to + * the record. + */ +static inline int sdp_set_record_state(sdp_record_t *rec, uint32_t state) +{ + return sdp_attr_add_new(rec, SDP_ATTR_RECORD_STATE, SDP_UINT32, &state); +} + +/* + * Set the ServiceID attribute of a service. + */ +void sdp_set_service_id(sdp_record_t *rec, uuid_t uuid); + +/* + * Set the GroupID attribute of a service + */ +void sdp_set_group_id(sdp_record_t *rec, uuid_t grouuuid); + +/* + * Set the ServiceAvailability attribute of a service. + * + * Note that this represents the relative availability + * of the service: 0x00 means completely unavailable; + * 0xFF means maximum availability. + */ +static inline int sdp_set_service_avail(sdp_record_t *rec, uint8_t avail) +{ + return sdp_attr_add_new(rec, SDP_ATTR_SERVICE_AVAILABILITY, SDP_UINT8, &avail); +} + +/* + * Set the profile descriptor list attribute of a record. + * + * Each element in the list is an object of type + * sdp_profile_desc_t which is a definition of the + * Bluetooth profile that this service conforms to. + */ +int sdp_set_profile_descs(sdp_record_t *rec, const sdp_list_t *desc); + +/* + * Set URL attributes of a record. + * + * ClientExecutableURL: a URL to a client's platform specific (WinCE, + * PalmOS) executable code that can be used to access this service. + * + * DocumentationURL: a URL pointing to service documentation + * + * IconURL: a URL to an icon that can be used to represent this service. + * + * Note: pass NULL for any URLs that you don't want to set or remove + */ +void sdp_set_url_attr(sdp_record_t *rec, const char *clientExecURL, const char *docURL, const char *iconURL); + +/* + * a service search request. + * + * INPUT : + * + * sdp_list_t *search + * list containing elements of the search + * pattern. Each entry in the list is a UUID + * of the service to be searched + * + * uint16_t max_rec_num + * An integer specifying the maximum number of + * entries that the client can handle in the response. + * + * OUTPUT : + * + * int return value + * 0 + * The request completed successfully. This does not + * mean the requested services were found + * -1 + * The request completed unsuccessfully + * + * sdp_list_t *rsp_list + * This variable is set on a successful return if there are + * non-zero service handles. It is a singly linked list of + * service record handles (uint16_t) + */ +int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num, sdp_list_t **rsp_list); + +/* + * a service attribute request. + * + * INPUT : + * + * uint32_t handle + * The handle of the service for which the attribute(s) are + * requested + * + * sdp_attrreq_type_t reqtype + * Attribute identifiers are 16 bit unsigned integers specified + * in one of 2 ways described below : + * SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers + * They are the actual attribute identifiers in ascending order + * + * SDP_ATTR_REQ_RANGE - 32bit identifier range + * The high-order 16bits is the start of range + * the low-order 16bits are the end of range + * 0x0000 to 0xFFFF gets all attributes + * + * sdp_list_t *attrid_list + * Singly linked list containing attribute identifiers desired. + * Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL) + * or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE) + * + * OUTPUT : + * int return value + * 0 + * The request completed successfully. This does not + * mean the requested services were found + * -1 + * The request completed unsuccessfully due to a timeout + */ +sdp_record_t *sdp_service_attr_req(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list); + +/* + * This is a service search request combined with the service + * attribute request. First a service class match is done and + * for matching service, requested attributes are extracted + * + * INPUT : + * + * sdp_list_t *search + * Singly linked list containing elements of the search + * pattern. Each entry in the list is a UUID(DataTypeSDP_UUID16) + * of the service to be searched + * + * AttributeSpecification attrSpec + * Attribute identifiers are 16 bit unsigned integers specified + * in one of 2 ways described below : + * SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers + * They are the actual attribute identifiers in ascending order + * + * SDP_ATTR_REQ_RANGE - 32bit identifier range + * The high-order 16bits is the start of range + * the low-order 16bits are the end of range + * 0x0000 to 0xFFFF gets all attributes + * + * sdp_list_t *attrid_list + * Singly linked list containing attribute identifiers desired. + * Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL) + * or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE) + * + * OUTPUT : + * int return value + * 0 + * The request completed successfully. This does not + * mean the requested services were found + * -1 + * The request completed unsuccessfully due to a timeout + * + * sdp_list_t *rsp_list + * This variable is set on a successful return to point to + * service(s) found. Each element of this list is of type + * sdp_record_t *. + */ +int sdp_service_search_attr_req(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list, sdp_list_t **rsp_list); + +/* + * Allocate/free a service record and its attributes + */ +sdp_record_t *sdp_record_alloc(void); +void sdp_record_free(sdp_record_t *rec); + +/* + * Register a service record. + * + * Note: It is the responsibility of the Service Provider to create the + * record first and set its attributes using setXXX() methods. + * + * The service provider must then call sdp_record_register() to make + * the service record visible to SDP clients. This function returns 0 + * on success or -1 on failure (and sets errno). + */ +int sdp_device_record_register_binary(sdp_session_t *session, bdaddr_t *device, uint8_t *data, uint32_t size, uint8_t flags, uint32_t *handle); +int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags); +int sdp_record_register(sdp_session_t *session, sdp_record_t *rec, uint8_t flags); + +/* + * Unregister a service record. + */ +int sdp_device_record_unregister_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle); +int sdp_device_record_unregister(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec); +int sdp_record_unregister(sdp_session_t *session, sdp_record_t *rec); + +/* + * Update an existing service record. (Calling this function + * before a previous call to sdp_record_register() will result + * in an error.) + */ +int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle, uint8_t *data, uint32_t size); +int sdp_device_record_update(sdp_session_t *session, bdaddr_t *device, const sdp_record_t *rec); +int sdp_record_update(sdp_session_t *sess, const sdp_record_t *rec); + +void sdp_record_print(const sdp_record_t *rec); + +/* + * UUID functions + */ +uuid_t *sdp_uuid16_create(uuid_t *uuid, uint16_t data); +uuid_t *sdp_uuid32_create(uuid_t *uuid, uint32_t data); +uuid_t *sdp_uuid128_create(uuid_t *uuid, const void *data); +int sdp_uuid16_cmp(const void *p1, const void *p2); +int sdp_uuid128_cmp(const void *p1, const void *p2); +int sdp_uuid_cmp(const void *p1, const void *p2); +uuid_t *sdp_uuid_to_uuid128(const uuid_t *uuid); +void sdp_uuid16_to_uuid128(uuid_t *uuid128, const uuid_t *uuid16); +void sdp_uuid32_to_uuid128(uuid_t *uuid128, const uuid_t *uuid32); +int sdp_uuid128_to_uuid(uuid_t *uuid); +int sdp_uuid_to_proto(uuid_t *uuid); +int sdp_uuid_extract(const uint8_t *buffer, int bufsize, uuid_t *uuid, int *scanned); +void sdp_uuid_print(const uuid_t *uuid); + +#define MAX_LEN_UUID_STR 37 +#define MAX_LEN_PROTOCOL_UUID_STR 8 +#define MAX_LEN_SERVICECLASS_UUID_STR 28 +#define MAX_LEN_PROFILEDESCRIPTOR_UUID_STR 28 + +int sdp_uuid2strn(const uuid_t *uuid, char *str, size_t n); +int sdp_proto_uuid2strn(const uuid_t *uuid, char *str, size_t n); +int sdp_svclass_uuid2strn(const uuid_t *uuid, char *str, size_t n); +int sdp_profile_uuid2strn(const uuid_t *uuid, char *str, size_t n); + +/* + * In all the sdp_get_XXX(handle, XXX *xxx) functions below, + * the XXX * is set to point to the value, should it exist + * and 0 is returned. If the value does not exist, -1 is + * returned and errno set to ENODATA. + * + * In all the methods below, the memory management rules are + * simple. Don't free anything! The pointer returned, in the + * case of constructed types, is a pointer to the contents + * of the sdp_record_t. + */ + +/* + * Get the access protocols from the service record + */ +int sdp_get_access_protos(const sdp_record_t *rec, sdp_list_t **protos); + +/* + * Get the additional access protocols from the service record + */ +int sdp_get_add_access_protos(const sdp_record_t *rec, sdp_list_t **protos); + +/* + * Extract the list of browse groups to which the service belongs. + * When set, seqp contains elements of GroupID (uint16_t) + */ +static inline int sdp_get_browse_groups(const sdp_record_t *rec, sdp_list_t **seqp) +{ + return sdp_get_uuidseq_attr(rec, SDP_ATTR_BROWSE_GRP_LIST, seqp); +} + +/* + * Extract language attribute meta-data of the service record. + * For each language in the service record, LangSeq has a struct of type + * sdp_lang_attr_t. + */ +int sdp_get_lang_attr(const sdp_record_t *rec, sdp_list_t **langSeq); + +/* + * Extract the Bluetooth profile descriptor sequence from a record. + * Each element in the list is of type sdp_profile_desc_t + * which contains the UUID of the profile and its version number + * (encoded as major and minor in the high-order 8bits + * and low-order 8bits respectively of the uint16_t) + */ +int sdp_get_profile_descs(const sdp_record_t *rec, sdp_list_t **profDesc); + +/* + * Extract SDP server version numbers + * + * Note: that this is an attribute of the SDP server only and + * contains a list of uint16_t each of which represent the + * major and minor SDP version numbers supported by this server + */ +int sdp_get_server_ver(const sdp_record_t *rec, sdp_list_t **pVnumList); + +int sdp_get_service_id(const sdp_record_t *rec, uuid_t *uuid); +int sdp_get_group_id(const sdp_record_t *rec, uuid_t *uuid); +int sdp_get_record_state(const sdp_record_t *rec, uint32_t *svcRecState); +int sdp_get_service_avail(const sdp_record_t *rec, uint8_t *svcAvail); +int sdp_get_service_ttl(const sdp_record_t *rec, uint32_t *svcTTLInfo); +int sdp_get_database_state(const sdp_record_t *rec, uint32_t *svcDBState); + +static inline int sdp_get_service_name(const sdp_record_t *rec, char *str, + size_t len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_SVCNAME_PRIMARY, str, len); +} + +static inline int sdp_get_service_desc(const sdp_record_t *rec, char *str, + size_t len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_SVCDESC_PRIMARY, str, len); +} + +static inline int sdp_get_provider_name(const sdp_record_t *rec, char *str, + size_t len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_PROVNAME_PRIMARY, str, len); +} + +static inline int sdp_get_doc_url(const sdp_record_t *rec, char *str, + size_t len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_DOC_URL, str, len); +} + +static inline int sdp_get_clnt_exec_url(const sdp_record_t *rec, char *str, + size_t len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_CLNT_EXEC_URL, str, len); +} + +static inline int sdp_get_icon_url(const sdp_record_t *rec, char *str, + size_t len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_ICON_URL, str, len); +} + +/* + * Set the supported features + * sf should be a list of list with each feature data + * Returns 0 on success -1 on fail + */ +int sdp_set_supp_feat(sdp_record_t *rec, const sdp_list_t *sf); + +/* + * Get the supported features + * seqp is set to a list of list with each feature data + * Returns 0 on success, if an error occurred -1 is returned and errno is set + */ +int sdp_get_supp_feat(const sdp_record_t *rec, sdp_list_t **seqp); + +sdp_record_t *sdp_extract_pdu(const uint8_t *pdata, int bufsize, int *scanned); +sdp_record_t *sdp_copy_record(sdp_record_t *rec); + +void sdp_data_print(sdp_data_t *data); +void sdp_print_service_attr(sdp_list_t *alist); + +int sdp_attrid_comp_func(const void *key1, const void *key2); + +void sdp_set_seq_len(uint8_t *ptr, uint32_t length); +void sdp_set_attrid(sdp_buf_t *pdu, uint16_t id); +void sdp_append_to_pdu(sdp_buf_t *dst, sdp_data_t *d); +void sdp_append_to_buf(sdp_buf_t *dst, uint8_t *data, uint32_t len); + +int sdp_gen_pdu(sdp_buf_t *pdu, sdp_data_t *data); +int sdp_gen_record_pdu(const sdp_record_t *rec, sdp_buf_t *pdu); + +int sdp_extract_seqtype(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size); + +sdp_data_t *sdp_extract_attr(const uint8_t *pdata, int bufsize, int *extractedLength, sdp_record_t *rec); + +void sdp_pattern_add_uuid(sdp_record_t *rec, uuid_t *uuid); +void sdp_pattern_add_uuidseq(sdp_record_t *rec, sdp_list_t *seq); + +int sdp_send_req_w4_rsp(sdp_session_t *session, uint8_t *req, uint8_t *rsp, uint32_t reqsize, uint32_t *rspsize); + +void sdp_add_lang_attr(sdp_record_t *rec); + +#ifdef __cplusplus +} +#endif + +#endif /* __SDP_LIB_H */ From 186c4e1a1092efba988aeadca68a165d293a3751 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 06/73] Vendor BlueZ's l2cap.h verbatim --- .../include/bluetooth/l2cap.h | 268 ++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/l2cap.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/l2cap.h b/Sources/CBluetoothLinuxABI/include/bluetooth/l2cap.h new file mode 100644 index 0000000..62cc04b --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/l2cap.h @@ -0,0 +1,268 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * + */ + +#ifndef __L2CAP_H +#define __L2CAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* L2CAP defaults */ +#define L2CAP_DEFAULT_MTU 672 +#define L2CAP_DEFAULT_FLUSH_TO 0xFFFF + +/* L2CAP socket address */ +struct sockaddr_l2 { + sa_family_t l2_family; + unsigned short l2_psm; + bdaddr_t l2_bdaddr; + unsigned short l2_cid; + uint8_t l2_bdaddr_type; +}; + +/* L2CAP socket options */ +#define L2CAP_OPTIONS 0x01 +struct l2cap_options { + uint16_t omtu; + uint16_t imtu; + uint16_t flush_to; + uint8_t mode; + uint8_t fcs; + uint8_t max_tx; + uint16_t txwin_size; +}; + +#define L2CAP_CONNINFO 0x02 +struct l2cap_conninfo { + uint16_t hci_handle; + uint8_t dev_class[3]; +}; + +#define L2CAP_LM 0x03 +#define L2CAP_LM_MASTER 0x0001 +#define L2CAP_LM_AUTH 0x0002 +#define L2CAP_LM_ENCRYPT 0x0004 +#define L2CAP_LM_TRUSTED 0x0008 +#define L2CAP_LM_RELIABLE 0x0010 +#define L2CAP_LM_SECURE 0x0020 + +/* L2CAP command codes */ +#define L2CAP_COMMAND_REJ 0x01 +#define L2CAP_CONN_REQ 0x02 +#define L2CAP_CONN_RSP 0x03 +#define L2CAP_CONF_REQ 0x04 +#define L2CAP_CONF_RSP 0x05 +#define L2CAP_DISCONN_REQ 0x06 +#define L2CAP_DISCONN_RSP 0x07 +#define L2CAP_ECHO_REQ 0x08 +#define L2CAP_ECHO_RSP 0x09 +#define L2CAP_INFO_REQ 0x0a +#define L2CAP_INFO_RSP 0x0b +#define L2CAP_CREATE_REQ 0x0c +#define L2CAP_CREATE_RSP 0x0d +#define L2CAP_MOVE_REQ 0x0e +#define L2CAP_MOVE_RSP 0x0f +#define L2CAP_MOVE_CFM 0x10 +#define L2CAP_MOVE_CFM_RSP 0x11 + +/* L2CAP extended feature mask */ +#define L2CAP_FEAT_FLOWCTL 0x00000001 +#define L2CAP_FEAT_RETRANS 0x00000002 +#define L2CAP_FEAT_BIDIR_QOS 0x00000004 +#define L2CAP_FEAT_ERTM 0x00000008 +#define L2CAP_FEAT_STREAMING 0x00000010 +#define L2CAP_FEAT_FCS 0x00000020 +#define L2CAP_FEAT_EXT_FLOW 0x00000040 +#define L2CAP_FEAT_FIXED_CHAN 0x00000080 +#define L2CAP_FEAT_EXT_WINDOW 0x00000100 +#define L2CAP_FEAT_UCD 0x00000200 + +/* L2CAP fixed channels */ +#define L2CAP_FC_L2CAP 0x02 +#define L2CAP_FC_CONNLESS 0x04 +#define L2CAP_FC_A2MP 0x08 + +/* L2CAP structures */ +typedef struct { + uint16_t len; + uint16_t cid; +} __attribute__ ((packed)) l2cap_hdr; +#define L2CAP_HDR_SIZE 4 + +typedef struct { + uint8_t code; + uint8_t ident; + uint16_t len; +} __attribute__ ((packed)) l2cap_cmd_hdr; +#define L2CAP_CMD_HDR_SIZE 4 + +typedef struct { + uint16_t reason; +} __attribute__ ((packed)) l2cap_cmd_rej; +#define L2CAP_CMD_REJ_SIZE 2 + +typedef struct { + uint16_t psm; + uint16_t scid; +} __attribute__ ((packed)) l2cap_conn_req; +#define L2CAP_CONN_REQ_SIZE 4 + +typedef struct { + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; +} __attribute__ ((packed)) l2cap_conn_rsp; +#define L2CAP_CONN_RSP_SIZE 8 + +/* connect result */ +#define L2CAP_CR_SUCCESS 0x0000 +#define L2CAP_CR_PEND 0x0001 +#define L2CAP_CR_BAD_PSM 0x0002 +#define L2CAP_CR_SEC_BLOCK 0x0003 +#define L2CAP_CR_NO_MEM 0x0004 + +/* connect status */ +#define L2CAP_CS_NO_INFO 0x0000 +#define L2CAP_CS_AUTHEN_PEND 0x0001 +#define L2CAP_CS_AUTHOR_PEND 0x0002 + +typedef struct { + uint16_t dcid; + uint16_t flags; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_conf_req; +#define L2CAP_CONF_REQ_SIZE 4 + +typedef struct { + uint16_t scid; + uint16_t flags; + uint16_t result; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_conf_rsp; +#define L2CAP_CONF_RSP_SIZE 6 + +#define L2CAP_CONF_SUCCESS 0x0000 +#define L2CAP_CONF_UNACCEPT 0x0001 +#define L2CAP_CONF_REJECT 0x0002 +#define L2CAP_CONF_UNKNOWN 0x0003 +#define L2CAP_CONF_PENDING 0x0004 +#define L2CAP_CONF_EFS_REJECT 0x0005 + +typedef struct { + uint8_t type; + uint8_t len; + uint8_t val[0]; +} __attribute__ ((packed)) l2cap_conf_opt; +#define L2CAP_CONF_OPT_SIZE 2 + +#define L2CAP_CONF_MTU 0x01 +#define L2CAP_CONF_FLUSH_TO 0x02 +#define L2CAP_CONF_QOS 0x03 +#define L2CAP_CONF_RFC 0x04 +#define L2CAP_CONF_FCS 0x05 +#define L2CAP_CONF_EFS 0x06 +#define L2CAP_CONF_EWS 0x07 + +#define L2CAP_CONF_MAX_SIZE 22 + +#define L2CAP_MODE_BASIC 0x00 +#define L2CAP_MODE_RETRANS 0x01 +#define L2CAP_MODE_FLOWCTL 0x02 +#define L2CAP_MODE_ERTM 0x03 +#define L2CAP_MODE_STREAMING 0x04 +#define L2CAP_MODE_LE_FLOWCTL 0x80 +#define L2CAP_MODE_ECRED 0x81 + +#define L2CAP_SERVTYPE_NOTRAFFIC 0x00 +#define L2CAP_SERVTYPE_BESTEFFORT 0x01 +#define L2CAP_SERVTYPE_GUARANTEED 0x02 + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_disconn_req; +#define L2CAP_DISCONN_REQ_SIZE 4 + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_disconn_rsp; +#define L2CAP_DISCONN_RSP_SIZE 4 + +typedef struct { + uint16_t type; +} __attribute__ ((packed)) l2cap_info_req; +#define L2CAP_INFO_REQ_SIZE 2 + +typedef struct { + uint16_t type; + uint16_t result; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_info_rsp; +#define L2CAP_INFO_RSP_SIZE 4 + +/* info type */ +#define L2CAP_IT_CL_MTU 0x0001 +#define L2CAP_IT_FEAT_MASK 0x0002 + +/* info result */ +#define L2CAP_IR_SUCCESS 0x0000 +#define L2CAP_IR_NOTSUPP 0x0001 + +typedef struct { + uint16_t psm; + uint16_t scid; + uint8_t id; +} __attribute__ ((packed)) l2cap_create_req; +#define L2CAP_CREATE_REQ_SIZE 5 + +typedef struct { + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; +} __attribute__ ((packed)) l2cap_create_rsp; +#define L2CAP_CREATE_RSP_SIZE 8 + +typedef struct { + uint16_t icid; + uint8_t id; +} __attribute__ ((packed)) l2cap_move_req; +#define L2CAP_MOVE_REQ_SIZE 3 + +typedef struct { + uint16_t icid; + uint16_t result; +} __attribute__ ((packed)) l2cap_move_rsp; +#define L2CAP_MOVE_RSP_SIZE 4 + +typedef struct { + uint16_t icid; + uint16_t result; +} __attribute__ ((packed)) l2cap_move_cfm; +#define L2CAP_MOVE_CFM_SIZE 4 + +typedef struct { + uint16_t icid; +} __attribute__ ((packed)) l2cap_move_cfm_rsp; +#define L2CAP_MOVE_CFM_RSP_SIZE 2 + +#ifdef __cplusplus +} +#endif + +#endif /* __L2CAP_H */ From 3ba48c5839305d815faaa870f99483d96d733bf7 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 07/73] Vendor BlueZ's rfcomm.h verbatim --- .../include/bluetooth/rfcomm.h | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/rfcomm.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/rfcomm.h b/Sources/CBluetoothLinuxABI/include/bluetooth/rfcomm.h new file mode 100644 index 0000000..0347ddc --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/rfcomm.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + */ + +#ifndef __RFCOMM_H +#define __RFCOMM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* RFCOMM defaults */ +#define RFCOMM_DEFAULT_MTU 127 + +#define RFCOMM_PSM 3 + +/* RFCOMM socket address */ +struct sockaddr_rc { + sa_family_t rc_family; + bdaddr_t rc_bdaddr; + uint8_t rc_channel; +}; + +/* RFCOMM socket options */ +#define RFCOMM_CONNINFO 0x02 +struct rfcomm_conninfo { + uint16_t hci_handle; + uint8_t dev_class[3]; +}; + +#define RFCOMM_LM 0x03 +#define RFCOMM_LM_MASTER 0x0001 +#define RFCOMM_LM_AUTH 0x0002 +#define RFCOMM_LM_ENCRYPT 0x0004 +#define RFCOMM_LM_TRUSTED 0x0008 +#define RFCOMM_LM_RELIABLE 0x0010 +#define RFCOMM_LM_SECURE 0x0020 + +/* RFCOMM TTY support */ +#define RFCOMM_MAX_DEV 256 + +#define RFCOMMCREATEDEV _IOW('R', 200, int) +#define RFCOMMRELEASEDEV _IOW('R', 201, int) +#define RFCOMMGETDEVLIST _IOR('R', 210, int) +#define RFCOMMGETDEVINFO _IOR('R', 211, int) + +struct rfcomm_dev_req { + int16_t dev_id; + uint32_t flags; + bdaddr_t src; + bdaddr_t dst; + uint8_t channel; +}; +#define RFCOMM_REUSE_DLC 0 +#define RFCOMM_RELEASE_ONHUP 1 +#define RFCOMM_HANGUP_NOW 2 +#define RFCOMM_TTY_ATTACHED 3 + +struct rfcomm_dev_info { + int16_t id; + uint32_t flags; + uint16_t state; + bdaddr_t src; + bdaddr_t dst; + uint8_t channel; +}; + +struct rfcomm_dev_list_req { + uint16_t dev_num; + struct rfcomm_dev_info dev_info[0]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __RFCOMM_H */ From 23f2a9097f56d9424958ea98591e8b19678963ee Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 08/73] Vendor BlueZ's sco.h verbatim --- .../include/bluetooth/sco.h | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/sco.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/sco.h b/Sources/CBluetoothLinuxABI/include/bluetooth/sco.h new file mode 100644 index 0000000..307d81f --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/sco.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + */ + +#ifndef __SCO_H +#define __SCO_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* SCO defaults */ +#define SCO_DEFAULT_MTU 500 +#define SCO_DEFAULT_FLUSH_TO 0xFFFF + +#define SCO_CONN_TIMEOUT (HZ * 40) +#define SCO_DISCONN_TIMEOUT (HZ * 2) +#define SCO_CONN_IDLE_TIMEOUT (HZ * 60) + +/* SCO socket address */ +struct sockaddr_sco { + sa_family_t sco_family; + bdaddr_t sco_bdaddr; +}; + +/* set/get sockopt defines */ +#define SCO_OPTIONS 0x01 +struct sco_options { + uint16_t mtu; +}; + +#define SCO_CONNINFO 0x02 +struct sco_conninfo { + uint16_t hci_handle; + uint8_t dev_class[3]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __SCO_H */ From be748905debc02e80ca33f3fa6438e7035f4d0a7 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 09/73] Vendor BlueZ's bnep.h verbatim --- .../include/bluetooth/bnep.h | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/bnep.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/bnep.h b/Sources/CBluetoothLinuxABI/include/bluetooth/bnep.h new file mode 100644 index 0000000..a0d3905 --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/bnep.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + */ + +#ifndef __BNEP_H +#define __BNEP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifndef ETH_ALEN +#define ETH_ALEN 6 /* from */ +#endif + +/* BNEP UUIDs */ +#define BNEP_BASE_UUID 0x0000000000001000800000805F9B34FB +#define BNEP_UUID16 0x02 +#define BNEP_UUID32 0x04 +#define BNEP_UUID128 0x16 + +#define BNEP_SVC_PANU 0x1115 +#define BNEP_SVC_NAP 0x1116 +#define BNEP_SVC_GN 0x1117 + +/* BNEP packet types */ +#define BNEP_GENERAL 0x00 +#define BNEP_CONTROL 0x01 +#define BNEP_COMPRESSED 0x02 +#define BNEP_COMPRESSED_SRC_ONLY 0x03 +#define BNEP_COMPRESSED_DST_ONLY 0x04 + +/* BNEP control types */ +#define BNEP_CMD_NOT_UNDERSTOOD 0x00 +#define BNEP_SETUP_CONN_REQ 0x01 +#define BNEP_SETUP_CONN_RSP 0x02 +#define BNEP_FILTER_NET_TYPE_SET 0x03 +#define BNEP_FILTER_NET_TYPE_RSP 0x04 +#define BNEP_FILTER_MULT_ADDR_SET 0x05 +#define BNEP_FILTER_MULT_ADDR_RSP 0x06 + +/* BNEP response messages */ +#define BNEP_SUCCESS 0x00 + +#define BNEP_CONN_INVALID_DST 0x01 +#define BNEP_CONN_INVALID_SRC 0x02 +#define BNEP_CONN_INVALID_SVC 0x03 +#define BNEP_CONN_NOT_ALLOWED 0x04 + +#define BNEP_FILTER_UNSUPPORTED_REQ 0x01 +#define BNEP_FILTER_INVALID_RANGE 0x02 +#define BNEP_FILTER_INVALID_MCADDR 0x02 +#define BNEP_FILTER_LIMIT_REACHED 0x03 +#define BNEP_FILTER_DENIED_SECURITY 0x04 + +/* L2CAP settings */ +#define BNEP_MTU 1691 +#define BNEP_FLUSH_TO 0xffff +#define BNEP_CONNECT_TO 15 +#define BNEP_FILTER_TO 15 + +#ifndef BNEP_PSM +#define BNEP_PSM 0x0f +#endif + +/* BNEP headers */ +#define BNEP_TYPE_MASK 0x7f +#define BNEP_EXT_HEADER 0x80 + +struct bnep_setup_conn_req { + uint8_t type; + uint8_t ctrl; + uint8_t uuid_size; + uint8_t service[0]; +} __attribute__((packed)); + +struct bnep_set_filter_req { + uint8_t type; + uint8_t ctrl; + uint16_t len; + uint8_t list[0]; +} __attribute__((packed)); + +struct bnep_ctrl_cmd_not_understood_cmd { + uint8_t type; + uint8_t ctrl; + uint8_t unkn_ctrl; +} __attribute__((packed)); + +struct bnep_control_rsp { + uint8_t type; + uint8_t ctrl; + uint16_t resp; +} __attribute__((packed)); + +struct bnep_ext_hdr { + uint8_t type; + uint8_t len; + uint8_t data[0]; +} __attribute__((packed)); + +/* BNEP ioctl defines */ +#define BNEPCONNADD _IOW('B', 200, int) +#define BNEPCONNDEL _IOW('B', 201, int) +#define BNEPGETCONNLIST _IOR('B', 210, int) +#define BNEPGETCONNINFO _IOR('B', 211, int) +#define BNEPGETSUPPFEAT _IOR('B', 212, int) + +#define BNEP_SETUP_RESPONSE 0 + +struct bnep_connadd_req { + int sock; /* Connected socket */ + uint32_t flags; + uint16_t role; + char device[16]; /* Name of the Ethernet device */ +}; + +struct bnep_conndel_req { + uint32_t flags; + uint8_t dst[ETH_ALEN]; +}; + +struct bnep_conninfo { + uint32_t flags; + uint16_t role; + uint16_t state; + uint8_t dst[ETH_ALEN]; + char device[16]; +}; + +struct bnep_connlist_req { + uint32_t cnum; + struct bnep_conninfo *ci; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __BNEP_H */ From e2dab8c18b6f872eec14119a3c3fdd3149534a89 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 10/73] Vendor BlueZ's cmtp.h verbatim --- .../include/bluetooth/cmtp.h | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/cmtp.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/cmtp.h b/Sources/CBluetoothLinuxABI/include/bluetooth/cmtp.h new file mode 100644 index 0000000..7ba8bfc --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/cmtp.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + */ + +#ifndef __CMTP_H +#define __CMTP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* CMTP defaults */ +#define CMTP_MINIMUM_MTU 152 +#define CMTP_DEFAULT_MTU 672 + +/* CMTP ioctl defines */ +#define CMTPCONNADD _IOW('C', 200, int) +#define CMTPCONNDEL _IOW('C', 201, int) +#define CMTPGETCONNLIST _IOR('C', 210, int) +#define CMTPGETCONNINFO _IOR('C', 211, int) + +#define CMTP_LOOPBACK 0 + +struct cmtp_connadd_req { + int sock; /* Connected socket */ + uint32_t flags; +}; + +struct cmtp_conndel_req { + bdaddr_t bdaddr; + uint32_t flags; +}; + +struct cmtp_conninfo { + bdaddr_t bdaddr; + uint32_t flags; + uint16_t state; + int num; +}; + +struct cmtp_connlist_req { + uint32_t cnum; + struct cmtp_conninfo *ci; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __CMTP_H */ From be04872e093d9b5738697aba8ddaf39968d2359c Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 11/73] Vendor BlueZ's hidp.h verbatim --- .../include/bluetooth/hidp.h | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/hidp.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/hidp.h b/Sources/CBluetoothLinuxABI/include/bluetooth/hidp.h new file mode 100644 index 0000000..da42a1b --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/hidp.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + */ + +#ifndef __HIDP_H +#define __HIDP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* HIDP defaults */ +#define HIDP_MINIMUM_MTU 48 +#define HIDP_DEFAULT_MTU 48 + +/* HIDP ioctl defines */ +#define HIDPCONNADD _IOW('H', 200, int) +#define HIDPCONNDEL _IOW('H', 201, int) +#define HIDPGETCONNLIST _IOR('H', 210, int) +#define HIDPGETCONNINFO _IOR('H', 211, int) + +#define HIDP_VIRTUAL_CABLE_UNPLUG 0 +#define HIDP_BOOT_PROTOCOL_MODE 1 +#define HIDP_BLUETOOTH_VENDOR_ID 9 + +struct hidp_connadd_req { + int ctrl_sock; /* Connected control socket */ + int intr_sock; /* Connected interrupt socket */ + uint16_t parser; /* Parser version */ + uint16_t rd_size; /* Report descriptor size */ + uint8_t *rd_data; /* Report descriptor data */ + uint8_t country; + uint8_t subclass; + uint16_t vendor; + uint16_t product; + uint16_t version; + uint32_t flags; + uint32_t idle_to; + char name[128]; /* Device name */ +}; + +struct hidp_conndel_req { + bdaddr_t bdaddr; + uint32_t flags; +}; + +struct hidp_conninfo { + bdaddr_t bdaddr; + uint32_t flags; + uint16_t state; + uint16_t vendor; + uint16_t product; + uint16_t version; + char name[128]; +}; + +struct hidp_connlist_req { + uint32_t cnum; + struct hidp_conninfo *ci; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __HIDP_H */ From ee12a0480a21241d6e9776e2e13f5a4d9d182cd5 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 12/73] Vendor BlueZ's uuid.h verbatim --- .../include/bluetooth/uuid.h | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/uuid.h diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/uuid.h b/Sources/CBluetoothLinuxABI/include/bluetooth/uuid.h new file mode 100644 index 0000000..479986f --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/uuid.h @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann + * Copyright 2023 NXP + * + * + */ + +#ifndef __BLUETOOTH_UUID_H +#define __BLUETOOTH_UUID_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805f9b34fb" + +#define HSP_HS_UUID "00001108-0000-1000-8000-00805f9b34fb" +#define HSP_AG_UUID "00001112-0000-1000-8000-00805f9b34fb" + +#define HFP_HS_UUID "0000111e-0000-1000-8000-00805f9b34fb" +#define HFP_AG_UUID "0000111f-0000-1000-8000-00805f9b34fb" + +#define ADVANCED_AUDIO_UUID "0000110d-0000-1000-8000-00805f9b34fb" + +#define A2DP_SOURCE_UUID "0000110a-0000-1000-8000-00805f9b34fb" +#define A2DP_SINK_UUID "0000110b-0000-1000-8000-00805f9b34fb" + +#define AVRCP_REMOTE_UUID "0000110e-0000-1000-8000-00805f9b34fb" +#define AVRCP_TARGET_UUID "0000110c-0000-1000-8000-00805f9b34fb" + +#define PANU_UUID "00001115-0000-1000-8000-00805f9b34fb" +#define NAP_UUID "00001116-0000-1000-8000-00805f9b34fb" +#define GN_UUID "00001117-0000-1000-8000-00805f9b34fb" +#define BNEP_SVC_UUID "0000000f-0000-1000-8000-00805f9b34fb" + +#define PNPID_UUID "00002a50-0000-1000-8000-00805f9b34fb" +#define DEVICE_INFORMATION_UUID "0000180a-0000-1000-8000-00805f9b34fb" + +#define GATT_UUID "00001801-0000-1000-8000-00805f9b34fb" +#define IMMEDIATE_ALERT_UUID "00001802-0000-1000-8000-00805f9b34fb" +#define LINK_LOSS_UUID "00001803-0000-1000-8000-00805f9b34fb" +#define TX_POWER_UUID "00001804-0000-1000-8000-00805f9b34fb" +#define BATTERY_UUID "0000180f-0000-1000-8000-00805f9b34fb" +#define SCAN_PARAMETERS_UUID "00001813-0000-1000-8000-00805f9b34fb" + +#define SAP_UUID "0000112D-0000-1000-8000-00805f9b34fb" + +#define HEART_RATE_UUID "0000180d-0000-1000-8000-00805f9b34fb" +#define HEART_RATE_MEASUREMENT_UUID "00002a37-0000-1000-8000-00805f9b34fb" +#define BODY_SENSOR_LOCATION_UUID "00002a38-0000-1000-8000-00805f9b34fb" +#define HEART_RATE_CONTROL_POINT_UUID "00002a39-0000-1000-8000-00805f9b34fb" + +#define HEALTH_THERMOMETER_UUID "00001809-0000-1000-8000-00805f9b34fb" +#define TEMPERATURE_MEASUREMENT_UUID "00002a1c-0000-1000-8000-00805f9b34fb" +#define TEMPERATURE_TYPE_UUID "00002a1d-0000-1000-8000-00805f9b34fb" +#define INTERMEDIATE_TEMPERATURE_UUID "00002a1e-0000-1000-8000-00805f9b34fb" +#define MEASUREMENT_INTERVAL_UUID "00002a21-0000-1000-8000-00805f9b34fb" + +#define CYCLING_SC_UUID "00001816-0000-1000-8000-00805f9b34fb" +#define CSC_MEASUREMENT_UUID "00002a5b-0000-1000-8000-00805f9b34fb" +#define CSC_FEATURE_UUID "00002a5c-0000-1000-8000-00805f9b34fb" +#define SENSOR_LOCATION_UUID "00002a5d-0000-1000-8000-00805f9b34fb" +#define SC_CONTROL_POINT_UUID "00002a55-0000-1000-8000-00805f9b34fb" + +#define RFCOMM_UUID_STR "00000003-0000-1000-8000-00805f9b34fb" + +#define HDP_UUID "00001400-0000-1000-8000-00805f9b34fb" +#define HDP_SOURCE_UUID "00001401-0000-1000-8000-00805f9b34fb" +#define HDP_SINK_UUID "00001402-0000-1000-8000-00805f9b34fb" + +#define HID_UUID "00001124-0000-1000-8000-00805f9b34fb" +#define HOG_UUID "00001812-0000-1000-8000-00805f9b34fb" + +#define DUN_GW_UUID "00001103-0000-1000-8000-00805f9b34fb" + +#define GAP_UUID "00001800-0000-1000-8000-00805f9b34fb" +#define PNP_UUID "00001200-0000-1000-8000-00805f9b34fb" + +#define SPP_UUID "00001101-0000-1000-8000-00805f9b34fb" + +#define OBEX_SYNC_UUID "00001104-0000-1000-8000-00805f9b34fb" +#define OBEX_OPP_UUID "00001105-0000-1000-8000-00805f9b34fb" +#define OBEX_FTP_UUID "00001106-0000-1000-8000-00805f9b34fb" +#define OBEX_PCE_UUID "0000112e-0000-1000-8000-00805f9b34fb" +#define OBEX_PSE_UUID "0000112f-0000-1000-8000-00805f9b34fb" +#define OBEX_PBAP_UUID "00001130-0000-1000-8000-00805f9b34fb" +#define OBEX_MAS_UUID "00001132-0000-1000-8000-00805f9b34fb" +#define OBEX_MNS_UUID "00001133-0000-1000-8000-00805f9b34fb" +#define OBEX_MAP_UUID "00001134-0000-1000-8000-00805f9b34fb" + +/* GATT UUIDs section */ +#define GATT_PRIM_SVC_UUID 0x2800 +#define GATT_SND_SVC_UUID 0x2801 +#define GATT_INCLUDE_UUID 0x2802 +#define GATT_CHARAC_UUID 0x2803 + +/* GATT Characteristic Types */ +#define GATT_CHARAC_DEVICE_NAME 0x2A00 +#define GATT_CHARAC_APPEARANCE 0x2A01 +#define GATT_CHARAC_PERIPHERAL_PRIV_FLAG 0x2A02 +#define GATT_CHARAC_RECONNECTION_ADDRESS 0x2A03 +#define GATT_CHARAC_PERIPHERAL_PREF_CONN 0x2A04 +#define GATT_CHARAC_SERVICE_CHANGED 0x2A05 +#define GATT_CHARAC_BATTERY_LEVEL 0x2A19 +#define GATT_CHARAC_SYSTEM_ID 0x2A23 +#define GATT_CHARAC_MODEL_NUMBER_STRING 0x2A24 +#define GATT_CHARAC_SERIAL_NUMBER_STRING 0x2A25 +#define GATT_CHARAC_FIRMWARE_REVISION_STRING 0x2A26 +#define GATT_CHARAC_HARDWARE_REVISION_STRING 0x2A27 +#define GATT_CHARAC_SOFTWARE_REVISION_STRING 0x2A28 +#define GATT_CHARAC_MANUFACTURER_NAME_STRING 0x2A29 +#define GATT_CHARAC_PNP_ID 0x2A50 +#define GATT_CHARAC_CAR 0x2AA6 + +/* GATT Characteristic Descriptors */ +#define GATT_CHARAC_EXT_PROPER_UUID 0x2900 +#define GATT_CHARAC_USER_DESC_UUID 0x2901 +#define GATT_CLIENT_CHARAC_CFG_UUID 0x2902 +#define GATT_SERVER_CHARAC_CFG_UUID 0x2903 +#define GATT_CHARAC_FMT_UUID 0x2904 +#define GATT_CHARAC_AGREG_FMT_UUID 0x2905 +#define GATT_CHARAC_VALID_RANGE_UUID 0x2906 +#define GATT_EXTERNAL_REPORT_REFERENCE 0x2907 +#define GATT_REPORT_REFERENCE 0x2908 + +/* GATT Mesh Services */ +#define MESH_PROV_SVC_UUID "00001827-0000-1000-8000-00805f9b34fb" +#define MESH_PROXY_SVC_UUID "00001828-0000-1000-8000-00805f9b34fb" + +/* GATT Mesh Characteristic Types */ +#define MESH_PROVISIONING_DATA_IN 0x2ADB +#define MESH_PROVISIONING_DATA_OUT 0x2ADC +#define MESH_PROXY_DATA_IN 0x2ADD +#define MESH_PROXY_DATA_OUT 0x2ADE + +/* GATT Caching attributes */ +#define GATT_CHARAC_CLI_FEAT 0x2B29 +#define GATT_CHARAC_DB_HASH 0x2B2A + +/* GATT Server Supported features */ +#define GATT_CHARAC_SERVER_FEAT 0x2B3A + +/* TODO: Update these on final UUID is given */ +#define PACS_UUID 0x1850 +#define PAC_SINK_CHRC_UUID 0x2bc9 +#define PAC_SINK_UUID "00002bc9-0000-1000-8000-00805f9b34fb" +#define PAC_SINK_LOC_CHRC_UUID 0x2bca + +#define PAC_SOURCE_CHRC_UUID 0x2bcb +#define PAC_SOURCE_UUID "00002bcb-0000-1000-8000-00805f9b34fb" +#define PAC_SOURCE_LOC_CHRC_UUID 0x2bcc + +#define BCAA_SERVICE 0x1852 +#define BCAA_SERVICE_UUID "00001852-0000-1000-8000-00805f9b34fb" + +#define BAA_SERVICE 0x1851 +#define BAA_SERVICE_UUID "00001851-0000-1000-8000-00805f9b34fb" + +#define ASHA_SERVICE 0xFDF0 +#define ASHA_PROFILE_UUID "0000FDF0-0000-1000-8000-00805f9b34fb" + +#define PAC_CONTEXT 0x2bcd +#define PAC_SUPPORTED_CONTEXT 0x2bce + +#define ASCS_UUID 0x184e +#define ASE_SINK_UUID 0x2bc4 +#define ASE_SOURCE_UUID 0x2bc5 +#define ASE_CP_UUID 0x2bc6 + +#define BASS_UUID 0x184f +#define BCAST_AUDIO_SCAN_CP_UUID 0x2bc7 +#define BCAST_RECV_STATE_UUID 0x2bc8 + +#define VCS_UUID 0x1844 +#define VOL_OFFSET_CS_UUID 0x1845 +#define AUDIO_INPUT_CS_UUID 0x1843 +#define VOL_STATE_CHRC_UUID 0x2B7D +#define VOL_CP_CHRC_UUID 0x2B7E +#define VOL_FLAG_CHRC_UUID 0x2B7F + +#define VOCS_STATE_CHAR_UUID 0x2B80 +#define VOCS_AUDIO_LOC_CHRC_UUID 0x2B81 +#define VOCS_CP_CHRC_UUID 0x2B82 +#define VOCS_AUDIO_OP_DESC_CHAR_UUID 0x2B83 + +#define AICS_INPUT_STATE_CHAR_UUID 0x2B77 +#define AICS_GAIN_SETTING_PROP_CHAR_UUID 0x2B78 +#define AICS_AUDIO_INPUT_TYPE_CHAR_UUID 0x2B79 +#define AICS_INPUT_STATUS_CHAR_UUID 0X2B7A +#define AICS_AUDIO_INPUT_CP_CHRC_UUID 0X2B7B +#define AICS_INPUT_DESCR_CHAR_UUID 0X2B7C + +#define GMCS_UUID 0x1849 +#define MEDIA_PLAYER_NAME_CHRC_UUID 0x2b93 +#define MEDIA_TRACK_CHNGD_CHRC_UUID 0x2b96 +#define MEDIA_TRACK_TITLE_CHRC_UUID 0x2b97 +#define MEDIA_TRACK_DURATION_CHRC_UUID 0x2b98 +#define MEDIA_TRACK_POSTION_CHRC_UUID 0x2b99 +#define MEDIA_PLAYBACK_SPEED_CHRC_UUID 0x2b9a +#define MEDIA_SEEKING_SPEED_CHRC_UUID 0x2b9b +#define MEDIA_PLAYING_ORDER_CHRC_UUID 0x2ba1 +#define MEDIA_PLAY_ORDER_SUPPRTD_CHRC_UUID 0x2ba2 +#define MEDIA_STATE_CHRC_UUID 0x2ba3 +#define MEDIA_CP_CHRC_UUID 0x2ba4 +#define MEDIA_CP_OP_SUPPORTED_CHRC_UUID 0x2ba5 +#define MEDIA_CONTENT_CONTROL_ID_CHRC_UUID 0x2bba + +/* Coordinated Set Identification Profile(CSIP) */ +#define CSIS_UUID 0x1846 +#define CS_SIRK 0x2B84 +#define CS_SIZE 0x2B85 +#define CS_LOCK 0x2B86 +#define CS_RANK 0x2B87 + + +/* Microphone Control Service(MICS) */ +#define MICS_UUID 0x184D +#define MUTE_CHRC_UUID 0x2BC3 + +/* Call Control Service(TBS/CCS) */ +#define TBS_UUID 0x184B +#define GTBS_UUID 0x184C + +#define BEARER_PROVIDER_NAME_CHRC_UUID 0x2bb3 +#define BEARER_UCI_CHRC_UUID 0x2bb4 +#define BEARER_TECH_CHRC_UUID 0x2bb5 +#define BEARER_URI_SCHEME_CHRC_UUID 0x2bb6 +#define BEARER_SIGNAL_STR_CHRC_UUID 0x2bb7 +#define BEARER_SIGNAL_INTRVL_CHRC_UUID 0x2bb8 +#define CURR_CALL_LIST_CHRC_UUID 0x2bb9 +#define BEARER_CCID_CHRC_UUID 0x2bba +#define CALL_STATUS_FLAG_CHRC_UUID 0x2bbb +#define INCOM_CALL_TARGET_URI_CHRC_UUID 0x2bbc +#define CALL_STATE_CHRC_UUID 0x2bbd +#define CALL_CTRL_POINT_CHRC_UUID 0x2bbe +#define CALL_CTRL_POINT_OPT_OPCODE_CHRC_UUID 0x2bbf +#define TERMINATION_REASON_CHRC_UUID 0x2bc0 +#define INCOMING_CALL_CHRC_UUID 0x2bc1 +#define CALL_FRIENDLY_NAME_CHRC_UUID 0x2bc2 + +typedef struct { + enum { + BT_UUID_UNSPEC = 0, + BT_UUID16 = 16, + BT_UUID32 = 32, + BT_UUID128 = 128, + } type; + union { + uint16_t u16; + uint32_t u32; + uint128_t u128; + } value; +} bt_uuid_t; + +int bt_uuid_strcmp(const void *a, const void *b); + +int bt_uuid16_create(bt_uuid_t *btuuid, uint16_t value); +int bt_uuid32_create(bt_uuid_t *btuuid, uint32_t value); +int bt_uuid128_create(bt_uuid_t *btuuid, uint128_t value); + +int bt_uuid_cmp(const bt_uuid_t *uuid1, const bt_uuid_t *uuid2); +int bt_uuid16_cmp(const bt_uuid_t *uuid1, uint16_t uuid2); +void bt_uuid_to_uuid128(const bt_uuid_t *src, bt_uuid_t *dst); + +#define MAX_LEN_UUID_STR 37 + +int bt_uuid_to_string(const bt_uuid_t *uuid, char *str, size_t n); +int bt_string_to_uuid(bt_uuid_t *uuid, const char *string); + +int bt_uuid_to_le(const bt_uuid_t *uuid, void *dst); + +static inline int bt_uuid_len(const bt_uuid_t *uuid) +{ + return uuid->type / 8; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __BLUETOOTH_UUID_H */ From 4dd84f7b4e24edef224cfc3be3a77fc0b875a9ef Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 13/73] Add the GPL-2.0-or-later license for the vendored headers --- .../include/bluetooth/LICENSE | 340 ++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/bluetooth/LICENSE diff --git a/Sources/CBluetoothLinuxABI/include/bluetooth/LICENSE b/Sources/CBluetoothLinuxABI/include/bluetooth/LICENSE new file mode 100644 index 0000000..6d45519 --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/bluetooth/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. From 3c2b231f58cf8e77a3dda797327975fa1684e67a Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:18 -0400 Subject: [PATCH 14/73] Add the CBluetoothLinuxABI umbrella header --- .../include/CBluetoothLinuxABI.h | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/CBluetoothLinuxABI.h diff --git a/Sources/CBluetoothLinuxABI/include/CBluetoothLinuxABI.h b/Sources/CBluetoothLinuxABI/include/CBluetoothLinuxABI.h new file mode 100644 index 0000000..2e80a8a --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/CBluetoothLinuxABI.h @@ -0,0 +1,29 @@ +// +// CBluetoothLinuxABI.h +// BluetoothLinux +// +// Umbrella header for the CBluetoothLinuxABI module. +// +// Everything under `bluetooth/` is vendored verbatim from BlueZ 5.85 +// (GPL-2.0-or-later, see bluetooth/LICENSE). The implementations — +// Swift in BluetoothLinuxABI, generated C stubs in gen/ — are +// type-checked against these declarations. +// + +#ifndef CBLUETOOTHLINUXABI_H +#define CBLUETOOTHLINUXABI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif /* CBLUETOOTHLINUXABI_H */ From a6af041c3c2385ca22a6519c5a6c40553b575609 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:19 -0400 Subject: [PATCH 15/73] Add the CBluetoothLinuxABI module map --- Sources/CBluetoothLinuxABI/include/module.modulemap | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/include/module.modulemap diff --git a/Sources/CBluetoothLinuxABI/include/module.modulemap b/Sources/CBluetoothLinuxABI/include/module.modulemap new file mode 100644 index 0000000..d97d54d --- /dev/null +++ b/Sources/CBluetoothLinuxABI/include/module.modulemap @@ -0,0 +1,4 @@ +module CBluetoothLinuxABI { + umbrella header "CBluetoothLinuxABI.h" + export * +} From 50b41b8d9498308bd874005eaf075d10ba3b8aad Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:19 -0400 Subject: [PATCH 16/73] Document CBluetoothLinuxABI's licensing and stub table --- Sources/CBluetoothLinuxABI/README.md | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/README.md diff --git a/Sources/CBluetoothLinuxABI/README.md b/Sources/CBluetoothLinuxABI/README.md new file mode 100644 index 0000000..464225a --- /dev/null +++ b/Sources/CBluetoothLinuxABI/README.md @@ -0,0 +1,55 @@ +# CBluetoothLinuxABI + +The C surface of `libbluetooth.so.3`. + +## Licensing + +This directory is licensed differently from the rest of this repository. + +- `include/bluetooth/*.h` are the eleven public headers **vendored + verbatim from [BlueZ](https://github.com/bluez/bluez) 5.85** (`bluetooth.h`, + `hci.h`, `hci_lib.h`, `sdp.h`, `sdp_lib.h`, `l2cap.h`, `rfcomm.h`, + `sco.h`, `bnep.h`, `cmtp.h`, `hidp.h`, plus `uuid.h`) and are licensed + **GPL-2.0-or-later** — see `include/bluetooth/LICENSE`. They are kept + byte-identical so that every implementation, Swift or C, is compiled + against the exact declarations the reference library exports, and so + that installing them satisfies consumers unchanged. Do not edit them; + to update, re-vendor from a newer BlueZ tag and record the tag here. +- `gen/cbt_stubs.c` is generated by `scripts/gen_stubs.py` and is + original work under this repository's MIT license. + +Binaries produced from the combination of these headers and the Swift +targets are subject to the GPL-2.0-or-later terms of the vendored +headers. Resolving the exact licensing of the shipped +`libbluetooth.so.3` is an open question that should be settled before +the port goes much further — it is the one material difference from the +swift-png precedent this work otherwise follows, where libpng's +permissive license made vendoring `png.h` free. + +## Contents + +| Path | Purpose | +|---|---| +| `include/bluetooth/` | The eleven vendored BlueZ public headers | +| `gen/cbt_stubs.c` | Generated: one loudly-failing stub per not-yet-implemented symbol | + +## The stub table + +`scripts/gen_stubs.py` emits a stub for every name in +`scripts/symbols.txt` that is not in `scripts/implemented.txt`. That is +what makes the port incremental: from the first commit the library +exports all 218 symbols the reference does, so it loads and resolves +against any consumer, and every call that is not implemented yet aborts +with a message naming the symbol rather than silently returning garbage. + +``` +$ ./stubtest +libbluetooth (PureSwift): hci_open_dev is not implemented yet. +Aborted +``` + +Each phase moves names from `symbols.txt` into `implemented.txt` and +watches the conformance diff shrink. Getting that bookkeeping wrong is a +build failure rather than a subtle bug: a name listed as implemented +that is not produces an undefined symbol at link time, and one left +listed after being implemented produces a duplicate symbol. From 73ab4d6210f73181011a65170f0ada4d4d44db91 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:26 -0400 Subject: [PATCH 17/73] Record the exported symbol surface of libbluetooth.so.3 --- scripts/symbols.txt | 233 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 scripts/symbols.txt diff --git a/scripts/symbols.txt b/scripts/symbols.txt new file mode 100644 index 0000000..8315d12 --- /dev/null +++ b/scripts/symbols.txt @@ -0,0 +1,233 @@ +# The complete export surface of libbluetooth.so.3, taken from the +# reference library (BlueZ 5.82, soname libbluetooth.so.3). +# +# scripts/check-exports.sh asserts the built library against this list; +# both a missing and an extra symbol fail. scripts/gen_stubs.py emits a +# loudly-failing stub for every name here that is not yet in +# implemented.txt, so the library is droppable before it is finished. +# +# `#` comments and blank lines are ignored. + +# --- bluetooth.c (17) — implemented in PureSwift/Bluetooth --- +ba2oui +ba2str +ba2strlc +bachk +bafprintf +baprintf +basnprintf +basprintf +baswap +batostr +bt_compidtostr +bt_error +bt_free +bt_malloc +bt_malloc0 +str2ba +strtoba + +# --- hci.c (101) --- +hci_authenticate_link +hci_bustostr +hci_change_link_key +hci_close_dev +hci_cmdtostr +hci_commandstostr +hci_create_connection +hci_delete_stored_link_key +hci_devba +hci_devid +hci_devinfo +hci_dflagstostr +hci_disconnect +hci_dtypetostr +hci_encrypt_link +hci_exit_park_mode +hci_for_each_dev +hci_get_route +hci_inquiry +hci_le_add_resolving_list +hci_le_add_white_list +hci_le_clear_resolving_list +hci_le_clear_white_list +hci_le_conn_update +hci_le_create_conn +hci_le_read_remote_features +hci_le_read_resolving_list_size +hci_le_read_white_list_size +hci_le_rm_resolving_list +hci_le_rm_white_list +hci_le_set_address_resolution_enable +hci_le_set_advertise_enable +hci_le_set_scan_enable +hci_le_set_scan_parameters +hci_lmtostr +hci_lptostr +hci_open_dev +hci_park_mode +hci_ptypetostr +hci_read_afh_map +hci_read_afh_mode +hci_read_bd_addr +hci_read_class_of_dev +hci_read_clock +hci_read_clock_offset +hci_read_current_iac_lap +hci_read_ext_inquiry_response +hci_read_inq_response_tx_power_level +hci_read_inquiry_mode +hci_read_inquiry_scan_type +hci_read_inquiry_transmit_power_level +hci_read_link_policy +hci_read_link_quality +hci_read_link_supervision_timeout +hci_read_local_commands +hci_read_local_ext_features +hci_read_local_features +hci_read_local_name +hci_read_local_oob_data +hci_read_local_version +hci_read_remote_ext_features +hci_read_remote_features +hci_read_remote_name +hci_read_remote_name_cancel +hci_read_remote_name_with_clock_offset +hci_read_remote_version +hci_read_rssi +hci_read_simple_pairing_mode +hci_read_stored_link_key +hci_read_transmit_power_level +hci_read_voice_setting +hci_scoptypetostr +hci_send_cmd +hci_send_req +hci_set_afh_classification +hci_strtolm +hci_strtolp +hci_strtoptype +hci_strtoscoptype +hci_strtover +hci_switch_role +hci_typetostr +hci_vertostr +hci_write_afh_mode +hci_write_class_of_dev +hci_write_current_iac_lap +hci_write_ext_inquiry_response +hci_write_inquiry_mode +hci_write_inquiry_scan_type +hci_write_inquiry_transmit_power_level +hci_write_link_policy +hci_write_link_supervision_timeout +hci_write_local_name +hci_write_simple_pairing_mode +hci_write_stored_link_key +hci_write_voice_setting +lmp_featurestostr +lmp_strtover +lmp_vertostr +pal_strtover +pal_vertostr + +# --- sdp.c (100) --- +sdp_add_lang_attr +sdp_append_to_buf +sdp_append_to_pdu +sdp_attr_add +sdp_attr_add_new +sdp_attr_remove +sdp_attr_replace +sdp_attrid_comp_func +sdp_close +sdp_connect +sdp_copy_record +sdp_create +sdp_data_alloc +sdp_data_alloc_with_length +sdp_data_free +sdp_data_get +sdp_device_record_register +sdp_device_record_register_binary +sdp_device_record_unregister +sdp_device_record_unregister_binary +sdp_device_record_update +sdp_device_record_update_binary +sdp_extract_attr +sdp_extract_pdu +sdp_extract_seqtype +sdp_gen_pdu +sdp_gen_record_pdu +sdp_gen_tid +sdp_general_inquiry +sdp_get_access_protos +sdp_get_add_access_protos +sdp_get_database_state +sdp_get_error +sdp_get_group_id +sdp_get_int_attr +sdp_get_lang_attr +sdp_get_profile_descs +sdp_get_proto_desc +sdp_get_proto_port +sdp_get_record_state +sdp_get_server_ver +sdp_get_service_avail +sdp_get_service_id +sdp_get_service_ttl +sdp_get_socket +sdp_get_string_attr +sdp_get_supp_feat +sdp_get_uuidseq_attr +sdp_list_append +sdp_list_free +sdp_list_insert_sorted +sdp_list_remove +sdp_pattern_add_uuid +sdp_pattern_add_uuidseq +sdp_process +sdp_profile_uuid2strn +sdp_proto_uuid2strn +sdp_record_alloc +sdp_record_free +sdp_record_print +sdp_record_register +sdp_record_unregister +sdp_record_update +sdp_send_req_w4_rsp +sdp_seq_alloc +sdp_seq_alloc_with_length +sdp_seq_append +sdp_service_attr_async +sdp_service_attr_req +sdp_service_search_async +sdp_service_search_attr_async +sdp_service_search_attr_req +sdp_service_search_req +sdp_set_access_protos +sdp_set_add_access_protos +sdp_set_attrid +sdp_set_group_id +sdp_set_info_attr +sdp_set_lang_attr +sdp_set_notify +sdp_set_profile_descs +sdp_set_seq_len +sdp_set_service_id +sdp_set_supp_feat +sdp_set_url_attr +sdp_set_uuidseq_attr +sdp_svclass_uuid2strn +sdp_uuid128_cmp +sdp_uuid128_create +sdp_uuid128_to_uuid +sdp_uuid16_cmp +sdp_uuid16_create +sdp_uuid16_to_uuid128 +sdp_uuid2strn +sdp_uuid32_create +sdp_uuid32_to_uuid128 +sdp_uuid_cmp +sdp_uuid_extract +sdp_uuid_to_proto +sdp_uuid_to_uuid128 From ca644a41209ae23f210597c78156e113231db225 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:26 -0400 Subject: [PATCH 18/73] Record which exported symbols are already implemented --- scripts/implemented.txt | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 scripts/implemented.txt diff --git a/scripts/implemented.txt b/scripts/implemented.txt new file mode 100644 index 0000000..54c6da7 --- /dev/null +++ b/scripts/implemented.txt @@ -0,0 +1,43 @@ +# Symbols already implemented, and therefore NOT stubbed. +# +# scripts/gen_stubs.py subtracts this list from symbols.txt. Moving a +# name here without providing an implementation is a link error at the +# next build (undefined symbol); leaving one here after implementing it +# is a duplicate-symbol error. Either way the mistake is loud. +# +# Each phase of the port moves names from stubbed to implemented and +# watches the conformance diff shrink. + +# --- Phase 1: PureSwift/Bluetooth, Sources/BluetoothABI + Sources/CBluetooth --- +# The bluetooth.c entry points (17). +ba2oui +ba2str +ba2strlc +bachk +bafprintf +baprintf +basnprintf +basprintf +baswap +batostr +bt_compidtostr +bt_error +bt_free +bt_malloc +bt_malloc0 +str2ba +strtoba + +# The bt_uuid_* family (10). Note these are NOT in symbols.txt: the +# reference keeps them in libbluetooth-internal.a and exports none of +# them. We export them anyway — see cmake/libbluetooth.map. +bt_string_to_uuid +bt_uuid128_create +bt_uuid16_cmp +bt_uuid16_create +bt_uuid32_create +bt_uuid_cmp +bt_uuid_strcmp +bt_uuid_to_le +bt_uuid_to_string +bt_uuid_to_uuid128 From f6f94bd930299623cce70a9c5b11bd74d602e997 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:26 -0400 Subject: [PATCH 19/73] Add the ownership audit for each exported symbol --- scripts/ownership.md | 74 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 scripts/ownership.md diff --git a/scripts/ownership.md b/scripts/ownership.md new file mode 100644 index 0000000..b8d45f9 --- /dev/null +++ b/scripts/ownership.md @@ -0,0 +1,74 @@ +# Ownership audit + +What each exported symbol returns, who frees it, and how long it lives. + +This table has to be filled in **before** a symbol is implemented, not +after: the return-value conventions in `libbluetooth` are not uniform, +and guessing produces leaks or double frees that no type checker +catches. Three conventions are already known to coexist: + +- Caller-owned heap: `batostr` returns an 18-byte `bt_malloc` buffer the + caller releases with `bt_free`; `strtoba` likewise. +- Caller-provided buffer: `ba2str`, `ba2strlc`, `ba2oui` and + `bt_uuid_to_string` write into a buffer the caller supplies and return + a length (or, for the last, a status). +- Static lifetime: `bt_compidtostr` returns a pointer into static + storage that must never be freed. The `hci_*tostr` family is mixed — + each one needs checking individually. + +And two distinct error conventions: + +- The UUID family returns `0` on success and `-EINVAL` on failure, with + `errno` untouched. `bt_uuid16_cmp` inverts even that: `1` for equal, + `0` for not equal *and* for a NULL or non-16-bit argument. +- The socket family returns `-1` and sets `errno`. + +## Status + +| Family | Symbols | Audited | +|---|---:|---| +| `bluetooth.c` | 17 | ✅ complete — implemented in PureSwift/Bluetooth | +| `bt_uuid_*` | 10 | ✅ complete — implemented in PureSwift/Bluetooth | +| `hci.c` | 101 | ❌ not started | +| `sdp.c` | 100 | ❌ not started | + +## bluetooth.c + +| Symbol | Returns | Who frees | Lifetime | +|---|---|---|---| +| `baswap` | `void` | — | writes through `dst` | +| `batostr` | `char *` (18 bytes) | caller, via `bt_free` | heap | +| `strtoba` | `bdaddr_t *` | caller, via `bt_free` | heap | +| `ba2str` | `int` (always 17) | — | writes into caller buffer, ≥18 bytes | +| `ba2strlc` | `int` (always 17) | — | writes into caller buffer, ≥18 bytes | +| `str2ba` | `int` (`0` / `-1`) | — | writes through `ba`; zeroes it on failure | +| `ba2oui` | `int` (always 8) | — | writes into caller buffer, ≥9 bytes | +| `bachk` | `int` (`0` / `-1`) | — | — | +| `baprintf` | `int` (chars written) | — | — | +| `bafprintf` | `int` (chars written) | — | — | +| `basprintf` | `int` (chars written) | — | writes into caller buffer, unbounded | +| `basnprintf` | `int` (chars *needed*) | — | writes into caller buffer, bounded | +| `bt_malloc` | `void *` | caller, via `bt_free` | heap | +| `bt_malloc0` | `void *` (zeroed) | caller, via `bt_free` | heap | +| `bt_free` | `void` | — | accepts NULL | +| `bt_error` | `int` (errno value) | — | — | +| `bt_compidtostr` | `const char *` | **nobody** | static | + +Note `basprintf` writes without a bound (the reference passes +`(~0U) >> 1` as the size) and `basnprintf` returns the length the +formatted string *would* have needed, not the length written — both +inherited straight from `vsnprintf`. + +## hci.c + +Not yet audited. 101 symbols. The `hci_*tostr` family is the part that +needs the most care: the return lifetimes are mixed within it. + +## sdp.c + +Not yet audited. 100 symbols, and the highest-risk group in the port: +`sdp_data_alloc` / `sdp_data_free` / `sdp_seq_alloc` and the intrusive +`sdp_list_t` are not really an API but an ownership contract callers +depend on, including recursive-free semantics. A defensible fallback is +to keep the reference allocator and list code as vendored C and move +only the codec to Swift. From b1469bec88aac3cb851bcfc690141538817d32a4 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:26 -0400 Subject: [PATCH 20/73] Add a generator for stubs of not-yet-implemented symbols --- scripts/gen_stubs.py | 186 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100755 scripts/gen_stubs.py diff --git a/scripts/gen_stubs.py b/scripts/gen_stubs.py new file mode 100755 index 0000000..4a0e917 --- /dev/null +++ b/scripts/gen_stubs.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +""" +Generate a loudly-failing stub for every exported symbol that is not yet +implemented. + +This is what makes the port incremental. From the first commit the +library exports all 218 names the reference does, so it loads and +resolves against any consumer; every call that is not yet implemented +aborts with a message naming the symbol rather than silently returning +garbage. Each phase moves names from `symbols.txt` into +`implemented.txt` and the stub disappears. + +Getting that bookkeeping wrong is a build failure, not a subtle bug: a +name listed as implemented that is not produces an undefined symbol at +link time, and one left here after being implemented produces a +duplicate symbol. + +The signatures come from the vendored BlueZ headers, so each stub is +type-checked against the declaration it satisfies. + +Usage: + scripts/gen_stubs.py [output.c] + +Defaults to Sources/CBluetoothLinuxABI/gen/cbt_stubs.c. +""" + +import os +import re +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +INCLUDE = os.path.join( + ROOT, "Sources", "CBluetoothLinuxABI", "include", "bluetooth" +) +SYMBOLS = os.path.join(ROOT, "scripts", "symbols.txt") +IMPLEMENTED = os.path.join(ROOT, "scripts", "implemented.txt") +DEFAULT_OUTPUT = os.path.join( + ROOT, "Sources", "CBluetoothLinuxABI", "gen", "cbt_stubs.c" +) + +HEADERS = [ + "bluetooth.h", + "hci.h", + "hci_lib.h", + "sdp.h", + "sdp_lib.h", +] + +PROLOGUE = '''/* + * Generated by scripts/gen_stubs.py — do not edit. + * + * One stub per exported symbol that PureSwift has not implemented yet. + * Each aborts with the symbol name, so an unported call site is + * immediately identifiable rather than silently wrong. + * + * Regenerate after editing scripts/implemented.txt: + * scripts/gen_stubs.py + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +__attribute__((noreturn)) +static void cbt_unimplemented(const char *symbol) +{ +\tfprintf(stderr, +\t\t"libbluetooth (PureSwift): %s is not implemented yet.\\n", +\t\tsymbol); +\tabort(); +} + +''' + + +def read_list(path): + names = [] + with open(path, encoding="utf-8") as handle: + for line in handle: + line = line.split("#", 1)[0].strip() + if line: + names.append(line) + return names + + +def read_declarations(): + """Map symbol name -> (return type, parameter list) from the headers.""" + text = [] + for name in HEADERS: + path = os.path.join(INCLUDE, name) + with open(path, encoding="utf-8") as handle: + text.append(handle.read()) + source = "\n".join(text) + + # Strip comments so a declaration inside one is not picked up. + source = re.sub(r"/\*.*?\*/", " ", source, flags=re.DOTALL) + source = re.sub(r"//[^\n]*", " ", source) + + declarations = {} + # A non-static, non-inline function declaration ending in `;`. + pattern = re.compile( + r"(? 1 else DEFAULT_OUTPUT + + exported = read_list(SYMBOLS) + implemented = set(read_list(IMPLEMENTED)) + declarations = read_declarations() + + todo = [name for name in exported if name not in implemented] + + chunks = [PROLOGUE] + missing = [] + for name in todo: + declaration = declarations.get(name) + if declaration is None: + missing.append(name) + continue + return_type, parameters = declaration + separator = "" if return_type.endswith("*") else " " + chunks.append( + "%s%s%s(%s)\n{\n%s\n}\n\n" + % (return_type, separator, name, parameters, stub_body(name, return_type)) + ) + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + with open(output_path, "w", encoding="utf-8") as handle: + handle.write("".join(chunks)) + + print( + "wrote %s (%d stubs, %d implemented, %d exported)" + % ( + os.path.relpath(output_path, ROOT), + len(todo) - len(missing), + len(implemented), + len(exported), + ) + ) + + if missing: + # A symbol the reference exports but no public header declares + # cannot be stubbed with a checked signature. Report rather than + # guess: an unchecked stub would defeat the point of vendoring + # the headers. + print( + "error: %d exported symbol(s) have no declaration in the " + "vendored headers:" % len(missing), + file=sys.stderr, + ) + for name in missing: + print(" %s" % name, file=sys.stderr) + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From ba52bd9f84cc20a6502f096ac4fbf6fbecfc8161 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:26 -0400 Subject: [PATCH 21/73] Generate stubs for the not-yet-implemented symbols --- Sources/CBluetoothLinuxABI/gen/cbt_stubs.c | 1037 ++++++++++++++++++++ 1 file changed, 1037 insertions(+) create mode 100644 Sources/CBluetoothLinuxABI/gen/cbt_stubs.c diff --git a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c new file mode 100644 index 0000000..b85a3d1 --- /dev/null +++ b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c @@ -0,0 +1,1037 @@ +/* + * Generated by scripts/gen_stubs.py — do not edit. + * + * One stub per exported symbol that PureSwift has not implemented yet. + * Each aborts with the symbol name, so an unported call site is + * immediately identifiable rather than silently wrong. + * + * Regenerate after editing scripts/implemented.txt: + * scripts/gen_stubs.py + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +__attribute__((noreturn)) +static void cbt_unimplemented(const char *symbol) +{ + fprintf(stderr, + "libbluetooth (PureSwift): %s is not implemented yet.\n", + symbol); + abort(); +} + +int hci_authenticate_link(int dd, uint16_t handle, int to) +{ + cbt_unimplemented("hci_authenticate_link"); +} + +const char *hci_bustostr(int bus) +{ + cbt_unimplemented("hci_bustostr"); +} + +int hci_change_link_key(int dd, uint16_t handle, int to) +{ + cbt_unimplemented("hci_change_link_key"); +} + +int hci_close_dev(int dd) +{ + cbt_unimplemented("hci_close_dev"); +} + +char *hci_cmdtostr(unsigned int cmd) +{ + cbt_unimplemented("hci_cmdtostr"); +} + +char *hci_commandstostr(const uint8_t *commands, const char *pref, int width) +{ + cbt_unimplemented("hci_commandstostr"); +} + +int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to) +{ + cbt_unimplemented("hci_create_connection"); +} + +int hci_delete_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to) +{ + cbt_unimplemented("hci_delete_stored_link_key"); +} + +int hci_devba(int dev_id, bdaddr_t *bdaddr) +{ + cbt_unimplemented("hci_devba"); +} + +int hci_devid(const char *str) +{ + cbt_unimplemented("hci_devid"); +} + +int hci_devinfo(int dev_id, struct hci_dev_info *di) +{ + cbt_unimplemented("hci_devinfo"); +} + +char *hci_dflagstostr(uint32_t flags) +{ + cbt_unimplemented("hci_dflagstostr"); +} + +int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to) +{ + cbt_unimplemented("hci_disconnect"); +} + +const char *hci_dtypetostr(int type) +{ + cbt_unimplemented("hci_dtypetostr"); +} + +int hci_encrypt_link(int dd, uint16_t handle, uint8_t encrypt, int to) +{ + cbt_unimplemented("hci_encrypt_link"); +} + +int hci_exit_park_mode(int dd, uint16_t handle, int to) +{ + cbt_unimplemented("hci_exit_park_mode"); +} + +int hci_for_each_dev(int flag, int(*func)(int dd, int dev_id, long arg), long arg) +{ + cbt_unimplemented("hci_for_each_dev"); +} + +int hci_get_route(bdaddr_t *bdaddr) +{ + cbt_unimplemented("hci_get_route"); +} + +int hci_inquiry(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info **ii, long flags) +{ + cbt_unimplemented("hci_inquiry"); +} + +int hci_le_add_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, uint8_t *peer_irk, uint8_t *local_irk, int to) +{ + cbt_unimplemented("hci_le_add_resolving_list"); +} + +int hci_le_add_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to) +{ + cbt_unimplemented("hci_le_add_white_list"); +} + +int hci_le_clear_resolving_list(int dd, int to) +{ + cbt_unimplemented("hci_le_clear_resolving_list"); +} + +int hci_le_clear_white_list(int dd, int to) +{ + cbt_unimplemented("hci_le_clear_white_list"); +} + +int hci_le_conn_update(int dd, uint16_t handle, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t supervision_timeout, int to) +{ + cbt_unimplemented("hci_le_conn_update"); +} + +int hci_le_create_conn(int dd, uint16_t interval, uint16_t window, uint8_t initiator_filter, uint8_t peer_bdaddr_type, bdaddr_t peer_bdaddr, uint8_t own_bdaddr_type, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t supervision_timeout, uint16_t min_ce_length, uint16_t max_ce_length, uint16_t *handle, int to) +{ + cbt_unimplemented("hci_le_create_conn"); +} + +int hci_le_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to) +{ + cbt_unimplemented("hci_le_read_remote_features"); +} + +int hci_le_read_resolving_list_size(int dd, uint8_t *size, int to) +{ + cbt_unimplemented("hci_le_read_resolving_list_size"); +} + +int hci_le_read_white_list_size(int dd, uint8_t *size, int to) +{ + cbt_unimplemented("hci_le_read_white_list_size"); +} + +int hci_le_rm_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to) +{ + cbt_unimplemented("hci_le_rm_resolving_list"); +} + +int hci_le_rm_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to) +{ + cbt_unimplemented("hci_le_rm_white_list"); +} + +int hci_le_set_address_resolution_enable(int dev_id, uint8_t enable, int to) +{ + cbt_unimplemented("hci_le_set_address_resolution_enable"); +} + +int hci_le_set_advertise_enable(int dev_id, uint8_t enable, int to) +{ + cbt_unimplemented("hci_le_set_advertise_enable"); +} + +int hci_le_set_scan_enable(int dev_id, uint8_t enable, uint8_t filter_dup, int to) +{ + cbt_unimplemented("hci_le_set_scan_enable"); +} + +int hci_le_set_scan_parameters(int dev_id, uint8_t type, uint16_t interval, uint16_t window, uint8_t own_type, uint8_t filter, int to) +{ + cbt_unimplemented("hci_le_set_scan_parameters"); +} + +char *hci_lmtostr(unsigned int ptype) +{ + cbt_unimplemented("hci_lmtostr"); +} + +char *hci_lptostr(unsigned int ptype) +{ + cbt_unimplemented("hci_lptostr"); +} + +int hci_open_dev(int dev_id) +{ + cbt_unimplemented("hci_open_dev"); +} + +int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval, uint16_t min_interval, int to) +{ + cbt_unimplemented("hci_park_mode"); +} + +char *hci_ptypetostr(unsigned int ptype) +{ + cbt_unimplemented("hci_ptypetostr"); +} + +int hci_read_afh_map(int dd, uint16_t handle, uint8_t *mode, uint8_t *map, int to) +{ + cbt_unimplemented("hci_read_afh_map"); +} + +int hci_read_afh_mode(int dd, uint8_t *mode, int to) +{ + cbt_unimplemented("hci_read_afh_mode"); +} + +int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to) +{ + cbt_unimplemented("hci_read_bd_addr"); +} + +int hci_read_class_of_dev(int dd, uint8_t *cls, int to) +{ + cbt_unimplemented("hci_read_class_of_dev"); +} + +int hci_read_clock(int dd, uint16_t handle, uint8_t which, uint32_t *clock, uint16_t *accuracy, int to) +{ + cbt_unimplemented("hci_read_clock"); +} + +int hci_read_clock_offset(int dd, uint16_t handle, uint16_t *clkoffset, int to) +{ + cbt_unimplemented("hci_read_clock_offset"); +} + +int hci_read_current_iac_lap(int dd, uint8_t *num_iac, uint8_t *lap, int to) +{ + cbt_unimplemented("hci_read_current_iac_lap"); +} + +int hci_read_ext_inquiry_response(int dd, uint8_t *fec, uint8_t *data, int to) +{ + cbt_unimplemented("hci_read_ext_inquiry_response"); +} + +int hci_read_inq_response_tx_power_level(int dd, int8_t *level, int to) +{ + cbt_unimplemented("hci_read_inq_response_tx_power_level"); +} + +int hci_read_inquiry_mode(int dd, uint8_t *mode, int to) +{ + cbt_unimplemented("hci_read_inquiry_mode"); +} + +int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to) +{ + cbt_unimplemented("hci_read_inquiry_scan_type"); +} + +int hci_read_inquiry_transmit_power_level(int dd, int8_t *level, int to) +{ + cbt_unimplemented("hci_read_inquiry_transmit_power_level"); +} + +int hci_read_link_policy(int dd, uint16_t handle, uint16_t *policy, int to) +{ + cbt_unimplemented("hci_read_link_policy"); +} + +int hci_read_link_quality(int dd, uint16_t handle, uint8_t *link_quality, int to) +{ + cbt_unimplemented("hci_read_link_quality"); +} + +int hci_read_link_supervision_timeout(int dd, uint16_t handle, uint16_t *timeout, int to) +{ + cbt_unimplemented("hci_read_link_supervision_timeout"); +} + +int hci_read_local_commands(int dd, uint8_t *commands, int to) +{ + cbt_unimplemented("hci_read_local_commands"); +} + +int hci_read_local_ext_features(int dd, uint8_t page, uint8_t *max_page, uint8_t *features, int to) +{ + cbt_unimplemented("hci_read_local_ext_features"); +} + +int hci_read_local_features(int dd, uint8_t *features, int to) +{ + cbt_unimplemented("hci_read_local_features"); +} + +int hci_read_local_name(int dd, int len, char *name, int to) +{ + cbt_unimplemented("hci_read_local_name"); +} + +int hci_read_local_oob_data(int dd, uint8_t *hash, uint8_t *randomizer, int to) +{ + cbt_unimplemented("hci_read_local_oob_data"); +} + +int hci_read_local_version(int dd, struct hci_version *ver, int to) +{ + cbt_unimplemented("hci_read_local_version"); +} + +int hci_read_remote_ext_features(int dd, uint16_t handle, uint8_t page, uint8_t *max_page, uint8_t *features, int to) +{ + cbt_unimplemented("hci_read_remote_ext_features"); +} + +int hci_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to) +{ + cbt_unimplemented("hci_read_remote_features"); +} + +int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to) +{ + cbt_unimplemented("hci_read_remote_name"); +} + +int hci_read_remote_name_cancel(int dd, const bdaddr_t *bdaddr, int to) +{ + cbt_unimplemented("hci_read_remote_name_cancel"); +} + +int hci_read_remote_name_with_clock_offset(int dd, const bdaddr_t *bdaddr, uint8_t pscan_rep_mode, uint16_t clkoffset, int len, char *name, int to) +{ + cbt_unimplemented("hci_read_remote_name_with_clock_offset"); +} + +int hci_read_remote_version(int dd, uint16_t handle, struct hci_version *ver, int to) +{ + cbt_unimplemented("hci_read_remote_version"); +} + +int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to) +{ + cbt_unimplemented("hci_read_rssi"); +} + +int hci_read_simple_pairing_mode(int dd, uint8_t *mode, int to) +{ + cbt_unimplemented("hci_read_simple_pairing_mode"); +} + +int hci_read_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to) +{ + cbt_unimplemented("hci_read_stored_link_key"); +} + +int hci_read_transmit_power_level(int dd, uint16_t handle, uint8_t type, int8_t *level, int to) +{ + cbt_unimplemented("hci_read_transmit_power_level"); +} + +int hci_read_voice_setting(int dd, uint16_t *vs, int to) +{ + cbt_unimplemented("hci_read_voice_setting"); +} + +char *hci_scoptypetostr(unsigned int ptype) +{ + cbt_unimplemented("hci_scoptypetostr"); +} + +int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param) +{ + cbt_unimplemented("hci_send_cmd"); +} + +int hci_send_req(int dd, struct hci_request *req, int timeout) +{ + cbt_unimplemented("hci_send_req"); +} + +int hci_set_afh_classification(int dd, uint8_t *map, int to) +{ + cbt_unimplemented("hci_set_afh_classification"); +} + +int hci_strtolm(char *str, unsigned int *val) +{ + cbt_unimplemented("hci_strtolm"); +} + +int hci_strtolp(char *str, unsigned int *val) +{ + cbt_unimplemented("hci_strtolp"); +} + +int hci_strtoptype(char *str, unsigned int *val) +{ + cbt_unimplemented("hci_strtoptype"); +} + +int hci_strtoscoptype(char *str, unsigned int *val) +{ + cbt_unimplemented("hci_strtoscoptype"); +} + +int hci_strtover(char *str, unsigned int *ver) +{ + cbt_unimplemented("hci_strtover"); +} + +int hci_switch_role(int dd, bdaddr_t *bdaddr, uint8_t role, int to) +{ + cbt_unimplemented("hci_switch_role"); +} + +char *hci_typetostr(int type) +{ + cbt_unimplemented("hci_typetostr"); +} + +char *hci_vertostr(unsigned int ver) +{ + cbt_unimplemented("hci_vertostr"); +} + +int hci_write_afh_mode(int dd, uint8_t mode, int to) +{ + cbt_unimplemented("hci_write_afh_mode"); +} + +int hci_write_class_of_dev(int dd, uint32_t cls, int to) +{ + cbt_unimplemented("hci_write_class_of_dev"); +} + +int hci_write_current_iac_lap(int dd, uint8_t num_iac, uint8_t *lap, int to) +{ + cbt_unimplemented("hci_write_current_iac_lap"); +} + +int hci_write_ext_inquiry_response(int dd, uint8_t fec, uint8_t *data, int to) +{ + cbt_unimplemented("hci_write_ext_inquiry_response"); +} + +int hci_write_inquiry_mode(int dd, uint8_t mode, int to) +{ + cbt_unimplemented("hci_write_inquiry_mode"); +} + +int hci_write_inquiry_scan_type(int dd, uint8_t type, int to) +{ + cbt_unimplemented("hci_write_inquiry_scan_type"); +} + +int hci_write_inquiry_transmit_power_level(int dd, int8_t level, int to) +{ + cbt_unimplemented("hci_write_inquiry_transmit_power_level"); +} + +int hci_write_link_policy(int dd, uint16_t handle, uint16_t policy, int to) +{ + cbt_unimplemented("hci_write_link_policy"); +} + +int hci_write_link_supervision_timeout(int dd, uint16_t handle, uint16_t timeout, int to) +{ + cbt_unimplemented("hci_write_link_supervision_timeout"); +} + +int hci_write_local_name(int dd, const char *name, int to) +{ + cbt_unimplemented("hci_write_local_name"); +} + +int hci_write_simple_pairing_mode(int dd, uint8_t mode, int to) +{ + cbt_unimplemented("hci_write_simple_pairing_mode"); +} + +int hci_write_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t *key, int to) +{ + cbt_unimplemented("hci_write_stored_link_key"); +} + +int hci_write_voice_setting(int dd, uint16_t vs, int to) +{ + cbt_unimplemented("hci_write_voice_setting"); +} + +char *lmp_featurestostr(uint8_t *features, char *pref, int width) +{ + cbt_unimplemented("lmp_featurestostr"); +} + +int lmp_strtover(char *str, unsigned int *ver) +{ + cbt_unimplemented("lmp_strtover"); +} + +char *lmp_vertostr(unsigned int ver) +{ + cbt_unimplemented("lmp_vertostr"); +} + +int pal_strtover(char *str, unsigned int *ver) +{ + cbt_unimplemented("pal_strtover"); +} + +char *pal_vertostr(unsigned int ver) +{ + cbt_unimplemented("pal_vertostr"); +} + +void sdp_add_lang_attr(sdp_record_t *rec) +{ + cbt_unimplemented("sdp_add_lang_attr"); +} + +void sdp_append_to_buf(sdp_buf_t *dst, uint8_t *data, uint32_t len) +{ + cbt_unimplemented("sdp_append_to_buf"); +} + +void sdp_append_to_pdu(sdp_buf_t *dst, sdp_data_t *d) +{ + cbt_unimplemented("sdp_append_to_pdu"); +} + +int sdp_attr_add(sdp_record_t *rec, uint16_t attr, sdp_data_t *data) +{ + cbt_unimplemented("sdp_attr_add"); +} + +int sdp_attr_add_new(sdp_record_t *rec, uint16_t attr, uint8_t dtd, const void *p) +{ + cbt_unimplemented("sdp_attr_add_new"); +} + +void sdp_attr_remove(sdp_record_t *rec, uint16_t attr) +{ + cbt_unimplemented("sdp_attr_remove"); +} + +void sdp_attr_replace(sdp_record_t *rec, uint16_t attr, sdp_data_t *data) +{ + cbt_unimplemented("sdp_attr_replace"); +} + +int sdp_attrid_comp_func(const void *key1, const void *key2) +{ + cbt_unimplemented("sdp_attrid_comp_func"); +} + +int sdp_close(sdp_session_t *session) +{ + cbt_unimplemented("sdp_close"); +} + +sdp_session_t *sdp_connect(const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags) +{ + cbt_unimplemented("sdp_connect"); +} + +sdp_record_t *sdp_copy_record(sdp_record_t *rec) +{ + cbt_unimplemented("sdp_copy_record"); +} + +sdp_session_t *sdp_create(int sk, uint32_t flags) +{ + cbt_unimplemented("sdp_create"); +} + +sdp_data_t *sdp_data_alloc(uint8_t dtd, const void *value) +{ + cbt_unimplemented("sdp_data_alloc"); +} + +sdp_data_t *sdp_data_alloc_with_length(uint8_t dtd, const void *value, uint32_t length) +{ + cbt_unimplemented("sdp_data_alloc_with_length"); +} + +void sdp_data_free(sdp_data_t *data) +{ + cbt_unimplemented("sdp_data_free"); +} + +sdp_data_t *sdp_data_get(const sdp_record_t *rec, uint16_t attr_id) +{ + cbt_unimplemented("sdp_data_get"); +} + +int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags) +{ + cbt_unimplemented("sdp_device_record_register"); +} + +int sdp_device_record_register_binary(sdp_session_t *session, bdaddr_t *device, uint8_t *data, uint32_t size, uint8_t flags, uint32_t *handle) +{ + cbt_unimplemented("sdp_device_record_register_binary"); +} + +int sdp_device_record_unregister(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec) +{ + cbt_unimplemented("sdp_device_record_unregister"); +} + +int sdp_device_record_unregister_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle) +{ + cbt_unimplemented("sdp_device_record_unregister_binary"); +} + +int sdp_device_record_update(sdp_session_t *session, bdaddr_t *device, const sdp_record_t *rec) +{ + cbt_unimplemented("sdp_device_record_update"); +} + +int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle, uint8_t *data, uint32_t size) +{ + cbt_unimplemented("sdp_device_record_update_binary"); +} + +sdp_data_t *sdp_extract_attr(const uint8_t *pdata, int bufsize, int *extractedLength, sdp_record_t *rec) +{ + cbt_unimplemented("sdp_extract_attr"); +} + +sdp_record_t *sdp_extract_pdu(const uint8_t *pdata, int bufsize, int *scanned) +{ + cbt_unimplemented("sdp_extract_pdu"); +} + +int sdp_extract_seqtype(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size) +{ + cbt_unimplemented("sdp_extract_seqtype"); +} + +int sdp_gen_pdu(sdp_buf_t *pdu, sdp_data_t *data) +{ + cbt_unimplemented("sdp_gen_pdu"); +} + +int sdp_gen_record_pdu(const sdp_record_t *rec, sdp_buf_t *pdu) +{ + cbt_unimplemented("sdp_gen_record_pdu"); +} + +uint16_t sdp_gen_tid(sdp_session_t *session) +{ + cbt_unimplemented("sdp_gen_tid"); +} + +int sdp_general_inquiry(inquiry_info *ii, int dev_num, int duration, uint8_t *found) +{ + cbt_unimplemented("sdp_general_inquiry"); +} + +int sdp_get_access_protos(const sdp_record_t *rec, sdp_list_t **protos) +{ + cbt_unimplemented("sdp_get_access_protos"); +} + +int sdp_get_add_access_protos(const sdp_record_t *rec, sdp_list_t **protos) +{ + cbt_unimplemented("sdp_get_add_access_protos"); +} + +int sdp_get_database_state(const sdp_record_t *rec, uint32_t *svcDBState) +{ + cbt_unimplemented("sdp_get_database_state"); +} + +int sdp_get_error(sdp_session_t *session) +{ + cbt_unimplemented("sdp_get_error"); +} + +int sdp_get_group_id(const sdp_record_t *rec, uuid_t *uuid) +{ + cbt_unimplemented("sdp_get_group_id"); +} + +int sdp_get_int_attr(const sdp_record_t *rec, uint16_t attr, int *value) +{ + cbt_unimplemented("sdp_get_int_attr"); +} + +int sdp_get_lang_attr(const sdp_record_t *rec, sdp_list_t **langSeq) +{ + cbt_unimplemented("sdp_get_lang_attr"); +} + +int sdp_get_profile_descs(const sdp_record_t *rec, sdp_list_t **profDesc) +{ + cbt_unimplemented("sdp_get_profile_descs"); +} + +sdp_data_t *sdp_get_proto_desc(sdp_list_t *list, int proto) +{ + cbt_unimplemented("sdp_get_proto_desc"); +} + +int sdp_get_proto_port(const sdp_list_t *list, int proto) +{ + cbt_unimplemented("sdp_get_proto_port"); +} + +int sdp_get_record_state(const sdp_record_t *rec, uint32_t *svcRecState) +{ + cbt_unimplemented("sdp_get_record_state"); +} + +int sdp_get_server_ver(const sdp_record_t *rec, sdp_list_t **pVnumList) +{ + cbt_unimplemented("sdp_get_server_ver"); +} + +int sdp_get_service_avail(const sdp_record_t *rec, uint8_t *svcAvail) +{ + cbt_unimplemented("sdp_get_service_avail"); +} + +int sdp_get_service_id(const sdp_record_t *rec, uuid_t *uuid) +{ + cbt_unimplemented("sdp_get_service_id"); +} + +int sdp_get_service_ttl(const sdp_record_t *rec, uint32_t *svcTTLInfo) +{ + cbt_unimplemented("sdp_get_service_ttl"); +} + +int sdp_get_socket(const sdp_session_t *session) +{ + cbt_unimplemented("sdp_get_socket"); +} + +int sdp_get_string_attr(const sdp_record_t *rec, uint16_t attr, char *value, size_t valuelen) +{ + cbt_unimplemented("sdp_get_string_attr"); +} + +int sdp_get_supp_feat(const sdp_record_t *rec, sdp_list_t **seqp) +{ + cbt_unimplemented("sdp_get_supp_feat"); +} + +int sdp_get_uuidseq_attr(const sdp_record_t *rec, uint16_t attr, sdp_list_t **seqp) +{ + cbt_unimplemented("sdp_get_uuidseq_attr"); +} + +sdp_list_t *sdp_list_append(sdp_list_t *list, void *d) +{ + cbt_unimplemented("sdp_list_append"); +} + +void sdp_list_free(sdp_list_t *list, sdp_free_func_t f) +{ + cbt_unimplemented("sdp_list_free"); +} + +sdp_list_t *sdp_list_insert_sorted(sdp_list_t *list, void *data, sdp_comp_func_t f) +{ + cbt_unimplemented("sdp_list_insert_sorted"); +} + +sdp_list_t *sdp_list_remove(sdp_list_t *list, void *d) +{ + cbt_unimplemented("sdp_list_remove"); +} + +void sdp_pattern_add_uuid(sdp_record_t *rec, uuid_t *uuid) +{ + cbt_unimplemented("sdp_pattern_add_uuid"); +} + +void sdp_pattern_add_uuidseq(sdp_record_t *rec, sdp_list_t *seq) +{ + cbt_unimplemented("sdp_pattern_add_uuidseq"); +} + +int sdp_process(sdp_session_t *session) +{ + cbt_unimplemented("sdp_process"); +} + +int sdp_profile_uuid2strn(const uuid_t *uuid, char *str, size_t n) +{ + cbt_unimplemented("sdp_profile_uuid2strn"); +} + +int sdp_proto_uuid2strn(const uuid_t *uuid, char *str, size_t n) +{ + cbt_unimplemented("sdp_proto_uuid2strn"); +} + +sdp_record_t *sdp_record_alloc(void) +{ + cbt_unimplemented("sdp_record_alloc"); +} + +void sdp_record_free(sdp_record_t *rec) +{ + cbt_unimplemented("sdp_record_free"); +} + +void sdp_record_print(const sdp_record_t *rec) +{ + cbt_unimplemented("sdp_record_print"); +} + +int sdp_record_register(sdp_session_t *session, sdp_record_t *rec, uint8_t flags) +{ + cbt_unimplemented("sdp_record_register"); +} + +int sdp_record_unregister(sdp_session_t *session, sdp_record_t *rec) +{ + cbt_unimplemented("sdp_record_unregister"); +} + +int sdp_record_update(sdp_session_t *sess, const sdp_record_t *rec) +{ + cbt_unimplemented("sdp_record_update"); +} + +int sdp_send_req_w4_rsp(sdp_session_t *session, uint8_t *req, uint8_t *rsp, uint32_t reqsize, uint32_t *rspsize) +{ + cbt_unimplemented("sdp_send_req_w4_rsp"); +} + +sdp_data_t *sdp_seq_alloc(void **dtds, void **values, int len) +{ + cbt_unimplemented("sdp_seq_alloc"); +} + +sdp_data_t *sdp_seq_alloc_with_length(void **dtds, void **values, int *length, int len) +{ + cbt_unimplemented("sdp_seq_alloc_with_length"); +} + +sdp_data_t *sdp_seq_append(sdp_data_t *seq, sdp_data_t *data) +{ + cbt_unimplemented("sdp_seq_append"); +} + +int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) +{ + cbt_unimplemented("sdp_service_attr_async"); +} + +sdp_record_t *sdp_service_attr_req(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) +{ + cbt_unimplemented("sdp_service_attr_req"); +} + +int sdp_service_search_async(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num) +{ + cbt_unimplemented("sdp_service_search_async"); +} + +int sdp_service_search_attr_async(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) +{ + cbt_unimplemented("sdp_service_search_attr_async"); +} + +int sdp_service_search_attr_req(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list, sdp_list_t **rsp_list) +{ + cbt_unimplemented("sdp_service_search_attr_req"); +} + +int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num, sdp_list_t **rsp_list) +{ + cbt_unimplemented("sdp_service_search_req"); +} + +int sdp_set_access_protos(sdp_record_t *rec, const sdp_list_t *proto) +{ + cbt_unimplemented("sdp_set_access_protos"); +} + +int sdp_set_add_access_protos(sdp_record_t *rec, const sdp_list_t *proto) +{ + cbt_unimplemented("sdp_set_add_access_protos"); +} + +void sdp_set_attrid(sdp_buf_t *pdu, uint16_t id) +{ + cbt_unimplemented("sdp_set_attrid"); +} + +void sdp_set_group_id(sdp_record_t *rec, uuid_t grouuuid) +{ + cbt_unimplemented("sdp_set_group_id"); +} + +void sdp_set_info_attr(sdp_record_t *rec, const char *name, const char *prov, const char *desc) +{ + cbt_unimplemented("sdp_set_info_attr"); +} + +int sdp_set_lang_attr(sdp_record_t *rec, const sdp_list_t *list) +{ + cbt_unimplemented("sdp_set_lang_attr"); +} + +int sdp_set_notify(sdp_session_t *session, sdp_callback_t *func, void *udata) +{ + cbt_unimplemented("sdp_set_notify"); +} + +int sdp_set_profile_descs(sdp_record_t *rec, const sdp_list_t *desc) +{ + cbt_unimplemented("sdp_set_profile_descs"); +} + +void sdp_set_seq_len(uint8_t *ptr, uint32_t length) +{ + cbt_unimplemented("sdp_set_seq_len"); +} + +void sdp_set_service_id(sdp_record_t *rec, uuid_t uuid) +{ + cbt_unimplemented("sdp_set_service_id"); +} + +int sdp_set_supp_feat(sdp_record_t *rec, const sdp_list_t *sf) +{ + cbt_unimplemented("sdp_set_supp_feat"); +} + +void sdp_set_url_attr(sdp_record_t *rec, const char *clientExecURL, const char *docURL, const char *iconURL) +{ + cbt_unimplemented("sdp_set_url_attr"); +} + +int sdp_set_uuidseq_attr(sdp_record_t *rec, uint16_t attr, sdp_list_t *seq) +{ + cbt_unimplemented("sdp_set_uuidseq_attr"); +} + +int sdp_svclass_uuid2strn(const uuid_t *uuid, char *str, size_t n) +{ + cbt_unimplemented("sdp_svclass_uuid2strn"); +} + +int sdp_uuid128_cmp(const void *p1, const void *p2) +{ + cbt_unimplemented("sdp_uuid128_cmp"); +} + +uuid_t *sdp_uuid128_create(uuid_t *uuid, const void *data) +{ + cbt_unimplemented("sdp_uuid128_create"); +} + +int sdp_uuid128_to_uuid(uuid_t *uuid) +{ + cbt_unimplemented("sdp_uuid128_to_uuid"); +} + +int sdp_uuid16_cmp(const void *p1, const void *p2) +{ + cbt_unimplemented("sdp_uuid16_cmp"); +} + +uuid_t *sdp_uuid16_create(uuid_t *uuid, uint16_t data) +{ + cbt_unimplemented("sdp_uuid16_create"); +} + +void sdp_uuid16_to_uuid128(uuid_t *uuid128, const uuid_t *uuid16) +{ + cbt_unimplemented("sdp_uuid16_to_uuid128"); +} + +int sdp_uuid2strn(const uuid_t *uuid, char *str, size_t n) +{ + cbt_unimplemented("sdp_uuid2strn"); +} + +uuid_t *sdp_uuid32_create(uuid_t *uuid, uint32_t data) +{ + cbt_unimplemented("sdp_uuid32_create"); +} + +void sdp_uuid32_to_uuid128(uuid_t *uuid128, const uuid_t *uuid32) +{ + cbt_unimplemented("sdp_uuid32_to_uuid128"); +} + +int sdp_uuid_cmp(const void *p1, const void *p2) +{ + cbt_unimplemented("sdp_uuid_cmp"); +} + +int sdp_uuid_extract(const uint8_t *buffer, int bufsize, uuid_t *uuid, int *scanned) +{ + cbt_unimplemented("sdp_uuid_extract"); +} + +int sdp_uuid_to_proto(uuid_t *uuid) +{ + cbt_unimplemented("sdp_uuid_to_proto"); +} + +uuid_t *sdp_uuid_to_uuid128(const uuid_t *uuid) +{ + cbt_unimplemented("sdp_uuid_to_uuid128"); +} + From 555fe15906ed62d0045174ea53cc627b457872a1 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:26 -0400 Subject: [PATCH 22/73] Add a generator for the linker version script and export list --- scripts/gen_symbols.py | 125 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 scripts/gen_symbols.py diff --git a/scripts/gen_symbols.py b/scripts/gen_symbols.py new file mode 100755 index 0000000..960b973 --- /dev/null +++ b/scripts/gen_symbols.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +""" +Generate the linker version script from symbols.txt. + +Upstream libbluetooth exports every symbol it defines and carries no +version script. We pin the list instead, so that the Swift runtime's +symbols and our own internals stay local and the export surface is +asserted rather than incidental. + +That is new behavior relative to the reference, and the risk it carries +is real: a consumer bound to something incidental that upstream happened +to export would stop resolving. Before this list is treated as final, +Debian's Contents index should be scanned for binaries with an undefined +symbol satisfied by libbluetooth.so.3. + +The `bt_uuid_*` family is added on top of symbols.txt: the reference +keeps those in libbluetooth-internal.a and exports none of them, but +PureSwift implements them and there is no reason to hide them. + +Usage: + scripts/gen_symbols.py [output.map] + +Defaults to cmake/libbluetooth.map. +""" + +import os +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SYMBOLS = os.path.join(ROOT, "scripts", "symbols.txt") +DEFAULT_OUTPUT = os.path.join(ROOT, "cmake", "libbluetooth.map") + +# The flat list of everything the version script exports, for +# scripts/check-exports.sh to assert the built library against. +EXPORTED = os.path.join(ROOT, "scripts", "exported.txt") + +# Implemented by PureSwift/Bluetooth and exported deliberately, though +# the reference library does not export them. +EXTRA_SYMBOLS = [ + "bt_string_to_uuid", + "bt_uuid128_create", + "bt_uuid16_cmp", + "bt_uuid16_create", + "bt_uuid32_create", + "bt_uuid_cmp", + "bt_uuid_strcmp", + "bt_uuid_to_le", + "bt_uuid_to_string", + "bt_uuid_to_uuid128", +] + +VERSION_NODE = "LIBBLUETOOTH_5" + + +def read_list(path): + names = [] + with open(path, encoding="utf-8") as handle: + for line in handle: + line = line.split("#", 1)[0].strip() + if line: + names.append(line) + return names + + +def main(): + output_path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_OUTPUT + + exported = read_list(SYMBOLS) + extra = [name for name in EXTRA_SYMBOLS if name not in exported] + + lines = [ + "/*", + " * Generated by scripts/gen_symbols.py — do not edit.", + " *", + " * The export surface of libbluetooth.so.3. Regenerate after", + " * editing scripts/symbols.txt:", + " * scripts/gen_symbols.py", + " */", + "", + "%s {" % VERSION_NODE, + "global:", + " /* The reference export surface (%d) */" % len(exported), + ] + lines += [" %s;" % name for name in sorted(exported)] + lines += [ + "", + " /* Implemented by PureSwift but not exported by the reference (%d) */" + % len(extra), + ] + lines += [" %s;" % name for name in sorted(extra)] + lines += [ + "", + "local:", + " *;", + "};", + "", + ] + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + with open(output_path, "w", encoding="utf-8") as handle: + handle.write("\n".join(lines)) + + with open(EXPORTED, "w", encoding="utf-8") as handle: + handle.write( + "# Generated by scripts/gen_symbols.py — do not edit.\n" + "#\n" + "# Every symbol cmake/libbluetooth.map exports, flat, for\n" + "# scripts/check-exports.sh.\n" + ) + handle.write("\n".join(sorted(exported + extra)) + "\n") + + print( + "wrote %s (%d symbols: %d reference + %d extra)" + % ( + os.path.relpath(output_path, ROOT), + len(exported) + len(extra), + len(exported), + len(extra), + ) + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 0e33e28aac0acc5ed0f701711f3a8cb695d0536e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:26 -0400 Subject: [PATCH 23/73] Add the linker version script for libbluetooth.so.3 --- cmake/libbluetooth.map | 245 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 cmake/libbluetooth.map diff --git a/cmake/libbluetooth.map b/cmake/libbluetooth.map new file mode 100644 index 0000000..1c741c3 --- /dev/null +++ b/cmake/libbluetooth.map @@ -0,0 +1,245 @@ +/* + * Generated by scripts/gen_symbols.py — do not edit. + * + * The export surface of libbluetooth.so.3. Regenerate after + * editing scripts/symbols.txt: + * scripts/gen_symbols.py + */ + +LIBBLUETOOTH_5 { +global: + /* The reference export surface (218) */ + ba2oui; + ba2str; + ba2strlc; + bachk; + bafprintf; + baprintf; + basnprintf; + basprintf; + baswap; + batostr; + bt_compidtostr; + bt_error; + bt_free; + bt_malloc; + bt_malloc0; + hci_authenticate_link; + hci_bustostr; + hci_change_link_key; + hci_close_dev; + hci_cmdtostr; + hci_commandstostr; + hci_create_connection; + hci_delete_stored_link_key; + hci_devba; + hci_devid; + hci_devinfo; + hci_dflagstostr; + hci_disconnect; + hci_dtypetostr; + hci_encrypt_link; + hci_exit_park_mode; + hci_for_each_dev; + hci_get_route; + hci_inquiry; + hci_le_add_resolving_list; + hci_le_add_white_list; + hci_le_clear_resolving_list; + hci_le_clear_white_list; + hci_le_conn_update; + hci_le_create_conn; + hci_le_read_remote_features; + hci_le_read_resolving_list_size; + hci_le_read_white_list_size; + hci_le_rm_resolving_list; + hci_le_rm_white_list; + hci_le_set_address_resolution_enable; + hci_le_set_advertise_enable; + hci_le_set_scan_enable; + hci_le_set_scan_parameters; + hci_lmtostr; + hci_lptostr; + hci_open_dev; + hci_park_mode; + hci_ptypetostr; + hci_read_afh_map; + hci_read_afh_mode; + hci_read_bd_addr; + hci_read_class_of_dev; + hci_read_clock; + hci_read_clock_offset; + hci_read_current_iac_lap; + hci_read_ext_inquiry_response; + hci_read_inq_response_tx_power_level; + hci_read_inquiry_mode; + hci_read_inquiry_scan_type; + hci_read_inquiry_transmit_power_level; + hci_read_link_policy; + hci_read_link_quality; + hci_read_link_supervision_timeout; + hci_read_local_commands; + hci_read_local_ext_features; + hci_read_local_features; + hci_read_local_name; + hci_read_local_oob_data; + hci_read_local_version; + hci_read_remote_ext_features; + hci_read_remote_features; + hci_read_remote_name; + hci_read_remote_name_cancel; + hci_read_remote_name_with_clock_offset; + hci_read_remote_version; + hci_read_rssi; + hci_read_simple_pairing_mode; + hci_read_stored_link_key; + hci_read_transmit_power_level; + hci_read_voice_setting; + hci_scoptypetostr; + hci_send_cmd; + hci_send_req; + hci_set_afh_classification; + hci_strtolm; + hci_strtolp; + hci_strtoptype; + hci_strtoscoptype; + hci_strtover; + hci_switch_role; + hci_typetostr; + hci_vertostr; + hci_write_afh_mode; + hci_write_class_of_dev; + hci_write_current_iac_lap; + hci_write_ext_inquiry_response; + hci_write_inquiry_mode; + hci_write_inquiry_scan_type; + hci_write_inquiry_transmit_power_level; + hci_write_link_policy; + hci_write_link_supervision_timeout; + hci_write_local_name; + hci_write_simple_pairing_mode; + hci_write_stored_link_key; + hci_write_voice_setting; + lmp_featurestostr; + lmp_strtover; + lmp_vertostr; + pal_strtover; + pal_vertostr; + sdp_add_lang_attr; + sdp_append_to_buf; + sdp_append_to_pdu; + sdp_attr_add; + sdp_attr_add_new; + sdp_attr_remove; + sdp_attr_replace; + sdp_attrid_comp_func; + sdp_close; + sdp_connect; + sdp_copy_record; + sdp_create; + sdp_data_alloc; + sdp_data_alloc_with_length; + sdp_data_free; + sdp_data_get; + sdp_device_record_register; + sdp_device_record_register_binary; + sdp_device_record_unregister; + sdp_device_record_unregister_binary; + sdp_device_record_update; + sdp_device_record_update_binary; + sdp_extract_attr; + sdp_extract_pdu; + sdp_extract_seqtype; + sdp_gen_pdu; + sdp_gen_record_pdu; + sdp_gen_tid; + sdp_general_inquiry; + sdp_get_access_protos; + sdp_get_add_access_protos; + sdp_get_database_state; + sdp_get_error; + sdp_get_group_id; + sdp_get_int_attr; + sdp_get_lang_attr; + sdp_get_profile_descs; + sdp_get_proto_desc; + sdp_get_proto_port; + sdp_get_record_state; + sdp_get_server_ver; + sdp_get_service_avail; + sdp_get_service_id; + sdp_get_service_ttl; + sdp_get_socket; + sdp_get_string_attr; + sdp_get_supp_feat; + sdp_get_uuidseq_attr; + sdp_list_append; + sdp_list_free; + sdp_list_insert_sorted; + sdp_list_remove; + sdp_pattern_add_uuid; + sdp_pattern_add_uuidseq; + sdp_process; + sdp_profile_uuid2strn; + sdp_proto_uuid2strn; + sdp_record_alloc; + sdp_record_free; + sdp_record_print; + sdp_record_register; + sdp_record_unregister; + sdp_record_update; + sdp_send_req_w4_rsp; + sdp_seq_alloc; + sdp_seq_alloc_with_length; + sdp_seq_append; + sdp_service_attr_async; + sdp_service_attr_req; + sdp_service_search_async; + sdp_service_search_attr_async; + sdp_service_search_attr_req; + sdp_service_search_req; + sdp_set_access_protos; + sdp_set_add_access_protos; + sdp_set_attrid; + sdp_set_group_id; + sdp_set_info_attr; + sdp_set_lang_attr; + sdp_set_notify; + sdp_set_profile_descs; + sdp_set_seq_len; + sdp_set_service_id; + sdp_set_supp_feat; + sdp_set_url_attr; + sdp_set_uuidseq_attr; + sdp_svclass_uuid2strn; + sdp_uuid128_cmp; + sdp_uuid128_create; + sdp_uuid128_to_uuid; + sdp_uuid16_cmp; + sdp_uuid16_create; + sdp_uuid16_to_uuid128; + sdp_uuid2strn; + sdp_uuid32_create; + sdp_uuid32_to_uuid128; + sdp_uuid_cmp; + sdp_uuid_extract; + sdp_uuid_to_proto; + sdp_uuid_to_uuid128; + str2ba; + strtoba; + + /* Implemented by PureSwift but not exported by the reference (10) */ + bt_string_to_uuid; + bt_uuid128_create; + bt_uuid16_cmp; + bt_uuid16_create; + bt_uuid32_create; + bt_uuid_cmp; + bt_uuid_strcmp; + bt_uuid_to_le; + bt_uuid_to_string; + bt_uuid_to_uuid128; + +local: + *; +}; From e6e57b8d16983d6d57bb75e3aba2dec8396d9d4e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:26 -0400 Subject: [PATCH 24/73] Add the flat exported symbol list for check-exports --- scripts/exported.txt | 232 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 scripts/exported.txt diff --git a/scripts/exported.txt b/scripts/exported.txt new file mode 100644 index 0000000..a1e3552 --- /dev/null +++ b/scripts/exported.txt @@ -0,0 +1,232 @@ +# Generated by scripts/gen_symbols.py — do not edit. +# +# Every symbol cmake/libbluetooth.map exports, flat, for +# scripts/check-exports.sh. +ba2oui +ba2str +ba2strlc +bachk +bafprintf +baprintf +basnprintf +basprintf +baswap +batostr +bt_compidtostr +bt_error +bt_free +bt_malloc +bt_malloc0 +bt_string_to_uuid +bt_uuid128_create +bt_uuid16_cmp +bt_uuid16_create +bt_uuid32_create +bt_uuid_cmp +bt_uuid_strcmp +bt_uuid_to_le +bt_uuid_to_string +bt_uuid_to_uuid128 +hci_authenticate_link +hci_bustostr +hci_change_link_key +hci_close_dev +hci_cmdtostr +hci_commandstostr +hci_create_connection +hci_delete_stored_link_key +hci_devba +hci_devid +hci_devinfo +hci_dflagstostr +hci_disconnect +hci_dtypetostr +hci_encrypt_link +hci_exit_park_mode +hci_for_each_dev +hci_get_route +hci_inquiry +hci_le_add_resolving_list +hci_le_add_white_list +hci_le_clear_resolving_list +hci_le_clear_white_list +hci_le_conn_update +hci_le_create_conn +hci_le_read_remote_features +hci_le_read_resolving_list_size +hci_le_read_white_list_size +hci_le_rm_resolving_list +hci_le_rm_white_list +hci_le_set_address_resolution_enable +hci_le_set_advertise_enable +hci_le_set_scan_enable +hci_le_set_scan_parameters +hci_lmtostr +hci_lptostr +hci_open_dev +hci_park_mode +hci_ptypetostr +hci_read_afh_map +hci_read_afh_mode +hci_read_bd_addr +hci_read_class_of_dev +hci_read_clock +hci_read_clock_offset +hci_read_current_iac_lap +hci_read_ext_inquiry_response +hci_read_inq_response_tx_power_level +hci_read_inquiry_mode +hci_read_inquiry_scan_type +hci_read_inquiry_transmit_power_level +hci_read_link_policy +hci_read_link_quality +hci_read_link_supervision_timeout +hci_read_local_commands +hci_read_local_ext_features +hci_read_local_features +hci_read_local_name +hci_read_local_oob_data +hci_read_local_version +hci_read_remote_ext_features +hci_read_remote_features +hci_read_remote_name +hci_read_remote_name_cancel +hci_read_remote_name_with_clock_offset +hci_read_remote_version +hci_read_rssi +hci_read_simple_pairing_mode +hci_read_stored_link_key +hci_read_transmit_power_level +hci_read_voice_setting +hci_scoptypetostr +hci_send_cmd +hci_send_req +hci_set_afh_classification +hci_strtolm +hci_strtolp +hci_strtoptype +hci_strtoscoptype +hci_strtover +hci_switch_role +hci_typetostr +hci_vertostr +hci_write_afh_mode +hci_write_class_of_dev +hci_write_current_iac_lap +hci_write_ext_inquiry_response +hci_write_inquiry_mode +hci_write_inquiry_scan_type +hci_write_inquiry_transmit_power_level +hci_write_link_policy +hci_write_link_supervision_timeout +hci_write_local_name +hci_write_simple_pairing_mode +hci_write_stored_link_key +hci_write_voice_setting +lmp_featurestostr +lmp_strtover +lmp_vertostr +pal_strtover +pal_vertostr +sdp_add_lang_attr +sdp_append_to_buf +sdp_append_to_pdu +sdp_attr_add +sdp_attr_add_new +sdp_attr_remove +sdp_attr_replace +sdp_attrid_comp_func +sdp_close +sdp_connect +sdp_copy_record +sdp_create +sdp_data_alloc +sdp_data_alloc_with_length +sdp_data_free +sdp_data_get +sdp_device_record_register +sdp_device_record_register_binary +sdp_device_record_unregister +sdp_device_record_unregister_binary +sdp_device_record_update +sdp_device_record_update_binary +sdp_extract_attr +sdp_extract_pdu +sdp_extract_seqtype +sdp_gen_pdu +sdp_gen_record_pdu +sdp_gen_tid +sdp_general_inquiry +sdp_get_access_protos +sdp_get_add_access_protos +sdp_get_database_state +sdp_get_error +sdp_get_group_id +sdp_get_int_attr +sdp_get_lang_attr +sdp_get_profile_descs +sdp_get_proto_desc +sdp_get_proto_port +sdp_get_record_state +sdp_get_server_ver +sdp_get_service_avail +sdp_get_service_id +sdp_get_service_ttl +sdp_get_socket +sdp_get_string_attr +sdp_get_supp_feat +sdp_get_uuidseq_attr +sdp_list_append +sdp_list_free +sdp_list_insert_sorted +sdp_list_remove +sdp_pattern_add_uuid +sdp_pattern_add_uuidseq +sdp_process +sdp_profile_uuid2strn +sdp_proto_uuid2strn +sdp_record_alloc +sdp_record_free +sdp_record_print +sdp_record_register +sdp_record_unregister +sdp_record_update +sdp_send_req_w4_rsp +sdp_seq_alloc +sdp_seq_alloc_with_length +sdp_seq_append +sdp_service_attr_async +sdp_service_attr_req +sdp_service_search_async +sdp_service_search_attr_async +sdp_service_search_attr_req +sdp_service_search_req +sdp_set_access_protos +sdp_set_add_access_protos +sdp_set_attrid +sdp_set_group_id +sdp_set_info_attr +sdp_set_lang_attr +sdp_set_notify +sdp_set_profile_descs +sdp_set_seq_len +sdp_set_service_id +sdp_set_supp_feat +sdp_set_url_attr +sdp_set_uuidseq_attr +sdp_svclass_uuid2strn +sdp_uuid128_cmp +sdp_uuid128_create +sdp_uuid128_to_uuid +sdp_uuid16_cmp +sdp_uuid16_create +sdp_uuid16_to_uuid128 +sdp_uuid2strn +sdp_uuid32_create +sdp_uuid32_to_uuid128 +sdp_uuid_cmp +sdp_uuid_extract +sdp_uuid_to_proto +sdp_uuid_to_uuid128 +str2ba +strtoba From aa0ae21b7f66b8535c26da374fb32aa94ffda715 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:26 -0400 Subject: [PATCH 25/73] Add a script to assert exported symbols against a list --- scripts/check-exports.sh | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100755 scripts/check-exports.sh diff --git a/scripts/check-exports.sh b/scripts/check-exports.sh new file mode 100755 index 0000000..4c72fd9 --- /dev/null +++ b/scripts/check-exports.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# +# Assert a library's exported symbols against an expected list. +# +# Both directions fail: a symbol in the list but not in the library +# (something was never implemented, or the version script does not +# match), and a symbol in the library but not in the list (an internal +# detail leaked into the ABI surface). +# +# Usage: Scripts/check-exports.sh [symbols.txt] +# +set -euo pipefail + +LIBRARY="${1:?usage: check-exports.sh [symbols.txt]}" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +EXPECTED_FILE="${2:-${ROOT}/scripts/exported.txt}" + +if [[ ! -f "${LIBRARY}" ]]; then + echo "error: ${LIBRARY} not found" >&2 + exit 1 +fi + +if [[ ! -f "${EXPECTED_FILE}" ]]; then + echo "error: ${EXPECTED_FILE} not found" >&2 + exit 1 +fi + +work="$(mktemp -d)" +trap 'rm -rf "${work}"' EXIT + +# Defined, exported functions and data — not undefined imports. +nm --dynamic --defined-only --format=posix "${LIBRARY}" \ + | awk '$2 ~ /^[TDBRWi]$/ { print $1 }' \ + | sed 's/@.*//' \ + | sort -u > "${work}/actual" + +grep -vE '^\s*(#|$)' "${EXPECTED_FILE}" | sort -u > "${work}/expected" + +comm -13 "${work}/actual" "${work}/expected" > "${work}/missing" +comm -23 "${work}/actual" "${work}/expected" > "${work}/extra" + +status=0 + +if [[ -s "${work}/missing" ]]; then + echo "error: $(wc -l < "${work}/missing") expected symbol(s) not exported by ${LIBRARY}:" >&2 + sed 's/^/ - /' "${work}/missing" >&2 + status=1 +fi + +if [[ -s "${work}/extra" ]]; then + echo "error: $(wc -l < "${work}/extra") unexpected symbol(s) exported by ${LIBRARY}:" >&2 + sed 's/^/ + /' "${work}/extra" >&2 + status=1 +fi + +if [[ "${status}" -eq 0 ]]; then + echo "exports: $(wc -l < "${work}/expected") symbols match $(basename "${EXPECTED_FILE}")" +fi + +exit "${status}" From 7cb042ee7081c755a647e0a6b77981002ac2c75b Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:33 -0400 Subject: [PATCH 26/73] Add the CMake shared-library placeholder translation unit --- cmake/empty.c | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 cmake/empty.c diff --git a/cmake/empty.c b/cmake/empty.c new file mode 100644 index 0000000..782b15f --- /dev/null +++ b/cmake/empty.c @@ -0,0 +1,9 @@ +/* + * Placeholder translation unit. + * + * CMake requires a shared library target to have at least one source of + * its own; every exported symbol actually arrives from the Swift and C + * static archives, linked whole (see CMakeLists.txt). + */ + +/* Deliberately empty. */ From a5c0a9480e3deef6a5b028a18c761cbe8da1fbf0 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:33 -0400 Subject: [PATCH 27/73] Add the pkg-config template for libbluetooth --- cmake/bluez.pc.in | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 cmake/bluez.pc.in diff --git a/cmake/bluez.pc.in b/cmake/bluez.pc.in new file mode 100644 index 0000000..8b1da7a --- /dev/null +++ b/cmake/bluez.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: bluez +Description: Bluetooth protocol stack for Linux +Version: @LIBBLUETOOTH_VERSION@ +Libs: -L${libdir} -lbluetooth +Cflags: -I${includedir} From 8c5e9e97c0a0fdf173a39cd7f7f54eb543abb049 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:33 -0400 Subject: [PATCH 28/73] Add the CMake build for libbluetooth.so.3 --- CMakeLists.txt | 245 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ad6080d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,245 @@ +## +## CMakeLists.txt +## BluetoothLinux +## +## Builds `libbluetooth.so.3` — a drop-in replacement for the shared +## library BlueZ installs, implemented in Swift. +## +## SwiftPM drives development and the test suites; CMake exists because +## soname, symbol versioning and install name are not expressible in +## `Package.swift`. +## +## Phase 0's deliverable is a library that loads, resolves every one of +## the 218 symbols the reference exports, and aborts loudly on every +## call that is not implemented yet (see scripts/gen_stubs.py). Each +## subsequent phase moves names from `scripts/symbols.txt` into +## `scripts/implemented.txt` and the stubs disappear. +## +## Replacing this library cannot break the Bluetooth stack itself: +## nothing in the `bluez` package links it — bluetoothd, bluetoothctl, +## btmon, hciconfig and sdptool all statically link +## libbluetooth-internal.a. It exists solely for third-party consumers. +## The corollary is that there is no "run bluetoothctl and see" smoke +## test, which is why Conformance/ matters. +## +## The Ninja generator is required — CMake does not support the Swift +## language with Makefiles. +## +## Usage: +## cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \ +## -DBLUETOOTH_PACKAGE_PATH=../Bluetooth +## cmake --build build +## cmake --build build --target check-exports +## cmake --install build --prefix /usr/local +## + +cmake_minimum_required(VERSION 3.26) + +if(NOT CMAKE_GENERATOR MATCHES "Ninja") + message(FATAL_ERROR + "The Ninja generator is required for Swift targets; " + "re-run with -G Ninja.") +endif() + +project(BluetoothLinuxABI + VERSION 3.19.15 + DESCRIPTION "Swift implementation of the BlueZ libbluetooth ABI" + LANGUAGES C Swift) + +# Checked after project(), which is what sets CMAKE_SYSTEM_NAME. +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + message(FATAL_ERROR + "libbluetooth.so.3 is a Linux library; this build is only " + "supported on Linux.") +endif() + +# --------------------------------------------------------------------- +# Options +# --------------------------------------------------------------------- + +# Debian 5.85-4 ships libbluetooth.so.3.19.15 (libtool -version-info +# 22:15:19); master is heading to .3.19.16. The soname is what consumers +# actually bind to. +set(LIBBLUETOOTH_SOVERSION 3) +set(LIBBLUETOOTH_VERSION 3.19.15) + +option(BLUETOOTH_ABI_STATIC_STDLIB + "Statically link the Swift runtime into the shared library" OFF) + +option(BLUETOOTH_ABI_INSTALL_HEADERS + "Install the vendored BlueZ headers under /include/bluetooth" ON) + +# The Swift implementations of the non-socket half live in +# PureSwift/Bluetooth. Point this at a checkout of it. +set(BLUETOOTH_PACKAGE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../Bluetooth" + CACHE PATH "Path to a PureSwift/Bluetooth checkout") + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) +endif() + +include(GNUInstallDirs) + +find_package(Python3 COMPONENTS Interpreter REQUIRED) + +# --------------------------------------------------------------------- +# Generated sources +# --------------------------------------------------------------------- +# +# Both the stub table and the version script are derived from +# scripts/symbols.txt, so the export surface has exactly one source of +# truth. They are committed as well as generated, so that a plain +# `swift build` sees the same files. + +set(LIBBLUETOOTH_STUBS + "${CMAKE_CURRENT_SOURCE_DIR}/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c") +set(LIBBLUETOOTH_VERSION_SCRIPT + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/libbluetooth.map") + +add_custom_command( + OUTPUT "${LIBBLUETOOTH_STUBS}" + COMMAND "${Python3_EXECUTABLE}" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen_stubs.py" + DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen_stubs.py" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/symbols.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/implemented.txt" + COMMENT "Generating unimplemented-symbol stubs" + VERBATIM) + +add_custom_command( + OUTPUT "${LIBBLUETOOTH_VERSION_SCRIPT}" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/exported.txt" + COMMAND "${Python3_EXECUTABLE}" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen_symbols.py" + DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen_symbols.py" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/symbols.txt" + COMMENT "Generating the version script" + VERBATIM) + +# --------------------------------------------------------------------- +# CBluetoothLinuxABI — the C surface +# --------------------------------------------------------------------- +# +# `include/bluetooth/` holds the eleven public headers vendored verbatim +# from BlueZ; they are GPL-2.0-or-later, unlike the rest of this +# repository. See Sources/CBluetoothLinuxABI/README.md. + +add_library(CBluetoothLinuxABI STATIC ${LIBBLUETOOTH_STUBS}) + +target_include_directories(CBluetoothLinuxABI PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/Sources/CBluetoothLinuxABI/include") + +# --------------------------------------------------------------------- +# The non-socket half, from PureSwift/Bluetooth +# --------------------------------------------------------------------- + +if(NOT EXISTS "${BLUETOOTH_PACKAGE_PATH}/CMakeLists.txt") + message(FATAL_ERROR + "PureSwift/Bluetooth not found at ${BLUETOOTH_PACKAGE_PATH}. " + "Set -DBLUETOOTH_PACKAGE_PATH=.") +endif() + +# Its own shared library is not wanted here — this build links the +# static archives into libbluetooth.so.3 instead. +set(BLUETOOTH_ABI_SHARED OFF CACHE BOOL "" FORCE) +set(BLUETOOTH_ABI_INSTALL_HEADERS OFF CACHE BOOL "" FORCE) + +add_subdirectory("${BLUETOOTH_PACKAGE_PATH}" "${CMAKE_CURRENT_BINARY_DIR}/Bluetooth") + +# --------------------------------------------------------------------- +# libbluetooth.so.3 +# --------------------------------------------------------------------- + +add_library(bluetooth3 SHARED cmake/empty.c) + +set_target_properties(bluetooth3 PROPERTIES + OUTPUT_NAME bluetooth + SOVERSION ${LIBBLUETOOTH_SOVERSION} + VERSION ${LIBBLUETOOTH_VERSION} + # The link is driven by swiftc, and `bluetooth3` would collide with + # the `Bluetooth` module; name the vestigial module explicitly. + Swift_MODULE_NAME libbluetooth_abi) + +# Every entry point is referenced only from outside, so the archives +# holding them must be linked whole or the linker drops the lot. +# +# The archives are named explicitly through `LINKER:` (which expands to +# `-Xlinker` here) rather than via `$`: +# swiftc's driver silently discards bare `.a` arguments, so the result +# would be a shared library with no symbols in it. +target_link_options(bluetooth3 PRIVATE + "LINKER:--whole-archive" + "LINKER:$" + "LINKER:$" + "LINKER:$" + "LINKER:--no-whole-archive") + +add_dependencies(bluetooth3 BluetoothABI CBluetooth CBluetoothLinuxABI) + +target_link_libraries(bluetooth3 PRIVATE Bluetooth) + +# Pin the export list. Upstream exports everything and carries no +# version script; see scripts/gen_symbols.py for why we do not, and for +# the audit that owes before this list is final. +target_link_options(bluetooth3 PRIVATE + "LINKER:--version-script=${LIBBLUETOOTH_VERSION_SCRIPT}") +set_target_properties(bluetooth3 PROPERTIES + LINK_DEPENDS "${LIBBLUETOOTH_VERSION_SCRIPT}") + +add_custom_target(generate-version-script + DEPENDS "${LIBBLUETOOTH_VERSION_SCRIPT}") +add_dependencies(bluetooth3 generate-version-script) + +if(BLUETOOTH_ABI_STATIC_STDLIB) + # Any process loading libbluetooth.so.3 otherwise pulls in + # libswiftCore. Measure the result against the 99 KB reference + # package before making this the default. + target_link_options(bluetooth3 PRIVATE "-static-stdlib") +endif() + +# --------------------------------------------------------------------- +# Install +# --------------------------------------------------------------------- + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/bluez.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/bluez.pc" + @ONLY) + +install(TARGETS bluetooth3 + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/bluez.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + +if(BLUETOOTH_ABI_INSTALL_HEADERS) + # The eleven headers the reference package installs. + file(GLOB LIBBLUETOOTH_HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/Sources/CBluetoothLinuxABI/include/bluetooth/*.h") + install(FILES ${LIBBLUETOOTH_HEADERS} + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/bluetooth") +endif() + +# --------------------------------------------------------------------- +# Export verification +# --------------------------------------------------------------------- +# +# Diffs the built library's dynamic symbol table against the generated +# list. Both a missing and an extra symbol fail, so the export surface +# cannot drift silently — this is the check that makes "the library +# still resolves everything the reference did" an assertion rather than +# a hope. + +add_custom_target(check-exports + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/scripts/check-exports.sh" + "$" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/exported.txt" + DEPENDS bluetooth3 generate-version-script + COMMENT "Checking exported symbols against scripts/exported.txt" + VERBATIM) From 0d08c6cd2f5c6c8e5d67471c4e6b41fd1bdee421 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:33 -0400 Subject: [PATCH 29/73] Add the opt-in CBluetoothLinuxABI target --- Package.swift | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Package.swift b/Package.swift index 4e4b463..1c022b4 100644 --- a/Package.swift +++ b/Package.swift @@ -182,3 +182,25 @@ if buildDocs { ] } #endif + +// C ABI (libbluetooth.so.3 replacement) +// +// Off by default so the ordinary Swift build stays unaffected. Enable +// with `SWIFTPM_BLUETOOTH_CABI=1` to build the C surface — the vendored +// BlueZ headers plus the generated stub table for every symbol not +// implemented yet (see scripts/gen_stubs.py). +// +// SwiftPM builds the sources; the installable `libbluetooth.so.3` +// itself is built by CMake, which is the only one of the two that can +// set a soname, a version and an export list. +if ProcessInfo.processInfo.environment["SWIFTPM_BLUETOOTH_CABI"] == "1" { + package.targets += [ + .target( + name: "CBluetoothLinuxABI", + exclude: [ + "README.md", + "include/bluetooth/LICENSE" + ] + ) + ] +} From 6db32967c6a314e2c3007437647cd06067ad4f7a Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 12:26:33 -0400 Subject: [PATCH 30/73] Document where the differential conformance driver lives --- Conformance/README.md | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Conformance/README.md diff --git a/Conformance/README.md b/Conformance/README.md new file mode 100644 index 0000000..2b6422b --- /dev/null +++ b/Conformance/README.md @@ -0,0 +1,52 @@ +# Conformance + +Differential conformance for `libbluetooth.so.3`: C programs compiled +twice — once against the reference library, once against ours — with the +outputs diffed and accepted deltas recorded. + +Because no BlueZ binary links the shared library (`bluetoothd`, +`bluetoothctl`, `btmon`, `hciconfig` and `sdptool` all statically link +`libbluetooth-internal.a`), "nothing crashed" tells us nothing. These +programs are the only signal that the replacement behaves like the +original. + +## Phase 1 — done, and it lives in PureSwift/Bluetooth + +The `bluetooth.c` and `bt_uuid_*` families are implemented there, so +their conformance harness is there too: `Conformance/compare.sh` in the +Bluetooth checkout, with `conformance_address.c` and +`conformance_uuid.c`. + +It can be pointed at the library this repository builds: + +``` +cmake --build .build/cmake +BLUEZ_SOURCE= \ + ../Bluetooth/Conformance/compare.sh .build/cmake/libbluetooth.so.3.19.15 +``` + +## Phases 2–4 — to be added here + +One driver per symbol family, following the same shape: + +| Driver | Covers | Phase | +|---|---|---| +| `conformance_hci_strings.c` | `hci_*tostr`, `lmp_*`, `pal_*` | 2 | +| `conformance_hci.c` | device and command wrappers | 3 | +| `conformance_sdp_codec.c` | `sdp_gen_pdu` / `sdp_extract_pdu` / `sdp_extract_attr` | 4a | +| `conformance_sdp_session.c` | session, registration, async requests | 4b | + +Two cheaper sources of coverage come first, though: + +- **BlueZ's own unit tests, reused directly.** `unit/test-uuid.c`, + `unit/test-lib.c` and `unit/test-sdp.c` link + `libbluetooth-internal.la` today; pointing them at our library instead + is free coverage for phases 1 and 4. +- **Fuzzing the parsers.** `bt_string_to_uuid`, `str2ba`, `bachk` and + `sdp_extract_pdu` against random and semi-structured input, comparing + both implementations byte for byte. `sdp_extract_pdu` is the one place + arbitrary remote bytes reach the library. + +Then an `LD_PRELOAD` substitution run against a real external consumer, +once one is identified — `bluez-cups` and the Python bindings are the +obvious candidates. From dc7f015894ba91a2bc7091882583e7022e2dd05f Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 15:24:20 -0400 Subject: [PATCH 31/73] Add script to generate HCI lookup tables from BlueZ hci.c Parses hci_map tables and header #define/enum values directly out of the reference source and emits Swift literal arrays, avoiding manual transcription of the 232-entry command map and 8x9 feature table. --- scripts/generate-hci-tables.py | 185 +++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100755 scripts/generate-hci-tables.py diff --git a/scripts/generate-hci-tables.py b/scripts/generate-hci-tables.py new file mode 100755 index 0000000..1d9d256 --- /dev/null +++ b/scripts/generate-hci-tables.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +Generate Sources/BluetoothLinuxABI/gen/HCITables.swift from BlueZ's +lib/bluetooth/hci.c and lib/bluetooth/hci.h. + +The `hci_*tostr`/`hci_strto*` family (plus `lmp_*`/`pal_*`) are all +table lookups over BlueZ's `hci_map` arrays — name/bit-or-value pairs. +Hand-transcribing the largest of these (`commands_map`, 232 entries; +`lmp_features_map`, 8x9 entries) risks a silent transcription error +that would only surface as a wrong string for one specific bit. Since +every entry is mechanical (a literal name and a `#define`d value), +generating them from the vendored source is both faster and exact. + +Usage: + scripts/generate-hci-tables.py /lib/bluetooth/hci.c + +`hci.c` itself is not vendored into this repository — only the headers +are (see Sources/CBluetoothLinuxABI/README.md) — so this script takes +the reference source as an argument rather than reading a shipped copy. +Re-run after re-vendoring a newer hci.h from the same BlueZ release and +commit the generated result. +""" + +import os +import re +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +HCI_H = os.path.join(ROOT, "Sources", "CBluetoothLinuxABI", "include", "bluetooth", "hci.h") +OUTPUT = os.path.join(ROOT, "Sources", "BluetoothLinuxABI", "gen", "HCITables.swift") + +HEADER = '''// +// HCITables.swift +// BluetoothLinux +// +// Generated by scripts/generate-hci-tables.py — do not edit. +// +// Source: BlueZ lib/bluetooth/hci.c's `hci_map` tables, with `#define` +// values resolved against lib/bluetooth/hci.h. +// + +''' + + +def load_defines(): + text = open(HCI_H, encoding="utf-8").read() + defines = {} + + # `#define NAME value` where value is a simple integer literal + # (decimal or hex), skipping function-like macros and multi-token + # expressions. + for match in re.finditer( + r"^#define\s+([A-Za-z_][A-Za-z0-9_]*)\s+(0[xX][0-9A-Fa-f]+|\d+)\s*$", + text, + re.MULTILINE, + ): + name, value = match.groups() + defines[name] = int(value, 0) + + # `enum { A, B, C };` — unnamed enums with no explicit values, used + # for the HCI device flag bit positions (hci_test_bit indices, not + # bitmasks). Comments and blank lines inside the braces are allowed. + for match in re.finditer(r"enum\s*\{([^}]*)\}\s*;", text, re.DOTALL): + body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.DOTALL) + names = [n.strip() for n in body.split(",")] + value = 0 + for name in names: + if not name or not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name): + continue + defines.setdefault(name, value) + value += 1 + + return defines + + +def resolve(token, defines): + token = token.strip() + if re.fullmatch(r"0[xX][0-9A-Fa-f]+|\d+", token): + return int(token, 0) + if token in defines: + return defines[token] + raise ValueError("cannot resolve %r" % token) + + +def extract_map(source, name): + match = re.search( + r"static const hci_map %s\[\] = \{(.*?)\n\};" % re.escape(name), + source, + re.DOTALL, + ) + if not match: + raise ValueError("map not found: %s" % name) + body = match.group(1) + entries = re.findall(r'\{\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^}]+?)\s*\}', body) + return entries + + +def extract_features_map(source): + match = re.search( + r"static const hci_map lmp_features_map\[8\]\[9\] = \{(.*?)\n\};", + source, + re.DOTALL, + ) + body = match.group(1) + bytes_ = re.findall(r"\{(.*?)\}(?:,|\s*$)", body, re.DOTALL) + # The outer split also grabs each byte's own `{ ... }` entries as + # nested matches; re-split more carefully by top-level byte blocks. + byte_blocks = re.findall(r"\{\s*/\* Byte \d+ \*/(.*?)\n\t\},?", body, re.DOTALL) + rows = [] + for block in byte_blocks: + entries = re.findall(r'\{\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^}]+?)\s*\}', block) + rows.append(entries) + return rows + + +def swift_escape(text): + return text.replace("\\", "\\\\").replace('"', '\\"') + + +def format_table(name, entries, defines, comment): + lines = ["/// %s" % comment] + lines.append("internal let %s: [(name: String, value: UInt32)] = [" % name) + for s, v in entries: + value = resolve(v, defines) + lines.append(' ("%s", 0x%x),' % (swift_escape(s), value)) + lines.append("]") + return "\n".join(lines) + "\n\n" + + +def main(): + if len(sys.argv) != 2: + print("usage: generate-hci-tables.py ", file=sys.stderr) + return 1 + source = open(sys.argv[1], encoding="utf-8").read() + defines = load_defines() + + chunks = [HEADER] + + tables = [ + ("hciDeviceFlagsMap", "dev_flags_map", "HCI device flags (bit tests)."), + ("hciPacketTypeMap", "pkt_type_map", "ACL packet types (bitmask)."), + ("hciSCOPacketTypeMap", "sco_ptype_map", "SCO packet types (bitmask)."), + ("hciLinkPolicyMap", "link_policy_map", "Link policy settings (bitmask)."), + ("hciLinkModeMap", "link_mode_map", "Link mode settings (bitmask)."), + ("hciVersionMap", "ver_map", "Core specification version (exact match)."), + ("hciPALVersionMap", "pal_map", "802.11 PAL version (exact match)."), + ] + for swift_name, c_name, comment in tables: + entries = extract_map(source, c_name) + chunks.append(format_table(swift_name, entries, defines, comment)) + + commands = extract_map(source, "commands_map") + chunks.append( + format_table( + "hciCommandsMap", + commands, + defines, + "Supported Commands bit -> name (%d entries)." % len(commands), + ) + ) + + feature_rows = extract_features_map(source) + chunks.append("/// LMP Features page 0, one array per byte (8 bytes x up to 9 bits).\n") + chunks.append("internal let lmpFeaturesMap: [[(name: String, value: UInt8)]] = [\n") + for row in feature_rows: + chunks.append(" [\n") + for s, v in row: + value = resolve(v, defines) + chunks.append(' ("%s", 0x%02x),\n' % (swift_escape(s), value)) + chunks.append(" ],\n") + chunks.append("]\n") + + os.makedirs(os.path.dirname(OUTPUT), exist_ok=True) + with open(OUTPUT, "w", encoding="utf-8") as handle: + handle.write("".join(chunks)) + + print( + "wrote %s (%d command entries, %d feature bytes)" + % (os.path.relpath(OUTPUT, ROOT), len(commands), len(feature_rows)) + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From feae81c4ced67cab6b93844166818aedaedef537 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 15:24:25 -0400 Subject: [PATCH 32/73] Add generated HCI lookup tables Bus, device flag, packet type, link policy/mode, version, and command name tables used by the HCI string converter family, generated from BlueZ's hci.c via scripts/generate-hci-tables.py. --- Sources/BluetoothLinuxABI/gen/HCITables.swift | 416 ++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/gen/HCITables.swift diff --git a/Sources/BluetoothLinuxABI/gen/HCITables.swift b/Sources/BluetoothLinuxABI/gen/HCITables.swift new file mode 100644 index 0000000..e843ab6 --- /dev/null +++ b/Sources/BluetoothLinuxABI/gen/HCITables.swift @@ -0,0 +1,416 @@ +// +// HCITables.swift +// BluetoothLinux +// +// Generated by scripts/generate-hci-tables.py — do not edit. +// +// Source: BlueZ lib/bluetooth/hci.c's `hci_map` tables, with `#define` +// values resolved against lib/bluetooth/hci.h. +// + +/// HCI device flags (bit tests). +internal let hciDeviceFlagsMap: [(name: String, value: UInt32)] = [ + ("UP", 0x0), + ("INIT", 0x1), + ("RUNNING", 0x2), + ("RAW", 0x8), + ("PSCAN", 0x3), + ("ISCAN", 0x4), + ("INQUIRY", 0x7), + ("AUTH", 0x5), + ("ENCRYPT", 0x6), +] + +/// ACL packet types (bitmask). +internal let hciPacketTypeMap: [(name: String, value: UInt32)] = [ + ("DM1", 0x8), + ("DM3", 0x400), + ("DM5", 0x4000), + ("DH1", 0x10), + ("DH3", 0x800), + ("DH5", 0x8000), + ("HV1", 0x20), + ("HV2", 0x40), + ("HV3", 0x80), + ("2-DH1", 0x2), + ("2-DH3", 0x100), + ("2-DH5", 0x1000), + ("3-DH1", 0x4), + ("3-DH3", 0x200), + ("3-DH5", 0x2000), +] + +/// SCO packet types (bitmask). +internal let hciSCOPacketTypeMap: [(name: String, value: UInt32)] = [ + ("HV1", 0x1), + ("HV2", 0x2), + ("HV3", 0x4), + ("EV3", 0x8), + ("EV4", 0x10), + ("EV5", 0x20), + ("2-EV3", 0x40), + ("2-EV5", 0x100), + ("3-EV3", 0x80), + ("3-EV5", 0x200), +] + +/// Link policy settings (bitmask). +internal let hciLinkPolicyMap: [(name: String, value: UInt32)] = [ + ("NONE", 0x0), + ("RSWITCH", 0x1), + ("HOLD", 0x2), + ("SNIFF", 0x4), + ("PARK", 0x8), +] + +/// Link mode settings (bitmask). +internal let hciLinkModeMap: [(name: String, value: UInt32)] = [ + ("NONE", 0x0), + ("ACCEPT", 0x8000), + ("CENTRAL", 0x1), + ("AUTH", 0x2), + ("ENCRYPT", 0x4), + ("TRUSTED", 0x8), + ("RELIABLE", 0x10), + ("SECURE", 0x20), +] + +/// Core specification version (exact match). +internal let hciVersionMap: [(name: String, value: UInt32)] = [ + ("1.0b", 0x0), + ("1.1", 0x1), + ("1.2", 0x2), + ("2.0", 0x3), + ("2.1", 0x4), + ("3.0", 0x5), + ("4.0", 0x6), + ("4.1", 0x7), + ("4.2", 0x8), + ("5.0", 0x9), + ("5.1", 0xa), + ("5.2", 0xb), + ("5.3", 0xc), + ("5.4", 0xd), +] + +/// 802.11 PAL version (exact match). +internal let hciPALVersionMap: [(name: String, value: UInt32)] = [ + ("3.0", 0x1), +] + +/// Supported Commands bit -> name (232 entries). +internal let hciCommandsMap: [(name: String, value: UInt32)] = [ + ("Inquiry", 0x0), + ("Inquiry Cancel", 0x1), + ("Periodic Inquiry Mode", 0x2), + ("Exit Periodic Inquiry Mode", 0x3), + ("Create Connection", 0x4), + ("Disconnect", 0x5), + ("Add SCO Connection", 0x6), + ("Cancel Create Connection", 0x7), + ("Accept Connection Request", 0x8), + ("Reject Connection Request", 0x9), + ("Link Key Request Reply", 0xa), + ("Link Key Request Negative Reply", 0xb), + ("PIN Code Request Reply", 0xc), + ("PIN Code Request Negative Reply", 0xd), + ("Change Connection Packet Type", 0xe), + ("Authentication Requested", 0xf), + ("Set Connection Encryption", 0x10), + ("Change Connection Link Key", 0x11), + ("Temporary Link Key", 0x12), + ("Remote Name Request", 0x13), + ("Cancel Remote Name Request", 0x14), + ("Read Remote Supported Features", 0x15), + ("Read Remote Extended Features", 0x16), + ("Read Remote Version Information", 0x17), + ("Read Clock Offset", 0x18), + ("Read LMP Handle", 0x19), + ("Reserved", 0x1a), + ("Reserved", 0x1b), + ("Reserved", 0x1c), + ("Reserved", 0x1d), + ("Reserved", 0x1e), + ("Reserved", 0x1f), + ("Reserved", 0x20), + ("Hold Mode", 0x21), + ("Sniff Mode", 0x22), + ("Exit Sniff Mode", 0x23), + ("Park State", 0x24), + ("Exit Park State", 0x25), + ("QoS Setup", 0x26), + ("Role Discovery", 0x27), + ("Switch Role", 0x28), + ("Read Link Policy Settings", 0x29), + ("Write Link Policy Settings", 0x2a), + ("Read Default Link Policy Settings", 0x2b), + ("Write Default Link Policy Settings", 0x2c), + ("Flow Specification", 0x2d), + ("Set Event Mask", 0x2e), + ("Reset", 0x2f), + ("Set Event Filter", 0x30), + ("Flush", 0x31), + ("Read PIN Type", 0x32), + ("Write PIN Type", 0x33), + ("Create New Unit Key", 0x34), + ("Read Stored Link Key", 0x35), + ("Write Stored Link Key", 0x36), + ("Delete Stored Link Key", 0x37), + ("Write Local Name", 0x38), + ("Read Local Name", 0x39), + ("Read Connection Accept Timeout", 0x3a), + ("Write Connection Accept Timeout", 0x3b), + ("Read Page Timeout", 0x3c), + ("Write Page Timeout", 0x3d), + ("Read Scan Enable", 0x3e), + ("Write Scan Enable", 0x3f), + ("Read Page Scan Activity", 0x40), + ("Write Page Scan Activity", 0x41), + ("Read Inquiry Scan Activity", 0x42), + ("Write Inquiry Scan Activity", 0x43), + ("Read Authentication Enable", 0x44), + ("Write Authentication Enable", 0x45), + ("Read Encryption Mode", 0x46), + ("Write Encryption Mode", 0x47), + ("Read Class Of Device", 0x48), + ("Write Class Of Device", 0x49), + ("Read Voice Setting", 0x4a), + ("Write Voice Setting", 0x4b), + ("Read Automatic Flush Timeout", 0x4c), + ("Write Automatic Flush Timeout", 0x4d), + ("Read Num Broadcast Retransmissions", 0x4e), + ("Write Num Broadcast Retransmissions", 0x4f), + ("Read Hold Mode Activity", 0x50), + ("Write Hold Mode Activity", 0x51), + ("Read Transmit Power Level", 0x52), + ("Read Synchronous Flow Control Enable", 0x53), + ("Write Synchronous Flow Control Enable", 0x54), + ("Set Host Controller To Host Flow Control", 0x55), + ("Host Buffer Size", 0x56), + ("Host Number Of Completed Packets", 0x57), + ("Read Link Supervision Timeout", 0x58), + ("Write Link Supervision Timeout", 0x59), + ("Read Number of Supported IAC", 0x5a), + ("Read Current IAC LAP", 0x5b), + ("Write Current IAC LAP", 0x5c), + ("Read Page Scan Period Mode", 0x5d), + ("Write Page Scan Period Mode", 0x5e), + ("Read Page Scan Mode", 0x5f), + ("Write Page Scan Mode", 0x60), + ("Set AFH Channel Classification", 0x61), + ("Reserved", 0x62), + ("Reserved", 0x63), + ("Read Inquiry Scan Type", 0x64), + ("Write Inquiry Scan Type", 0x65), + ("Read Inquiry Mode", 0x66), + ("Write Inquiry Mode", 0x67), + ("Read Page Scan Type", 0x68), + ("Write Page Scan Type", 0x69), + ("Read AFH Channel Assessment Mode", 0x6a), + ("Write AFH Channel Assessment Mode", 0x6b), + ("Reserved", 0x6c), + ("Reserved", 0x6d), + ("Reserved", 0x6e), + ("Reserved", 0x6f), + ("Reserved", 0x70), + ("Reserved", 0x71), + ("Reserved", 0x72), + ("Read Local Version Information", 0x73), + ("Read Local Supported Commands", 0x74), + ("Read Local Supported Features", 0x75), + ("Read Local Extended Features", 0x76), + ("Read Buffer Size", 0x77), + ("Read Country Code", 0x78), + ("Read BD ADDR", 0x79), + ("Read Failed Contact Counter", 0x7a), + ("Reset Failed Contact Counter", 0x7b), + ("Get Link Quality", 0x7c), + ("Read RSSI", 0x7d), + ("Read AFH Channel Map", 0x7e), + ("Read BD Clock", 0x7f), + ("Read Loopback Mode", 0x80), + ("Write Loopback Mode", 0x81), + ("Enable Device Under Test Mode", 0x82), + ("Setup Synchronous Connection", 0x83), + ("Accept Synchronous Connection", 0x84), + ("Reject Synchronous Connection", 0x85), + ("Reserved", 0x86), + ("Reserved", 0x87), + ("Read Extended Inquiry Response", 0x88), + ("Write Extended Inquiry Response", 0x89), + ("Refresh Encryption Key", 0x8a), + ("Reserved", 0x8b), + ("Sniff Subrating", 0x8c), + ("Read Simple Pairing Mode", 0x8d), + ("Write Simple Pairing Mode", 0x8e), + ("Read Local OOB Data", 0x8f), + ("Read Inquiry Response Transmit Power Level", 0x90), + ("Write Inquiry Transmit Power Level", 0x91), + ("Read Default Erroneous Data Reporting", 0x92), + ("Write Default Erroneous Data Reporting", 0x93), + ("Reserved", 0x94), + ("Reserved", 0x95), + ("Reserved", 0x96), + ("IO Capability Request Reply", 0x97), + ("User Confirmation Request Reply", 0x98), + ("User Confirmation Request Negative Reply", 0x99), + ("User Passkey Request Reply", 0x9a), + ("User Passkey Request Negative Reply", 0x9b), + ("Remote OOB Data Request Reply", 0x9c), + ("Write Simple Pairing Debug Mode", 0x9d), + ("Enhanced Flush", 0x9e), + ("Remote OOB Data Request Negative Reply", 0x9f), + ("Reserved", 0xa0), + ("Reserved", 0xa1), + ("Send Keypress Notification", 0xa2), + ("IO Capability Request Negative Reply", 0xa3), + ("Read Encryption Key Size", 0xa4), + ("Reserved", 0xa5), + ("Reserved", 0xa6), + ("Reserved", 0xa7), + ("Create Physical Link", 0xa8), + ("Accept Physical Link", 0xa9), + ("Disconnect Physical Link", 0xaa), + ("Create Logical Link", 0xab), + ("Accept Logical Link", 0xac), + ("Disconnect Logical Link", 0xad), + ("Logical Link Cancel", 0xae), + ("Flow Specification Modify", 0xaf), + ("Read Logical Link Accept Timeout", 0xb0), + ("Write Logical Link Accept Timeout", 0xb1), + ("Set Event Mask Page 2", 0xb2), + ("Read Location Data", 0xb3), + ("Write Location Data", 0xb4), + ("Read Local AMP Info", 0xb5), + ("Read Local AMP_ASSOC", 0xb6), + ("Write Remote AMP_ASSOC", 0xb7), + ("Read Flow Control Mode", 0xb8), + ("Write Flow Control Mode", 0xb9), + ("Read Data Block Size", 0xba), + ("Reserved", 0xbb), + ("Reserved", 0xbc), + ("Enable AMP Receiver Reports", 0xbd), + ("AMP Test End", 0xbe), + ("AMP Test Command", 0xbf), + ("Read Enhanced Transmit Power Level", 0xc0), + ("Reserved", 0xc1), + ("Read Best Effort Flush Timeout", 0xc2), + ("Write Best Effort Flush Timeout", 0xc3), + ("Short Range Mode", 0xc4), + ("Read LE Host Support", 0xc5), + ("Write LE Host Support", 0xc6), + ("Reserved", 0xc7), + ("LE Set Event Mask", 0xc8), + ("LE Read Buffer Size", 0xc9), + ("LE Read Local Supported Features", 0xca), + ("Reserved", 0xcb), + ("LE Set Random Address", 0xcc), + ("LE Set Advertising Parameters", 0xcd), + ("LE Read Advertising Channel TX Power", 0xce), + ("LE Set Advertising Data", 0xcf), + ("LE Set Scan Response Data", 0xd0), + ("LE Set Advertise Enable", 0xd1), + ("LE Set Scan Parameters", 0xd2), + ("LE Set Scan Enable", 0xd3), + ("LE Create Connection", 0xd4), + ("LE Create Connection Cancel", 0xd5), + ("LE Read Accept List Size", 0xd6), + ("LE Clear Accept List", 0xd7), + ("LE Add Device To Accept List", 0xd8), + ("LE Remove Device From Accept List", 0xd9), + ("LE Connection Update", 0xda), + ("LE Set Host Channel Classification", 0xdb), + ("LE Read Channel Map", 0xdc), + ("LE Read Remote Used Features", 0xdd), + ("LE Encrypt", 0xde), + ("LE Rand", 0xdf), + ("LE Start Encryption", 0xe0), + ("LE Long Term Key Request Reply", 0xe1), + ("LE Long Term Key Request Negative Reply", 0xe2), + ("LE Read Supported States", 0xe3), + ("LE Receiver Test", 0xe4), + ("LE Transmitter Test", 0xe5), + ("LE Test End", 0xe6), + ("Reserved", 0xe7), +] + +/// LMP Features page 0, one array per byte (8 bytes x up to 9 bits). +internal let lmpFeaturesMap: [[(name: String, value: UInt8)]] = [ + [ + ("<3-slot packets>", 0x01), + ("<5-slot packets>", 0x02), + ("", 0x04), + ("", 0x08), + ("", 0x10), + ("", 0x20), + ("", 0x40), + ("", 0x80), + ], + [ + ("", 0x01), + ("", 0x02), + ("", 0x04), + ("", 0x08), + ("", 0x10), + ("", 0x20), + ("", 0x40), + ("", 0x80), + ], + [ + ("", 0x01), + ("", 0x02), + ("", 0x04), + ("", 0x08), + ("", 0x80), + ], + [ + ("", 0x01), + ("", 0x02), + ("", 0x04), + ("", 0x08), + ("", 0x10), + ("", 0x20), + ("", 0x40), + ("", 0x80), + ], + [ + ("", 0x01), + ("", 0x02), + ("", 0x04), + ("", 0x08), + ("", 0x10), + ("
", 0x20), + ("", 0x40), + ("<3-slot EDR ACL>", 0x80), + ], + [ + ("<5-slot EDR ACL>", 0x01), + ("", 0x02), + ("", 0x04), + ("", 0x08), + ("", 0x10), + ("", 0x20), + ("", 0x40), + ("<3-slot EDR eSCO>", 0x80), + ], + [ + ("", 0x01), + ("", 0x02), + ("", 0x04), + ("", 0x08), + ("", 0x10), + ("", 0x20), + ("", 0x40), + ("", 0x80), + ], + [ + ("", 0x01), + ("", 0x02), + ("", 0x04), + ("", 0x08), + ("", 0x10), + ("", 0x20), + ("", 0x40), + ("", 0x80), + ], +] From 6198c1fafe8353658090f040fd74bf3c3cedc391 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 15:24:34 -0400 Subject: [PATCH 33/73] Implement HCI string converter family MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hci_bustostr, hci_dtypetostr, hci_typetostr, hci_dflagstostr, hci_ptypetostr/hci_strtoptype, hci_scoptypetostr/hci_strtoscoptype, hci_lptostr/hci_strtolp, hci_lmtostr/hci_strtolm, hci_cmdtostr, hci_commandstostr, hci_vertostr/hci_strtover, lmp_vertostr/lmp_strtover, pal_vertostr/pal_strtover, and lmp_featurestostr — 21 symbols total. hci_lmtostr intentionally does not replicate the reference's fixed 50-byte buffer overflow for large link-mode masks, and hci_commandstostr matches the deployed 5.82 library's trailing-space behavior rather than the 5.85 source tree's trimmed output. --- Sources/BluetoothLinuxABI/HCIStrings.swift | 371 +++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/HCIStrings.swift diff --git a/Sources/BluetoothLinuxABI/HCIStrings.swift b/Sources/BluetoothLinuxABI/HCIStrings.swift new file mode 100644 index 0000000..4a492bb --- /dev/null +++ b/Sources/BluetoothLinuxABI/HCIStrings.swift @@ -0,0 +1,371 @@ +// +// HCIStrings.swift +// BluetoothLinux +// +// Swift implementations of the `hci_*tostr`/`hci_strto*` (and +// `lmp_*`/`pal_*`) string converter family, bound to the declarations +// in the vendored `hci_lib.h`. The lookup tables themselves are +// generated (see `gen/HCITables.swift`) from BlueZ's own `hci_map` +// arrays, to avoid transcription errors across the largest of them +// (232 HCI command names). +// +// One deliberate deviation from the reference: `hci_lmtostr` mallocs +// a fixed 50-byte buffer for a "PERIPHERAL " prefix concatenated with +// `hci_bit2str`'s (up to 120-byte) result — with enough link-mode +// bits set simultaneously, the reference overflows its own buffer. +// This is a real bug, not a documented contract; this implementation +// allocates enough space for the actual output instead of reproducing +// the overflow. +// + +import CBluetoothLinuxABI + +// MARK: - Table lookup helpers (BlueZ's hci_bit2str/hci_str2bit/hci_uint2str/hci_str2uint) + +/// `static char *hci_bit2str(const hci_map *m, unsigned int val)` +/// +/// Every set bit's name, space-separated, in table order. Returns an +/// empty (not NULL) string when no bits match, matching the reference. +private func bit2str(_ table: [(name: String, value: UInt32)], _ val: UInt32) -> UnsafeMutablePointer { + var result = "" + for entry in table where entry.value & val != 0 { + result += entry.name + result += " " + } + return strdup(result) +} + +/// `static int hci_str2bit(const hci_map *map, char *str, unsigned int *val)` +/// +/// Comma-separated, case-insensitive; ORs every matching entry's bits +/// into `*val`. Returns whether anything matched. +private func str2bit(_ table: [(name: String, value: UInt32)], _ str: UnsafePointer?) -> (matched: Bool, value: UInt32) { + guard let str else { return (false, 0) } + var value: UInt32 = 0 + var matched = false + for token in String(cString: str).split(separator: ",") { + let token = trimmed(token) + for entry in table where entry.name.lowercased() == token.lowercased() { + value |= entry.value + matched = true + } + } + return (matched, value) +} + +/// `static char *hci_uint2str(const hci_map *m, unsigned int val)` +/// +/// Exact match; empty (not NULL) string when nothing matches. +private func uint2str(_ table: [(name: String, value: UInt32)], _ val: UInt32) -> UnsafeMutablePointer { + for entry in table where entry.value == val { + return strdup(entry.name) + } + return strdup("") +} + +/// `static int hci_str2uint(const hci_map *map, char *str, unsigned int *val)` +private func str2uint(_ table: [(name: String, value: UInt32)], _ str: UnsafePointer?) -> (matched: Bool, value: UInt32) { + guard let str else { return (false, 0) } + let token = String(cString: str) + for candidate in token.split(separator: ",") { + let candidate = trimmed(candidate) + for entry in table where entry.name.lowercased() == candidate.lowercased() { + return (true, entry.value) + } + } + return (false, 0) +} + +/// Strips leading/trailing spaces and tabs — a Foundation-free stand-in +/// for `CharacterSet.whitespaces`-based trimming. +private func trimmed(_ substring: Substring) -> Substring { + var result = substring + while let first = result.first, first == " " || first == "\t" { result.removeFirst() } + while let last = result.last, last == " " || last == "\t" { result.removeLast() } + return result +} + +// MARK: - Static-lifetime strings (const char *, never freed) + +private nonisolated(unsafe) let staticStrings: [String: UnsafePointer] = [ + "Virtual": staticCString("Virtual"), + "USB": staticCString("USB"), + "PCCARD": staticCString("PCCARD"), + "UART": staticCString("UART"), + "RS232": staticCString("RS232"), + "PCI": staticCString("PCI"), + "SDIO": staticCString("SDIO"), + "SPI": staticCString("SPI"), + "I2C": staticCString("I2C"), + "SMD": staticCString("SMD"), + "VIRTIO": staticCString("VIRTIO"), + "IPC": staticCString("IPC"), + "Unknown": staticCString("Unknown"), + "Primary": staticCString("Primary"), + "AMP": staticCString("AMP") +] + +private func staticCString(_ string: String) -> UnsafePointer { + UnsafePointer(strdup(string)!) +} + +// MARK: - Bus / controller type + +/// `const char *hci_bustostr(int bus)` +@c(hci_bustostr) +public func hci_bustostr(_ bus: Int32) -> UnsafePointer? { + let name: String + switch bus { + case Int32(HCI_VIRTUAL): name = "Virtual" + case Int32(HCI_USB): name = "USB" + case Int32(HCI_PCCARD): name = "PCCARD" + case Int32(HCI_UART): name = "UART" + case Int32(HCI_RS232): name = "RS232" + case Int32(HCI_PCI): name = "PCI" + case Int32(HCI_SDIO): name = "SDIO" + case Int32(HCI_SPI): name = "SPI" + case Int32(HCI_I2C): name = "I2C" + case Int32(HCI_SMD): name = "SMD" + case Int32(HCI_VIRTIO): name = "VIRTIO" + case Int32(HCI_IPC): name = "IPC" + default: name = "Unknown" + } + return staticStrings[name] +} + +/// `const char *hci_dtypetostr(int type)` +@c(hci_dtypetostr) +public func hci_dtypetostr(_ type: Int32) -> UnsafePointer? { + hci_bustostr(type & 0x0f) +} + +/// `char *hci_typetostr(int type)` +@c(hci_typetostr) +public func hci_typetostr(_ type: Int32) -> UnsafeMutablePointer? { + let name: String + switch type { + case Int32(HCI_PRIMARY): name = "Primary" + case Int32(HCI_AMP): name = "AMP" + default: name = "Unknown" + } + return UnsafeMutablePointer(mutating: staticStrings[name]) +} + +// MARK: - Device flags + +/// `char *hci_dflagstostr(uint32_t flags)` +@c(hci_dflagstostr) +public func hci_dflagstostr(_ flags: UInt32) -> UnsafeMutablePointer? { + var result = "" + if flags & (1 << UInt32(HCI_UP)) == 0 { + result += "DOWN " + } + for entry in hciDeviceFlagsMap where flags & (1 << entry.value) != 0 { + result += entry.name + result += " " + } + return strdup(result) +} + +// MARK: - Packet types + +/// `char *hci_ptypetostr(unsigned int ptype)` +@c(hci_ptypetostr) +public func hci_ptypetostr(_ ptype: UInt32) -> UnsafeMutablePointer? { + bit2str(hciPacketTypeMap, ptype) +} + +/// `int hci_strtoptype(char *str, unsigned int *val)` +@c(hci_strtoptype) +public func hci_strtoptype(_ str: UnsafeMutablePointer?, _ val: UnsafeMutablePointer?) -> Int32 { + let (matched, value) = str2bit(hciPacketTypeMap, str) + val?.pointee = value + return matched ? 1 : 0 +} + +/// `char *hci_scoptypetostr(unsigned int ptype)` +@c(hci_scoptypetostr) +public func hci_scoptypetostr(_ ptype: UInt32) -> UnsafeMutablePointer? { + bit2str(hciSCOPacketTypeMap, ptype) +} + +/// `int hci_strtoscoptype(char *str, unsigned int *val)` +@c(hci_strtoscoptype) +public func hci_strtoscoptype(_ str: UnsafeMutablePointer?, _ val: UnsafeMutablePointer?) -> Int32 { + let (matched, value) = str2bit(hciSCOPacketTypeMap, str) + val?.pointee = value + return matched ? 1 : 0 +} + +// MARK: - Link policy + +/// `char *hci_lptostr(unsigned int lp)` +@c(hci_lptostr) +public func hci_lptostr(_ lp: UInt32) -> UnsafeMutablePointer? { + bit2str(hciLinkPolicyMap, lp) +} + +/// `int hci_strtolp(char *str, unsigned int *val)` +@c(hci_strtolp) +public func hci_strtolp(_ str: UnsafeMutablePointer?, _ val: UnsafeMutablePointer?) -> Int32 { + let (matched, value) = str2bit(hciLinkPolicyMap, str) + val?.pointee = value + return matched ? 1 : 0 +} + +// MARK: - Link mode + +/// `char *hci_lmtostr(unsigned int lm)` +/// +/// See the file-level note: allocates enough space for the real +/// output instead of reproducing the reference's fixed-buffer overflow. +@c(hci_lmtostr) +public func hci_lmtostr(_ lm: UInt32) -> UnsafeMutablePointer? { + var result = "" + if lm & UInt32(HCI_LM_MASTER) == 0 { + result += "PERIPHERAL " + } + for entry in hciLinkModeMap where entry.value & lm != 0 { + result += entry.name + result += " " + } + return strdup(result) +} + +/// `int hci_strtolm(char *str, unsigned int *val)` +@c(hci_strtolm) +public func hci_strtolm(_ str: UnsafeMutablePointer?, _ val: UnsafeMutablePointer?) -> Int32 { + var (matched, value) = str2bit(hciLinkModeMap, str) + // Deprecated name, kept for compatibility. + if let str, String(cString: str).lowercased().contains("master") { + matched = true + value |= UInt32(HCI_LM_MASTER) + } + val?.pointee = value + return matched ? 1 : 0 +} + +// MARK: - Commands + +/// `char *hci_cmdtostr(unsigned int cmd)` +@c(hci_cmdtostr) +public func hci_cmdtostr(_ cmd: UInt32) -> UnsafeMutablePointer? { + uint2str(hciCommandsMap, cmd) +} + +/// `char *hci_commandstostr(const uint8_t *commands, const char *pref, int width)` +/// +/// Every supported-commands bit set in `commands` (a 64-byte bitmap, +/// per the HCI spec), quoted and space-separated, wrapped to `width` +/// columns with `pref` repeated at the start of each line. +/// +/// BlueZ's `lib/bluetooth/hci.c` (as of 5.85, the version these +/// declarations are vendored from) trims the final trailing space +/// unconditionally (`ptr[-1] = '\0'` once anything was written). The +/// installed reference on this system is BlueZ 5.82, whose compiled +/// behavior — confirmed by differential conformance — keeps it. Since +/// conformance means matching the actual deployed library, not a +/// specific source revision, the trailing space is kept here too. +@c(hci_commandstostr) +public func hci_commandstostr( + _ commands: UnsafePointer?, + _ pref: UnsafePointer?, + _ width: Int32 +) -> UnsafeMutablePointer? { + guard let commands else { return nil } + let prefix = pref.map { String(cString: $0) } ?? "" + let maxWidth = Int(width) - 3 + + var result = prefix + var lineStart = result.count + + for entry in hciCommandsMap { + let bit = Int(entry.value) + guard commands[bit / 8] & (1 << (bit % 8)) != 0 else { continue } + let piece = "'\(entry.name)' " + if !result.isEmpty && (result.count - lineStart) + entry.name.count > maxWidth { + result.removeLast() + result += "\n" + result += prefix + lineStart = result.count + } + result += piece + } + return strdup(result) +} + +// MARK: - Versions + +/// `char *hci_vertostr(unsigned int ver)` +@c(hci_vertostr) +public func hci_vertostr(_ ver: UInt32) -> UnsafeMutablePointer? { + uint2str(hciVersionMap, ver) +} + +/// `int hci_strtover(char *str, unsigned int *ver)` +@c(hci_strtover) +public func hci_strtover(_ str: UnsafeMutablePointer?, _ ver: UnsafeMutablePointer?) -> Int32 { + let (matched, value) = str2uint(hciVersionMap, str) + if matched { ver?.pointee = value } + return matched ? 1 : 0 +} + +/// `char *lmp_vertostr(unsigned int ver)` +@c(lmp_vertostr) +public func lmp_vertostr(_ ver: UInt32) -> UnsafeMutablePointer? { + uint2str(hciVersionMap, ver) +} + +/// `int lmp_strtover(char *str, unsigned int *ver)` +@c(lmp_strtover) +public func lmp_strtover(_ str: UnsafeMutablePointer?, _ ver: UnsafeMutablePointer?) -> Int32 { + let (matched, value) = str2uint(hciVersionMap, str) + if matched { ver?.pointee = value } + return matched ? 1 : 0 +} + +/// `char *pal_vertostr(unsigned int ver)` +@c(pal_vertostr) +public func pal_vertostr(_ ver: UInt32) -> UnsafeMutablePointer? { + uint2str(hciPALVersionMap, ver) +} + +/// `int pal_strtover(char *str, unsigned int *ver)` +@c(pal_strtover) +public func pal_strtover(_ str: UnsafeMutablePointer?, _ ver: UnsafeMutablePointer?) -> Int32 { + let (matched, value) = str2uint(hciPALVersionMap, str) + if matched { ver?.pointee = value } + return matched ? 1 : 0 +} + +// MARK: - LMP features + +/// `char *lmp_featurestostr(uint8_t *features, char *pref, int width)` +@c(lmp_featurestostr) +public func lmp_featurestostr( + _ features: UnsafeMutablePointer?, + _ pref: UnsafeMutablePointer?, + _ width: Int32 +) -> UnsafeMutablePointer? { + guard let features else { return nil } + let prefix = pref.map { String(cString: $0) } + let maxWidth = Int(width) - 1 + + var result = prefix ?? "" + var lineStart = result.count + + for (byteIndex, row) in lmpFeaturesMap.enumerated() { + let byte = features[byteIndex] + for entry in row where entry.value & byte != 0 { + if (result.count - lineStart) + entry.name.count > maxWidth { + result += "\n" + result += prefix ?? "" + lineStart = result.count + } + result += entry.name + result += " " + } + } + + return strdup(result) +} From 72cbde57801009d269114c051f74c4aa7f197c32 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 15:24:42 -0400 Subject: [PATCH 34/73] Wire BluetoothLinuxABI into the C ABI SwiftPM build Adds the BluetoothLinuxABI product/target (built against CBluetoothLinuxABI and Bluetooth's BluetoothABI product for bt_malloc/bt_free) plus its test target, gated behind SWIFTPM_BLUETOOTH_CABI=1 alongside the rest of the C ABI surface. --- Package.swift | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Package.swift b/Package.swift index 1c022b4..a5f1a6e 100644 --- a/Package.swift +++ b/Package.swift @@ -194,6 +194,13 @@ if buildDocs { // itself is built by CMake, which is the only one of the two that can // set a soname, a version and an export list. if ProcessInfo.processInfo.environment["SWIFTPM_BLUETOOTH_CABI"] == "1" { + package.products += [ + .library( + name: "BluetoothLinuxABI", + type: .dynamic, + targets: ["BluetoothLinuxABI"] + ) + ] package.targets += [ .target( name: "CBluetoothLinuxABI", @@ -201,6 +208,25 @@ if ProcessInfo.processInfo.environment["SWIFTPM_BLUETOOTH_CABI"] == "1" { "README.md", "include/bluetooth/LICENSE" ] + ), + .target( + name: "BluetoothLinuxABI", + dependencies: [ + "CBluetoothLinuxABI", + // For bt_malloc/bt_malloc0/bt_free — CBluetoothLinuxABI + // declares them (bluetooth.h) but does not define them; + // BluetoothABI's CBluetooth does. Also gated behind + // SWIFTPM_BLUETOOTH_CABI=1, which SwiftPM propagates to + // this dependency's own manifest evaluation. + .product(name: "BluetoothABI", package: "Bluetooth") + ] + ), + .testTarget( + name: "BluetoothLinuxABITests", + dependencies: [ + "BluetoothLinuxABI", + "CBluetoothLinuxABI" + ] ) ] } From 783cf08e14df92380d07bcdb6d622df208d4ee67 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 1 Aug 2026 15:24:46 -0400 Subject: [PATCH 35/73] Add unit tests for HCI string converters Covers bus, device type, device flags, packet type, link mode, command name, and version conversions. --- .../HCIStringsTests.swift | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Tests/BluetoothLinuxABITests/HCIStringsTests.swift diff --git a/Tests/BluetoothLinuxABITests/HCIStringsTests.swift b/Tests/BluetoothLinuxABITests/HCIStringsTests.swift new file mode 100644 index 0000000..cf3be29 --- /dev/null +++ b/Tests/BluetoothLinuxABITests/HCIStringsTests.swift @@ -0,0 +1,84 @@ +// +// HCIStringsTests.swift +// BluetoothLinuxABITests +// +// Round-trip and behavior tests for the HCI string converter family, +// asserting the reference (BlueZ `lib/bluetooth/hci.c`) behavior. +// + +import Testing +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import CBluetoothLinuxABI +@testable import BluetoothLinuxABI + +@Suite("HCI Strings") +struct HCIStringsTests { + + @Test func bus() { + #expect(String(cString: BluetoothLinuxABI.hci_bustostr(Int32(HCI_USB))!) == "USB") + #expect(String(cString: BluetoothLinuxABI.hci_bustostr(99)!) == "Unknown") + #expect(String(cString: BluetoothLinuxABI.hci_dtypetostr(Int32(HCI_USB))!) == "USB") + } + + @Test func deviceType() { + #expect(String(cString: BluetoothLinuxABI.hci_typetostr(Int32(HCI_PRIMARY))!) == "Primary") + #expect(String(cString: BluetoothLinuxABI.hci_typetostr(Int32(HCI_AMP))!) == "AMP") + } + + @Test func deviceFlags() { + let down = BluetoothLinuxABI.hci_dflagstostr(0)! + #expect(String(cString: down) == "DOWN ") + free(down) + + // No "DOWN" prefix once HCI_UP is set, but "UP" itself is still + // printed — it's just another entry in the device flags table. + let up = BluetoothLinuxABI.hci_dflagstostr(1 << UInt32(HCI_UP))! + #expect(String(cString: up) == "UP ") + free(up) + } + + @Test func packetType() { + let str = BluetoothLinuxABI.hci_ptypetostr(UInt32(HCI_DM1) | UInt32(HCI_DH1))! + #expect(String(cString: str) == "DM1 DH1 ") + free(str) + + var value: UInt32 = 0 + let matched = "DM1,DH1".withCString { BluetoothLinuxABI.hci_strtoptype(UnsafeMutablePointer(mutating: $0), &value) } + #expect(matched == 1) + #expect(value == UInt32(HCI_DM1) | UInt32(HCI_DH1)) + } + + @Test func linkMode() { + // No "PERIPHERAL" prefix once HCI_LM_MASTER is set, but + // "CENTRAL" (the table entry HCI_LM_MASTER maps to) is still + // printed by the same bit-table loop. + let str = BluetoothLinuxABI.hci_lmtostr(UInt32(HCI_LM_MASTER))! + #expect(String(cString: str) == "CENTRAL ") + free(str) + + let peripheral = BluetoothLinuxABI.hci_lmtostr(0)! + #expect(String(cString: peripheral) == "PERIPHERAL ") + free(peripheral) + } + + @Test func commands() { + let str = BluetoothLinuxABI.hci_cmdtostr(0)! + #expect(String(cString: str) == "Inquiry") + free(str) + } + + @Test func version() { + let str = BluetoothLinuxABI.hci_vertostr(0x09)! + #expect(String(cString: str) == "5.0") + free(str) + + var ver: UInt32 = 0 + let matched = "5.0".withCString { BluetoothLinuxABI.hci_strtover(UnsafeMutablePointer(mutating: $0), &ver) } + #expect(matched == 1) + #expect(ver == 0x09) + } +} From 9df0bc1da62dc589d407546b1b1f8c3c813d5b9b Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 17:59:16 -0400 Subject: [PATCH 36/73] Mark HCI string converter symbols as implemented Also includes the 75 SDP symbols implemented in the Bluetooth dependency, so gen_stubs.py stops generating stubs for either family. --- scripts/implemented.txt | 102 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/scripts/implemented.txt b/scripts/implemented.txt index 54c6da7..98f1af6 100644 --- a/scripts/implemented.txt +++ b/scripts/implemented.txt @@ -41,3 +41,105 @@ bt_uuid_strcmp bt_uuid_to_le bt_uuid_to_string bt_uuid_to_uuid128 + +# The pure (non-socket) sdp.c symbols (75), implemented in +# PureSwift/Bluetooth's Sources/BluetoothSDP. +sdp_add_lang_attr +sdp_append_to_buf +sdp_append_to_pdu +sdp_attr_add +sdp_attr_add_new +sdp_attr_remove +sdp_attr_replace +sdp_attrid_comp_func +sdp_copy_record +sdp_data_alloc +sdp_data_alloc_with_length +sdp_data_free +sdp_data_get +sdp_extract_attr +sdp_extract_pdu +sdp_extract_seqtype +sdp_gen_pdu +sdp_gen_record_pdu +sdp_get_access_protos +sdp_get_add_access_protos +sdp_get_database_state +sdp_get_group_id +sdp_get_int_attr +sdp_get_lang_attr +sdp_get_profile_descs +sdp_get_proto_desc +sdp_get_proto_port +sdp_get_record_state +sdp_get_server_ver +sdp_get_service_avail +sdp_get_service_id +sdp_get_service_ttl +sdp_get_string_attr +sdp_get_supp_feat +sdp_get_uuidseq_attr +sdp_list_append +sdp_list_free +sdp_list_insert_sorted +sdp_list_remove +sdp_pattern_add_uuid +sdp_pattern_add_uuidseq +sdp_profile_uuid2strn +sdp_proto_uuid2strn +sdp_record_alloc +sdp_record_free +sdp_record_print +sdp_seq_alloc +sdp_seq_alloc_with_length +sdp_seq_append +sdp_set_access_protos +sdp_set_add_access_protos +sdp_set_attrid +sdp_set_group_id +sdp_set_info_attr +sdp_set_lang_attr +sdp_set_profile_descs +sdp_set_seq_len +sdp_set_service_id +sdp_set_supp_feat +sdp_set_url_attr +sdp_set_uuidseq_attr +sdp_svclass_uuid2strn +sdp_uuid128_cmp +sdp_uuid128_create +sdp_uuid128_to_uuid +sdp_uuid16_cmp +sdp_uuid16_create +sdp_uuid16_to_uuid128 +sdp_uuid2strn +sdp_uuid32_create +sdp_uuid32_to_uuid128 +sdp_uuid_cmp +sdp_uuid_extract +sdp_uuid_to_proto +sdp_uuid_to_uuid128 + +# HCI string converters (21), implemented in +# Sources/BluetoothLinuxABI/HCIStrings.swift. +hci_bustostr +hci_cmdtostr +hci_commandstostr +hci_dflagstostr +hci_dtypetostr +hci_lmtostr +hci_lptostr +hci_ptypetostr +hci_scoptypetostr +hci_strtolm +hci_strtolp +hci_strtoptype +hci_strtoscoptype +hci_strtover +hci_typetostr +hci_vertostr +lmp_featurestostr +lmp_strtover +lmp_vertostr +pal_strtover +pal_vertostr From 29e270ed9253e5660aeeaf174b61f41b9a65ed41 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 17:59:20 -0400 Subject: [PATCH 37/73] Regenerate C ABI stub table Drops the 96 stubs now covered by BluetoothSDP and BluetoothLinuxABI (105 remaining, 123 implemented). --- Sources/CBluetoothLinuxABI/gen/cbt_stubs.c | 480 --------------------- 1 file changed, 480 deletions(-) diff --git a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c index b85a3d1..dec55e0 100644 --- a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c +++ b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c @@ -35,11 +35,6 @@ int hci_authenticate_link(int dd, uint16_t handle, int to) cbt_unimplemented("hci_authenticate_link"); } -const char *hci_bustostr(int bus) -{ - cbt_unimplemented("hci_bustostr"); -} - int hci_change_link_key(int dd, uint16_t handle, int to) { cbt_unimplemented("hci_change_link_key"); @@ -50,16 +45,6 @@ int hci_close_dev(int dd) cbt_unimplemented("hci_close_dev"); } -char *hci_cmdtostr(unsigned int cmd) -{ - cbt_unimplemented("hci_cmdtostr"); -} - -char *hci_commandstostr(const uint8_t *commands, const char *pref, int width) -{ - cbt_unimplemented("hci_commandstostr"); -} - int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to) { cbt_unimplemented("hci_create_connection"); @@ -85,21 +70,11 @@ int hci_devinfo(int dev_id, struct hci_dev_info *di) cbt_unimplemented("hci_devinfo"); } -char *hci_dflagstostr(uint32_t flags) -{ - cbt_unimplemented("hci_dflagstostr"); -} - int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to) { cbt_unimplemented("hci_disconnect"); } -const char *hci_dtypetostr(int type) -{ - cbt_unimplemented("hci_dtypetostr"); -} - int hci_encrypt_link(int dd, uint16_t handle, uint8_t encrypt, int to) { cbt_unimplemented("hci_encrypt_link"); @@ -200,16 +175,6 @@ int hci_le_set_scan_parameters(int dev_id, uint8_t type, uint16_t interval, uint cbt_unimplemented("hci_le_set_scan_parameters"); } -char *hci_lmtostr(unsigned int ptype) -{ - cbt_unimplemented("hci_lmtostr"); -} - -char *hci_lptostr(unsigned int ptype) -{ - cbt_unimplemented("hci_lptostr"); -} - int hci_open_dev(int dev_id) { cbt_unimplemented("hci_open_dev"); @@ -220,11 +185,6 @@ int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval, uint16_t min_i cbt_unimplemented("hci_park_mode"); } -char *hci_ptypetostr(unsigned int ptype) -{ - cbt_unimplemented("hci_ptypetostr"); -} - int hci_read_afh_map(int dd, uint16_t handle, uint8_t *mode, uint8_t *map, int to) { cbt_unimplemented("hci_read_afh_map"); @@ -385,11 +345,6 @@ int hci_read_voice_setting(int dd, uint16_t *vs, int to) cbt_unimplemented("hci_read_voice_setting"); } -char *hci_scoptypetostr(unsigned int ptype) -{ - cbt_unimplemented("hci_scoptypetostr"); -} - int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param) { cbt_unimplemented("hci_send_cmd"); @@ -405,46 +360,11 @@ int hci_set_afh_classification(int dd, uint8_t *map, int to) cbt_unimplemented("hci_set_afh_classification"); } -int hci_strtolm(char *str, unsigned int *val) -{ - cbt_unimplemented("hci_strtolm"); -} - -int hci_strtolp(char *str, unsigned int *val) -{ - cbt_unimplemented("hci_strtolp"); -} - -int hci_strtoptype(char *str, unsigned int *val) -{ - cbt_unimplemented("hci_strtoptype"); -} - -int hci_strtoscoptype(char *str, unsigned int *val) -{ - cbt_unimplemented("hci_strtoscoptype"); -} - -int hci_strtover(char *str, unsigned int *ver) -{ - cbt_unimplemented("hci_strtover"); -} - int hci_switch_role(int dd, bdaddr_t *bdaddr, uint8_t role, int to) { cbt_unimplemented("hci_switch_role"); } -char *hci_typetostr(int type) -{ - cbt_unimplemented("hci_typetostr"); -} - -char *hci_vertostr(unsigned int ver) -{ - cbt_unimplemented("hci_vertostr"); -} - int hci_write_afh_mode(int dd, uint8_t mode, int to) { cbt_unimplemented("hci_write_afh_mode"); @@ -510,71 +430,6 @@ int hci_write_voice_setting(int dd, uint16_t vs, int to) cbt_unimplemented("hci_write_voice_setting"); } -char *lmp_featurestostr(uint8_t *features, char *pref, int width) -{ - cbt_unimplemented("lmp_featurestostr"); -} - -int lmp_strtover(char *str, unsigned int *ver) -{ - cbt_unimplemented("lmp_strtover"); -} - -char *lmp_vertostr(unsigned int ver) -{ - cbt_unimplemented("lmp_vertostr"); -} - -int pal_strtover(char *str, unsigned int *ver) -{ - cbt_unimplemented("pal_strtover"); -} - -char *pal_vertostr(unsigned int ver) -{ - cbt_unimplemented("pal_vertostr"); -} - -void sdp_add_lang_attr(sdp_record_t *rec) -{ - cbt_unimplemented("sdp_add_lang_attr"); -} - -void sdp_append_to_buf(sdp_buf_t *dst, uint8_t *data, uint32_t len) -{ - cbt_unimplemented("sdp_append_to_buf"); -} - -void sdp_append_to_pdu(sdp_buf_t *dst, sdp_data_t *d) -{ - cbt_unimplemented("sdp_append_to_pdu"); -} - -int sdp_attr_add(sdp_record_t *rec, uint16_t attr, sdp_data_t *data) -{ - cbt_unimplemented("sdp_attr_add"); -} - -int sdp_attr_add_new(sdp_record_t *rec, uint16_t attr, uint8_t dtd, const void *p) -{ - cbt_unimplemented("sdp_attr_add_new"); -} - -void sdp_attr_remove(sdp_record_t *rec, uint16_t attr) -{ - cbt_unimplemented("sdp_attr_remove"); -} - -void sdp_attr_replace(sdp_record_t *rec, uint16_t attr, sdp_data_t *data) -{ - cbt_unimplemented("sdp_attr_replace"); -} - -int sdp_attrid_comp_func(const void *key1, const void *key2) -{ - cbt_unimplemented("sdp_attrid_comp_func"); -} - int sdp_close(sdp_session_t *session) { cbt_unimplemented("sdp_close"); @@ -585,36 +440,11 @@ sdp_session_t *sdp_connect(const bdaddr_t *src, const bdaddr_t *dst, uint32_t fl cbt_unimplemented("sdp_connect"); } -sdp_record_t *sdp_copy_record(sdp_record_t *rec) -{ - cbt_unimplemented("sdp_copy_record"); -} - sdp_session_t *sdp_create(int sk, uint32_t flags) { cbt_unimplemented("sdp_create"); } -sdp_data_t *sdp_data_alloc(uint8_t dtd, const void *value) -{ - cbt_unimplemented("sdp_data_alloc"); -} - -sdp_data_t *sdp_data_alloc_with_length(uint8_t dtd, const void *value, uint32_t length) -{ - cbt_unimplemented("sdp_data_alloc_with_length"); -} - -void sdp_data_free(sdp_data_t *data) -{ - cbt_unimplemented("sdp_data_free"); -} - -sdp_data_t *sdp_data_get(const sdp_record_t *rec, uint16_t attr_id) -{ - cbt_unimplemented("sdp_data_get"); -} - int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags) { cbt_unimplemented("sdp_device_record_register"); @@ -645,31 +475,6 @@ int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, ui cbt_unimplemented("sdp_device_record_update_binary"); } -sdp_data_t *sdp_extract_attr(const uint8_t *pdata, int bufsize, int *extractedLength, sdp_record_t *rec) -{ - cbt_unimplemented("sdp_extract_attr"); -} - -sdp_record_t *sdp_extract_pdu(const uint8_t *pdata, int bufsize, int *scanned) -{ - cbt_unimplemented("sdp_extract_pdu"); -} - -int sdp_extract_seqtype(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size) -{ - cbt_unimplemented("sdp_extract_seqtype"); -} - -int sdp_gen_pdu(sdp_buf_t *pdu, sdp_data_t *data) -{ - cbt_unimplemented("sdp_gen_pdu"); -} - -int sdp_gen_record_pdu(const sdp_record_t *rec, sdp_buf_t *pdu) -{ - cbt_unimplemented("sdp_gen_record_pdu"); -} - uint16_t sdp_gen_tid(sdp_session_t *session) { cbt_unimplemented("sdp_gen_tid"); @@ -680,161 +485,21 @@ int sdp_general_inquiry(inquiry_info *ii, int dev_num, int duration, uint8_t *fo cbt_unimplemented("sdp_general_inquiry"); } -int sdp_get_access_protos(const sdp_record_t *rec, sdp_list_t **protos) -{ - cbt_unimplemented("sdp_get_access_protos"); -} - -int sdp_get_add_access_protos(const sdp_record_t *rec, sdp_list_t **protos) -{ - cbt_unimplemented("sdp_get_add_access_protos"); -} - -int sdp_get_database_state(const sdp_record_t *rec, uint32_t *svcDBState) -{ - cbt_unimplemented("sdp_get_database_state"); -} - int sdp_get_error(sdp_session_t *session) { cbt_unimplemented("sdp_get_error"); } -int sdp_get_group_id(const sdp_record_t *rec, uuid_t *uuid) -{ - cbt_unimplemented("sdp_get_group_id"); -} - -int sdp_get_int_attr(const sdp_record_t *rec, uint16_t attr, int *value) -{ - cbt_unimplemented("sdp_get_int_attr"); -} - -int sdp_get_lang_attr(const sdp_record_t *rec, sdp_list_t **langSeq) -{ - cbt_unimplemented("sdp_get_lang_attr"); -} - -int sdp_get_profile_descs(const sdp_record_t *rec, sdp_list_t **profDesc) -{ - cbt_unimplemented("sdp_get_profile_descs"); -} - -sdp_data_t *sdp_get_proto_desc(sdp_list_t *list, int proto) -{ - cbt_unimplemented("sdp_get_proto_desc"); -} - -int sdp_get_proto_port(const sdp_list_t *list, int proto) -{ - cbt_unimplemented("sdp_get_proto_port"); -} - -int sdp_get_record_state(const sdp_record_t *rec, uint32_t *svcRecState) -{ - cbt_unimplemented("sdp_get_record_state"); -} - -int sdp_get_server_ver(const sdp_record_t *rec, sdp_list_t **pVnumList) -{ - cbt_unimplemented("sdp_get_server_ver"); -} - -int sdp_get_service_avail(const sdp_record_t *rec, uint8_t *svcAvail) -{ - cbt_unimplemented("sdp_get_service_avail"); -} - -int sdp_get_service_id(const sdp_record_t *rec, uuid_t *uuid) -{ - cbt_unimplemented("sdp_get_service_id"); -} - -int sdp_get_service_ttl(const sdp_record_t *rec, uint32_t *svcTTLInfo) -{ - cbt_unimplemented("sdp_get_service_ttl"); -} - int sdp_get_socket(const sdp_session_t *session) { cbt_unimplemented("sdp_get_socket"); } -int sdp_get_string_attr(const sdp_record_t *rec, uint16_t attr, char *value, size_t valuelen) -{ - cbt_unimplemented("sdp_get_string_attr"); -} - -int sdp_get_supp_feat(const sdp_record_t *rec, sdp_list_t **seqp) -{ - cbt_unimplemented("sdp_get_supp_feat"); -} - -int sdp_get_uuidseq_attr(const sdp_record_t *rec, uint16_t attr, sdp_list_t **seqp) -{ - cbt_unimplemented("sdp_get_uuidseq_attr"); -} - -sdp_list_t *sdp_list_append(sdp_list_t *list, void *d) -{ - cbt_unimplemented("sdp_list_append"); -} - -void sdp_list_free(sdp_list_t *list, sdp_free_func_t f) -{ - cbt_unimplemented("sdp_list_free"); -} - -sdp_list_t *sdp_list_insert_sorted(sdp_list_t *list, void *data, sdp_comp_func_t f) -{ - cbt_unimplemented("sdp_list_insert_sorted"); -} - -sdp_list_t *sdp_list_remove(sdp_list_t *list, void *d) -{ - cbt_unimplemented("sdp_list_remove"); -} - -void sdp_pattern_add_uuid(sdp_record_t *rec, uuid_t *uuid) -{ - cbt_unimplemented("sdp_pattern_add_uuid"); -} - -void sdp_pattern_add_uuidseq(sdp_record_t *rec, sdp_list_t *seq) -{ - cbt_unimplemented("sdp_pattern_add_uuidseq"); -} - int sdp_process(sdp_session_t *session) { cbt_unimplemented("sdp_process"); } -int sdp_profile_uuid2strn(const uuid_t *uuid, char *str, size_t n) -{ - cbt_unimplemented("sdp_profile_uuid2strn"); -} - -int sdp_proto_uuid2strn(const uuid_t *uuid, char *str, size_t n) -{ - cbt_unimplemented("sdp_proto_uuid2strn"); -} - -sdp_record_t *sdp_record_alloc(void) -{ - cbt_unimplemented("sdp_record_alloc"); -} - -void sdp_record_free(sdp_record_t *rec) -{ - cbt_unimplemented("sdp_record_free"); -} - -void sdp_record_print(const sdp_record_t *rec) -{ - cbt_unimplemented("sdp_record_print"); -} - int sdp_record_register(sdp_session_t *session, sdp_record_t *rec, uint8_t flags) { cbt_unimplemented("sdp_record_register"); @@ -855,21 +520,6 @@ int sdp_send_req_w4_rsp(sdp_session_t *session, uint8_t *req, uint8_t *rsp, uint cbt_unimplemented("sdp_send_req_w4_rsp"); } -sdp_data_t *sdp_seq_alloc(void **dtds, void **values, int len) -{ - cbt_unimplemented("sdp_seq_alloc"); -} - -sdp_data_t *sdp_seq_alloc_with_length(void **dtds, void **values, int *length, int len) -{ - cbt_unimplemented("sdp_seq_alloc_with_length"); -} - -sdp_data_t *sdp_seq_append(sdp_data_t *seq, sdp_data_t *data) -{ - cbt_unimplemented("sdp_seq_append"); -} - int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) { cbt_unimplemented("sdp_service_attr_async"); @@ -900,138 +550,8 @@ int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search, uin cbt_unimplemented("sdp_service_search_req"); } -int sdp_set_access_protos(sdp_record_t *rec, const sdp_list_t *proto) -{ - cbt_unimplemented("sdp_set_access_protos"); -} - -int sdp_set_add_access_protos(sdp_record_t *rec, const sdp_list_t *proto) -{ - cbt_unimplemented("sdp_set_add_access_protos"); -} - -void sdp_set_attrid(sdp_buf_t *pdu, uint16_t id) -{ - cbt_unimplemented("sdp_set_attrid"); -} - -void sdp_set_group_id(sdp_record_t *rec, uuid_t grouuuid) -{ - cbt_unimplemented("sdp_set_group_id"); -} - -void sdp_set_info_attr(sdp_record_t *rec, const char *name, const char *prov, const char *desc) -{ - cbt_unimplemented("sdp_set_info_attr"); -} - -int sdp_set_lang_attr(sdp_record_t *rec, const sdp_list_t *list) -{ - cbt_unimplemented("sdp_set_lang_attr"); -} - int sdp_set_notify(sdp_session_t *session, sdp_callback_t *func, void *udata) { cbt_unimplemented("sdp_set_notify"); } -int sdp_set_profile_descs(sdp_record_t *rec, const sdp_list_t *desc) -{ - cbt_unimplemented("sdp_set_profile_descs"); -} - -void sdp_set_seq_len(uint8_t *ptr, uint32_t length) -{ - cbt_unimplemented("sdp_set_seq_len"); -} - -void sdp_set_service_id(sdp_record_t *rec, uuid_t uuid) -{ - cbt_unimplemented("sdp_set_service_id"); -} - -int sdp_set_supp_feat(sdp_record_t *rec, const sdp_list_t *sf) -{ - cbt_unimplemented("sdp_set_supp_feat"); -} - -void sdp_set_url_attr(sdp_record_t *rec, const char *clientExecURL, const char *docURL, const char *iconURL) -{ - cbt_unimplemented("sdp_set_url_attr"); -} - -int sdp_set_uuidseq_attr(sdp_record_t *rec, uint16_t attr, sdp_list_t *seq) -{ - cbt_unimplemented("sdp_set_uuidseq_attr"); -} - -int sdp_svclass_uuid2strn(const uuid_t *uuid, char *str, size_t n) -{ - cbt_unimplemented("sdp_svclass_uuid2strn"); -} - -int sdp_uuid128_cmp(const void *p1, const void *p2) -{ - cbt_unimplemented("sdp_uuid128_cmp"); -} - -uuid_t *sdp_uuid128_create(uuid_t *uuid, const void *data) -{ - cbt_unimplemented("sdp_uuid128_create"); -} - -int sdp_uuid128_to_uuid(uuid_t *uuid) -{ - cbt_unimplemented("sdp_uuid128_to_uuid"); -} - -int sdp_uuid16_cmp(const void *p1, const void *p2) -{ - cbt_unimplemented("sdp_uuid16_cmp"); -} - -uuid_t *sdp_uuid16_create(uuid_t *uuid, uint16_t data) -{ - cbt_unimplemented("sdp_uuid16_create"); -} - -void sdp_uuid16_to_uuid128(uuid_t *uuid128, const uuid_t *uuid16) -{ - cbt_unimplemented("sdp_uuid16_to_uuid128"); -} - -int sdp_uuid2strn(const uuid_t *uuid, char *str, size_t n) -{ - cbt_unimplemented("sdp_uuid2strn"); -} - -uuid_t *sdp_uuid32_create(uuid_t *uuid, uint32_t data) -{ - cbt_unimplemented("sdp_uuid32_create"); -} - -void sdp_uuid32_to_uuid128(uuid_t *uuid128, const uuid_t *uuid32) -{ - cbt_unimplemented("sdp_uuid32_to_uuid128"); -} - -int sdp_uuid_cmp(const void *p1, const void *p2) -{ - cbt_unimplemented("sdp_uuid_cmp"); -} - -int sdp_uuid_extract(const uint8_t *buffer, int bufsize, uuid_t *uuid, int *scanned) -{ - cbt_unimplemented("sdp_uuid_extract"); -} - -int sdp_uuid_to_proto(uuid_t *uuid) -{ - cbt_unimplemented("sdp_uuid_to_proto"); -} - -uuid_t *sdp_uuid_to_uuid128(const uuid_t *uuid) -{ - cbt_unimplemented("sdp_uuid_to_uuid128"); -} - From 23d5c4b7fe15191f526a409a2e6437d0ef190626 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 17:59:55 -0400 Subject: [PATCH 38/73] Add differential conformance driver for HCI string converters Exercises all 21 symbols with edge-case inputs (unknown bus/device values, empty and saturated bitmasks, malformed parse strings). Links directly against the system libbluetooth.so.3 since all 21 symbols are exported by it. --- Conformance/conformance_hci_strings.c | 217 ++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 Conformance/conformance_hci_strings.c diff --git a/Conformance/conformance_hci_strings.c b/Conformance/conformance_hci_strings.c new file mode 100644 index 0000000..caf2293 --- /dev/null +++ b/Conformance/conformance_hci_strings.c @@ -0,0 +1,217 @@ +/* + * Differential conformance driver for the HCI string converter family + * (21 symbols: hci_*tostr/hci_strto*, lmp_*, pal_*). + * + * All 21 are exported by the system libbluetooth.so.3 (verified + * against nm -D), so this driver links against it directly — no + * BlueZ source tree needed. + */ + +#include +#include +#include + +#include +#include +#include + +static void dump_bus(void) +{ + int buses[] = { + HCI_VIRTUAL, HCI_USB, HCI_PCCARD, HCI_UART, HCI_RS232, HCI_PCI, + HCI_SDIO, HCI_SPI, HCI_I2C, HCI_SMD, HCI_VIRTIO, HCI_IPC, 99 + }; + size_t i; + + for (i = 0; i < sizeof(buses) / sizeof(*buses); i++) { + printf("bustostr(%d) = \"%s\"\n", buses[i], hci_bustostr(buses[i])); + printf("dtypetostr(%d) = \"%s\"\n", buses[i], hci_dtypetostr(buses[i])); + } + + printf("typetostr(%d) = \"%s\"\n", HCI_PRIMARY, hci_typetostr(HCI_PRIMARY)); + printf("typetostr(%d) = \"%s\"\n", HCI_AMP, hci_typetostr(HCI_AMP)); + printf("typetostr(99) = \"%s\"\n", hci_typetostr(99)); +} + +static void dump_dflags(void) +{ + uint32_t flags[] = { + 0, + 1 << HCI_UP, + 1 << HCI_INIT, + (1 << HCI_UP) | (1 << HCI_RUNNING) | (1 << HCI_PSCAN), + 0xFFFFFFFF + }; + size_t i; + + for (i = 0; i < sizeof(flags) / sizeof(*flags); i++) { + char *s = hci_dflagstostr(flags[i]); + printf("dflagstostr(0x%08x) = \"%s\"\n", flags[i], s ? s : "(null)"); + free(s); + } +} + +static void dump_ptype(void) +{ + unsigned int values[] = { + 0, HCI_DM1, HCI_DM1 | HCI_DH1, HCI_DM1 | HCI_DM3 | HCI_DM5 | + HCI_DH1 | HCI_DH3 | HCI_DH5, 0xFFFF + }; + size_t i; + static const char *strs[] = { + "DM1", "DM1,DH1", "DM1,DH1,BOGUS", "bogus", NULL + }; + + for (i = 0; i < sizeof(values) / sizeof(*values); i++) { + char *s = hci_ptypetostr(values[i]); + printf("ptypetostr(0x%04x) = \"%s\"\n", values[i], s ? s : "(null)"); + free(s); + + s = hci_scoptypetostr(values[i]); + printf("scoptypetostr(0x%04x) = \"%s\"\n", values[i], s ? s : "(null)"); + free(s); + } + + for (i = 0; strs[i]; i++) { + unsigned int val = 0xdeadbeef; + int rc = hci_strtoptype((char *) strs[i], &val); + printf("strtoptype(\"%s\") = %d val=0x%x\n", strs[i], rc, val); + + val = 0xdeadbeef; + rc = hci_strtoscoptype((char *) strs[i], &val); + printf("strtoscoptype(\"%s\") = %d val=0x%x\n", strs[i], rc, val); + } +} + +static void dump_lp_lm(void) +{ + unsigned int values[] = { 0, HCI_LP_RSWITCH, HCI_LP_SNIFF | HCI_LP_PARK, 0xFF }; + size_t i; + static const char *lp_strs[] = { "RSWITCH", "SNIFF,PARK", "bogus", NULL }; + static const char *lm_strs[] = { "ACCEPT", "MASTER", "CENTRAL,AUTH", "bogus", NULL }; + + for (i = 0; i < sizeof(values) / sizeof(*values); i++) { + char *s = hci_lptostr(values[i]); + printf("lptostr(0x%02x) = \"%s\"\n", values[i], s ? s : "(null)"); + free(s); + + s = hci_lmtostr(values[i]); + printf("lmtostr(0x%02x) = \"%s\"\n", values[i], s ? s : "(null)"); + free(s); + } + + for (i = 0; lp_strs[i]; i++) { + unsigned int val = 0xdeadbeef; + int rc = hci_strtolp((char *) lp_strs[i], &val); + printf("strtolp(\"%s\") = %d val=0x%x\n", lp_strs[i], rc, val); + } + + for (i = 0; lm_strs[i]; i++) { + unsigned int val = 0xdeadbeef; + int rc = hci_strtolm((char *) lm_strs[i], &val); + printf("strtolm(\"%s\") = %d val=0x%x\n", lm_strs[i], rc, val); + } +} + +static void dump_commands(void) +{ + unsigned int cmds[] = { 0, 1, 227, 231, 9999 }; + size_t i; + uint8_t bitmap[64]; + + for (i = 0; i < sizeof(cmds) / sizeof(*cmds); i++) { + char *s = hci_cmdtostr(cmds[i]); + printf("cmdtostr(%u) = \"%s\"\n", cmds[i], s ? s : "(null)"); + free(s); + } + + memset(bitmap, 0, sizeof(bitmap)); + bitmap[0] = 0x01; /* bit 0: Inquiry */ + bitmap[0] |= 0x02; /* bit 1: Inquiry Cancel */ + bitmap[28] |= 0x08; /* bit 227: LE Read Supported States */ + { + char *s = hci_commandstostr(bitmap, " ", 60); + printf("commandstostr(narrow) = \"%s\"\n", s ? s : "(null)"); + free(s); + + s = hci_commandstostr(bitmap, NULL, 200); + printf("commandstostr(wide, no pref) = \"%s\"\n", s ? s : "(null)"); + free(s); + } +} + +static void dump_versions(void) +{ + unsigned int values[] = { 0x00, 0x09, 0x0d, 0xff }; + size_t i; + static const char *strs[] = { "5.0", "1.0b", "bogus", NULL }; + + for (i = 0; i < sizeof(values) / sizeof(*values); i++) { + char *s = hci_vertostr(values[i]); + printf("vertostr(0x%02x) = \"%s\"\n", values[i], s ? s : "(null)"); + free(s); + + s = lmp_vertostr(values[i]); + printf("lmp_vertostr(0x%02x) = \"%s\"\n", values[i], s ? s : "(null)"); + free(s); + } + + { + unsigned int palValues[] = { 0x00, 0x01, 0xff }; + for (i = 0; i < sizeof(palValues) / sizeof(*palValues); i++) { + char *s = pal_vertostr(palValues[i]); + printf("pal_vertostr(0x%02x) = \"%s\"\n", palValues[i], s ? s : "(null)"); + free(s); + } + } + + for (i = 0; strs[i]; i++) { + unsigned int ver = 0xdeadbeef; + int rc = hci_strtover((char *) strs[i], &ver); + printf("strtover(\"%s\") = %d ver=0x%x\n", strs[i], rc, ver); + + ver = 0xdeadbeef; + rc = lmp_strtover((char *) strs[i], &ver); + printf("lmp_strtover(\"%s\") = %d ver=0x%x\n", strs[i], rc, ver); + + ver = 0xdeadbeef; + rc = pal_strtover((char *) strs[i], &ver); + printf("pal_strtover(\"%s\") = %d ver=0x%x\n", strs[i], rc, ver); + } +} + +static void dump_features(void) +{ + uint8_t features[8]; + char *s; + + memset(features, 0, sizeof(features)); + features[0] = LMP_3SLOT | LMP_5SLOT | LMP_ENCRYPT; + features[4] = LMP_LE; + features[6] = LMP_SIMPLE_PAIR; + + s = lmp_featurestostr(features, NULL, 200); + printf("lmp_featurestostr(wide) = \"%s\"\n", s ? s : "(null)"); + free(s); + + s = lmp_featurestostr(features, "\t", 30); + printf("lmp_featurestostr(narrow) = \"%s\"\n", s ? s : "(null)"); + free(s); + + memset(features, 0xff, sizeof(features)); + s = lmp_featurestostr(features, NULL, 200); + printf("lmp_featurestostr(all) = \"%s\"\n", s ? s : "(null)"); + free(s); +} + +int main(void) +{ + dump_bus(); + dump_dflags(); + dump_ptype(); + dump_lp_lm(); + dump_commands(); + dump_versions(); + dump_features(); + return 0; +} From ed0be9a6435a47df37580e636137b0b1a6ef21a5 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 18:00:00 -0400 Subject: [PATCH 39/73] Add conformance compare script for BluetoothLinux drivers Builds conformance_hci_strings.c against both the system reference and libBluetoothLinuxABI.so and diffs the output, using the same known-differences.txt convention as the Bluetooth checkout. The phase-1 and SDP drivers stay in the Bluetooth checkout alongside the symbols they cover. --- Conformance/compare.sh | 117 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100755 Conformance/compare.sh diff --git a/Conformance/compare.sh b/Conformance/compare.sh new file mode 100755 index 0000000..236b512 --- /dev/null +++ b/Conformance/compare.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# +# Differential conformance: build each driver twice — once against the +# reference libbluetooth.so.3, once against ours — and diff the output. +# +# Drivers: +# conformance_hci_strings.c the hci_*tostr/hci_strto*, lmp_*, pal_* +# family (21 symbols). Exported directly +# by the system library, so this driver +# links against it as-is. +# +# The phase-1 (bluetooth.c + bt_uuid_*) and SDP drivers live in the +# PureSwift/Bluetooth checkout instead, alongside the symbols they +# cover — see Conformance/README.md. +# +# Both sides of each comparison compile against the *same* (vendored) +# headers, so the only variable is which implementation is linked. +# +# Usage: +# SWIFTPM_BLUETOOTH_CABI=1 swift build +# Conformance/compare.sh [path-to-libBluetoothLinuxABI.so] +# +# Environment: +# BT_REFERENCE_LIB path to the reference libbluetooth.so.3 +# +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BUILD="${ROOT}/.build/conformance" +TRIPLE="$(swift -print-target-info | sed -n 's/.*"unversionedTriple": "\([^"]*\)".*/\1/p' | head -1)" +OURS="${1:-${ROOT}/.build/${TRIPLE}/debug/libBluetoothLinuxABI.so}" + +find_reference() { + local found + found="$(/sbin/ldconfig -p 2>/dev/null \ + | sed -n 's/.*libbluetooth\.so\.3 (libc6[^)]*) => \(.*\)/\1/p' | head -1)" + if [[ -n "${found}" ]]; then + echo "${found}" + return + fi + local dir + for dir in /usr/lib/"$(uname -m)"-linux-gnu /usr/lib64 /usr/lib /lib; do + if [[ -f "${dir}/libbluetooth.so.3" ]]; then + echo "${dir}/libbluetooth.so.3" + return + fi + done +} + +REFERENCE="${BT_REFERENCE_LIB:-$(find_reference)}" + +if [[ ! -f "${OURS}" ]]; then + echo "error: ${OURS} not found; run 'SWIFTPM_BLUETOOTH_CABI=1 swift build' first" >&2 + exit 1 +fi + +if [[ -z "${REFERENCE}" || ! -f "${REFERENCE}" ]]; then + echo "error: reference libbluetooth.so.3 not found; install libbluetooth3 or set BT_REFERENCE_LIB" >&2 + exit 1 +fi + +mkdir -p "${BUILD}" + +# The drivers include , so stage the vendored headers +# under that prefix and use them for every build. +mkdir -p "${BUILD}/include/bluetooth" +cp "${ROOT}"/Sources/CBluetoothLinuxABI/include/bluetooth/*.h "${BUILD}/include/bluetooth/" + +CFLAGS=(-O0 -g -I "${BUILD}/include") +status=0 + +compare() { + local name="$1" reference="$2" ours="$3" + + "${reference}" > "${BUILD}/${name}.reference.txt" 2>&1 || true + "${ours}" > "${BUILD}/${name}.ours.txt" 2>&1 || true + + if diff -u "${BUILD}/${name}.reference.txt" "${BUILD}/${name}.ours.txt" \ + > "${BUILD}/${name}.diff.txt"; then + echo "conformance/${name}: identical output ($(wc -l < "${BUILD}/${name}.reference.txt") lines)" + return 0 + fi + + grep -E '^[+-]' "${BUILD}/${name}.diff.txt" | grep -vE '^(\+\+\+|---)' | sed 's/^[+-]//' \ + | sort -u > "${BUILD}/${name}.changed.txt" + + if [[ -f "${ROOT}/Conformance/known-differences.txt" ]]; then + grep -vE '^\s*(#|$)' "${ROOT}/Conformance/known-differences.txt" | sort -u \ + > "${BUILD}/known.txt" + else + : > "${BUILD}/known.txt" + fi + + comm -23 "${BUILD}/${name}.changed.txt" "${BUILD}/known.txt" \ + > "${BUILD}/${name}.unexpected.txt" + + if [[ -s "${BUILD}/${name}.unexpected.txt" ]]; then + echo "conformance/${name}: $(wc -l < "${BUILD}/${name}.unexpected.txt") unexpected differing line(s):" >&2 + head -50 "${BUILD}/${name}.unexpected.txt" >&2 + echo "(full diff: ${BUILD}/${name}.diff.txt)" >&2 + return 1 + fi + + echo "conformance/${name}: all $(wc -l < "${BUILD}/${name}.changed.txt") differing lines are known-accepted" +} + +# --- HCI string converters ---------------------------------------------- + +cc "${CFLAGS[@]}" -o "${BUILD}/hci_strings.reference" \ + "${ROOT}/Conformance/conformance_hci_strings.c" "${REFERENCE}" +cc "${CFLAGS[@]}" -o "${BUILD}/hci_strings.ours" \ + "${ROOT}/Conformance/conformance_hci_strings.c" "${OURS}" \ + -Wl,-rpath,"$(dirname "${OURS}")" + +compare hci_strings "${BUILD}/hci_strings.reference" "${BUILD}/hci_strings.ours" || status=1 + +exit "${status}" From fbda7ae951b4fdf593d6f00fa76b763b5f835913 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 18:00:05 -0400 Subject: [PATCH 40/73] Wire BluetoothSDP and BluetoothLinuxABI into the CMake build Adds a BluetoothLinuxABI static library target and links it, along with BluetoothSDP, into the final bluetooth3 artifact via --whole-archive. BluetoothSDP was previously implemented but never actually linked into the CMake build. --- CMakeLists.txt | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad6080d..7491b49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,22 @@ add_library(CBluetoothLinuxABI STATIC ${LIBBLUETOOTH_STUBS}) target_include_directories(CBluetoothLinuxABI PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/Sources/CBluetoothLinuxABI/include") +# --------------------------------------------------------------------- +# BluetoothLinuxABI — the Swift implementations of the C entry points +# this repository owns (currently: the HCI string converter family) +# --------------------------------------------------------------------- + +file(GLOB BLUETOOTH_LINUX_ABI_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/Sources/BluetoothLinuxABI/*.swift" + "${CMAKE_CURRENT_SOURCE_DIR}/Sources/BluetoothLinuxABI/gen/*.swift") + +add_library(BluetoothLinuxABI STATIC ${BLUETOOTH_LINUX_ABI_SOURCES}) + +set_target_properties(BluetoothLinuxABI PROPERTIES + Swift_MODULE_NAME BluetoothLinuxABI) + +target_link_libraries(BluetoothLinuxABI PUBLIC CBluetoothLinuxABI) + # --------------------------------------------------------------------- # The non-socket half, from PureSwift/Bluetooth # --------------------------------------------------------------------- @@ -176,11 +192,13 @@ set_target_properties(bluetooth3 PROPERTIES target_link_options(bluetooth3 PRIVATE "LINKER:--whole-archive" "LINKER:$" + "LINKER:$" + "LINKER:$" "LINKER:$" "LINKER:$" "LINKER:--no-whole-archive") -add_dependencies(bluetooth3 BluetoothABI CBluetooth CBluetoothLinuxABI) +add_dependencies(bluetooth3 BluetoothABI BluetoothSDP BluetoothLinuxABI CBluetooth CBluetoothLinuxABI) target_link_libraries(bluetooth3 PRIVATE Bluetooth) From f533b48faa40b83bba3ec97d31319924bd82e5b5 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 18:43:11 -0400 Subject: [PATCH 41/73] Implement HCI device management family MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hci_open_dev, hci_close_dev, hci_devinfo, hci_devba, hci_devid, hci_for_each_dev, hci_get_route, hci_send_cmd, and hci_send_req — the raw AF_BLUETOOTH/BTPROTO_HCI socket layer that the rest of the HCI command wrappers build on. These call socket/bind/ioctl/writev/poll directly rather than routing through BluetoothLinux's own async HostController/Socket infrastructure, since the ABI surface has to be synchronous and match the reference's wire layout precisely. hci_send_cmd and hci_send_req haven't been exercised against a real or virtual HCI device yet — no differential conformance harness for this family exists. --- Sources/BluetoothLinuxABI/HCIDevice.swift | 432 ++++++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/HCIDevice.swift diff --git a/Sources/BluetoothLinuxABI/HCIDevice.swift b/Sources/BluetoothLinuxABI/HCIDevice.swift new file mode 100644 index 0000000..433eee5 --- /dev/null +++ b/Sources/BluetoothLinuxABI/HCIDevice.swift @@ -0,0 +1,432 @@ +// +// HCIDevice.swift +// BluetoothLinux +// +// Swift implementations of the HCI device management family: opening +// and closing a raw HCI socket, looking up device info/address/id, +// enumerating devices, routing, and sending raw commands/requests. +// +// These talk to the kernel directly via AF_BLUETOOTH/BTPROTO_HCI raw +// sockets and the HCIGETDEVINFO/HCIGETDEVLIST ioctls, mirroring +// lib/hci.c exactly, rather than routing through BluetoothLinux's own +// (async, higher-level) HostController/Socket infrastructure — the +// ABI surface has to be synchronous and match the reference's wire +// layout precisely, which a bridge into the async engine would not +// make any simpler. +// +// hci_send_cmd/hci_send_req have not been exercised against a live +// or virtual HCI device (no differential conformance harness for +// this family yet); they're a direct, careful translation of +// lib/hci.c's hci_send_cmd/hci_send_req. +// + +import CBluetoothLinuxABI +import Glibc + +// MARK: - Opcode packing (cmd_opcode_pack is a function-like macro, not imported) + +private func hciOpcode(ogf: UInt16, ocf: UInt16) -> UInt16 { + (ocf & 0x03ff) | (ogf << 10) +} + +// MARK: - ioctl request codes (_IOR('H', nr, int) macros; not imported — +// ClangImporter can't evaluate function-like macros that use sizeof). +// Values confirmed against the vendored header with a small C program. + +private let hciGetDeviceList: UInt = 0x800448d2 +private let hciGetDeviceInfo: UInt = 0x800448d3 + +// MARK: - atoi-equivalent (BlueZ's hci_devid uses atoi(str + 3)) + +private func atoiPrefix(_ string: Substring) -> Int32 { + var characters = string + while let first = characters.first, first == " " || first == "\t" || first == "\n" { + characters.removeFirst() + } + var sign: Int32 = 1 + if let first = characters.first, first == "+" || first == "-" { + if first == "-" { sign = -1 } + characters.removeFirst() + } + var value: Int32 = 0 + while let first = characters.first, let digit = first.wholeNumberValue, first.isASCII, digit <= 9 { + value = value &* 10 &+ Int32(digit) + characters.removeFirst() + } + return sign &* value +} + +// MARK: - Open / close + +/// `int hci_open_dev(int dev_id)` +@c(hci_open_dev) +public func hci_open_dev(_ devID: Int32) -> Int32 { + guard devID >= 0 else { + errno = ENODEV + return -1 + } + let dd = socket(Int32(AF_BLUETOOTH), Int32(SOCK_RAW.rawValue) | Int32(SOCK_CLOEXEC.rawValue), Int32(BTPROTO_HCI)) + guard dd >= 0 else { return dd } + + var address = sockaddr_hci(hci_family: sa_family_t(AF_BLUETOOTH), hci_dev: UInt16(devID), hci_channel: 0) + let result = withUnsafeMutablePointer(to: &address) { pointer -> Int32 in + pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPointer in + bind(dd, sockaddrPointer, socklen_t(MemoryLayout.size)) + } + } + guard result >= 0 else { + let savedError = errno + close(dd) + errno = savedError + return -1 + } + return dd +} + +/// `int hci_close_dev(int dd)` +@c(hci_close_dev) +public func hci_close_dev(_ dd: Int32) -> Int32 { + close(dd) +} + +// MARK: - Device info / address / id + +/// `int hci_devinfo(int dev_id, struct hci_dev_info *di)` +@c(hci_devinfo) +public func hci_devinfo(_ devID: Int32, _ info: UnsafeMutablePointer?) -> Int32 { + guard let info else { return -1 } + let dd = socket(Int32(AF_BLUETOOTH), Int32(SOCK_RAW.rawValue) | Int32(SOCK_CLOEXEC.rawValue), Int32(BTPROTO_HCI)) + guard dd >= 0 else { return dd } + + memset(info, 0, MemoryLayout.size) + info.pointee.dev_id = UInt16(devID) + let result = ioctl(dd, numericCast(hciGetDeviceInfo), info) + + let savedError = errno + close(dd) + errno = savedError + return result +} + +/// `int hci_devba(int dev_id, bdaddr_t *bdaddr)` +@c(hci_devba) +public func hci_devba(_ devID: Int32, _ bdaddr: UnsafeMutablePointer?) -> Int32 { + var info = hci_dev_info() + guard hci_devinfo(devID, &info) >= 0 else { return -1 } + guard hci_test_bit(Int32(HCI_UP), &info.flags) != 0 else { + errno = ENETDOWN + return -1 + } + bdaddr?.pointee = info.bdaddr + return 0 +} + +/// `int hci_devid(const char *str)` +@c(hci_devid) +public func hci_devid(_ str: UnsafePointer?) -> Int32 { + guard let str else { return -1 } + let string = String(cString: str) + var id: Int32 = -1 + if string.hasPrefix("hci"), string.utf8.count >= 4 { + id = atoiPrefix(string.dropFirst(3)) + var address = bdaddr_t(b: (0, 0, 0, 0, 0, 0)) + guard hci_devba(id, &address) >= 0 else { return -1 } + } else { + errno = ENODEV + var address = bdaddr_t(b: (0, 0, 0, 0, 0, 0)) + str2ba(str, &address) + id = withUnsafeMutablePointer(to: &address) { + hci_for_each_dev(Int32(HCI_UP), sameBDAddress, Int(bitPattern: $0)) + } + } + return id +} + +// MARK: - Enumeration / routing + +private func sameBDAddress(_ dd: Int32, _ devID: Int32, _ arg: Int) -> Int32 { + var info = hci_dev_info() + info.dev_id = UInt16(devID) + guard ioctl(dd, numericCast(hciGetDeviceInfo), &info) == 0 else { return 0 } + guard let target = UnsafeRawPointer(bitPattern: arg)?.assumingMemoryBound(to: bdaddr_t.self) else { return 0 } + return bacmp(target, &info.bdaddr) == 0 ? 1 : 0 +} + +private func otherBDAddress(_ dd: Int32, _ devID: Int32, _ arg: Int) -> Int32 { + var info = hci_dev_info() + info.dev_id = UInt16(devID) + guard ioctl(dd, numericCast(hciGetDeviceInfo), &info) == 0 else { return 0 } + guard hci_test_bit(Int32(HCI_RAW), &info.flags) == 0 else { return 0 } + guard let target = UnsafeRawPointer(bitPattern: arg)?.assumingMemoryBound(to: bdaddr_t.self) else { return 0 } + return Int32(bacmp(target, &info.bdaddr)) +} + +/// `int hci_for_each_dev(int flag, int(*func)(int dd, int dev_id, long arg), long arg)` +@c(hci_for_each_dev) +public func hci_for_each_dev( + _ flag: Int32, + _ callback: (@convention(c) (Int32, Int32, Int) -> Int32)?, + _ arg: Int +) -> Int32 { + let sk = socket(Int32(AF_BLUETOOTH), Int32(SOCK_RAW.rawValue) | Int32(SOCK_CLOEXEC.rawValue), Int32(BTPROTO_HCI)) + guard sk >= 0 else { return -1 } + + let maxDevices = Int(HCI_MAX_DEV) + let headerSize = MemoryLayout.size + let entrySize = MemoryLayout.size + let byteCount = headerSize + maxDevices * entrySize + let buffer = UnsafeMutableRawPointer.allocate( + byteCount: byteCount, + alignment: MemoryLayout.alignment + ) + defer { buffer.deallocate() } + buffer.initializeMemory(as: UInt8.self, repeating: 0, count: byteCount) + + let listRequest = buffer.assumingMemoryBound(to: hci_dev_list_req.self) + listRequest.pointee.dev_num = UInt16(maxDevices) + + var deviceID: Int32 = -1 + var savedError: Int32 = 0 + + if ioctl(sk, numericCast(hciGetDeviceList), buffer) < 0 { + savedError = errno + } else { + let deviceRequests = buffer.advanced(by: headerSize).assumingMemoryBound(to: hci_dev_req.self) + let count = Int(listRequest.pointee.dev_num) + for i in 0 ..< count { + var options = deviceRequests[i].dev_opt + guard hci_test_bit(flag, &options) != 0 else { continue } + let id = Int32(deviceRequests[i].dev_id) + if callback == nil || callback!(sk, id, arg) != 0 { + deviceID = id + break + } + } + if deviceID < 0 { + savedError = Int32(ENODEV) + } + } + + close(sk) + errno = savedError + return deviceID +} + +/// `int hci_get_route(bdaddr_t *bdaddr)` +@c(hci_get_route) +public func hci_get_route(_ bdaddr: UnsafeMutablePointer?) -> Int32 { + func route(with target: UnsafeMutablePointer) -> Int32 { + let arg = Int(bitPattern: target) + var deviceID = hci_for_each_dev(Int32(HCI_UP), otherBDAddress, arg) + if deviceID < 0 { + deviceID = hci_for_each_dev(Int32(HCI_UP), sameBDAddress, arg) + } + return deviceID + } + if let bdaddr { + return route(with: bdaddr) + } + var any = bdaddr_t(b: (0, 0, 0, 0, 0, 0)) + return withUnsafeMutablePointer(to: &any) { route(with: $0) } +} + +// MARK: - Sending commands / requests + +/// `int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param)` +@c(hci_send_cmd) +public func hci_send_cmd(_ dd: Int32, _ ogf: UInt16, _ ocf: UInt16, _ plen: UInt8, _ param: UnsafeMutableRawPointer?) -> Int32 { + var packetType = UInt8(HCI_COMMAND_PKT) + var header = hci_command_hdr(opcode: hciOpcode(ogf: ogf, ocf: ocf), plen: plen) + + return withUnsafeMutableBytes(of: &packetType) { typeBuffer -> Int32 in + withUnsafeMutableBytes(of: &header) { headerBuffer -> Int32 in + var vectors = [ + iovec(iov_base: typeBuffer.baseAddress, iov_len: 1), + iovec(iov_base: headerBuffer.baseAddress, iov_len: headerBuffer.count) + ] + if plen > 0 { + vectors.append(iovec(iov_base: param, iov_len: Int(plen))) + } + while true { + let written = writev(dd, &vectors, Int32(vectors.count)) + if written < 0 { + if errno == EAGAIN || errno == EINTR { continue } + return -1 + } + return 0 + } + } + } +} + +/// `int hci_send_req(int dd, struct hci_request *req, int timeout)` +@c(hci_send_req) +public func hci_send_req(_ dd: Int32, _ request: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let request else { + errno = EINVAL + return -1 + } + let opcode = hciOpcode(ogf: request.pointee.ogf, ocf: request.pointee.ocf) + + var oldFilter = hci_filter() + var oldFilterLength = socklen_t(MemoryLayout.size) + guard getsockopt(dd, Int32(SOL_HCI), Int32(HCI_FILTER), &oldFilter, &oldFilterLength) >= 0 else { + return -1 + } + + var newFilter = hci_filter() + hci_filter_clear(&newFilter) + hci_filter_set_ptype(Int32(HCI_EVENT_PKT), &newFilter) + hci_filter_set_event(Int32(EVT_CMD_STATUS), &newFilter) + hci_filter_set_event(Int32(EVT_CMD_COMPLETE), &newFilter) + hci_filter_set_event(Int32(EVT_LE_META_EVENT), &newFilter) + hci_filter_set_event(request.pointee.event, &newFilter) + hci_filter_set_opcode(Int32(opcode), &newFilter) + guard setsockopt(dd, Int32(SOL_HCI), Int32(HCI_FILTER), &newFilter, socklen_t(MemoryLayout.size)) >= 0 else { + return -1 + } + + func restoreFilterAndFail() -> Int32 { + let savedError = errno + var restore = oldFilter + var finalError = savedError + if setsockopt(dd, Int32(SOL_HCI), Int32(HCI_FILTER), &restore, socklen_t(MemoryLayout.size)) < 0 { + finalError = errno + } + errno = finalError + return -1 + } + + func restoreFilterAndSucceed() -> Int32 { + var restore = oldFilter + guard setsockopt(dd, Int32(SOL_HCI), Int32(HCI_FILTER), &restore, socklen_t(MemoryLayout.size)) >= 0 else { + return -1 + } + return 0 + } + + guard hci_send_cmd( + dd, + request.pointee.ogf, + request.pointee.ocf, + UInt8(truncatingIfNeeded: request.pointee.clen), + request.pointee.cparam + ) >= 0 else { + return restoreFilterAndFail() + } + + var buffer = [UInt8](repeating: 0, count: Int(HCI_MAX_EVENT_SIZE)) + var remainingTimeout = timeout + var attempts = 10 + + while attempts > 0 { + attempts -= 1 + + if remainingTimeout != 0 { + var pfd = pollfd(fd: dd, events: Int16(POLLIN), revents: 0) + var pollResult: Int32 + repeat { + pollResult = poll(&pfd, 1, remainingTimeout) + } while pollResult < 0 && (errno == EAGAIN || errno == EINTR) + + if pollResult < 0 { + return restoreFilterAndFail() + } + if pollResult == 0 { + errno = ETIMEDOUT + return restoreFilterAndFail() + } + + remainingTimeout -= 10 + if remainingTimeout < 0 { remainingTimeout = 0 } + } + + var bytesRead: Int + repeat { + bytesRead = buffer.withUnsafeMutableBytes { read(dd, $0.baseAddress, $0.count) } + } while bytesRead < 0 && (errno == EAGAIN || errno == EINTR) + + if bytesRead < 0 { + return restoreFilterAndFail() + } + + let dataOffset = 1 + Int(HCI_EVENT_HDR_SIZE) + guard bytesRead >= dataOffset else { continue } + + let eventCode = Int32(buffer[1]) + let length = bytesRead - dataOffset + + switch eventCode { + case Int32(EVT_CMD_STATUS): + guard length >= MemoryLayout.size else { continue } + let status: evt_cmd_status = buffer.withUnsafeBytes { $0.loadUnaligned(fromByteOffset: dataOffset, as: evt_cmd_status.self) } + guard status.opcode == opcode else { continue } + if request.pointee.event != Int32(EVT_CMD_STATUS) { + if status.status != 0 { + errno = EIO + return restoreFilterAndFail() + } + } else { + let resultLength = min(length, Int(request.pointee.rlen)) + request.pointee.rlen = Int32(resultLength) + if let rparam = request.pointee.rparam { + _ = buffer.withUnsafeBytes { memcpy(rparam, $0.baseAddress!.advanced(by: dataOffset), resultLength) } + } + return restoreFilterAndSucceed() + } + + case Int32(EVT_CMD_COMPLETE): + guard length >= MemoryLayout.size else { continue } + let complete: evt_cmd_complete = buffer.withUnsafeBytes { $0.loadUnaligned(fromByteOffset: dataOffset, as: evt_cmd_complete.self) } + guard complete.opcode == opcode else { continue } + let paramOffset = dataOffset + Int(EVT_CMD_COMPLETE_SIZE) + let paramLength = length - Int(EVT_CMD_COMPLETE_SIZE) + let resultLength = min(paramLength, Int(request.pointee.rlen)) + request.pointee.rlen = Int32(resultLength) + if let rparam = request.pointee.rparam, resultLength > 0 { + _ = buffer.withUnsafeBytes { memcpy(rparam, $0.baseAddress!.advanced(by: paramOffset), resultLength) } + } + return restoreFilterAndSucceed() + + case Int32(EVT_REMOTE_NAME_REQ_COMPLETE): + guard eventCode == request.pointee.event else { break } + guard length >= MemoryLayout.size else { continue } + let addressOffset = dataOffset + 1 + var remoteAddress: bdaddr_t = buffer.withUnsafeBytes { $0.loadUnaligned(fromByteOffset: addressOffset, as: bdaddr_t.self) } + if let cparam = request.pointee.cparam { + var commandAddress = cparam.loadUnaligned(as: bdaddr_t.self) + guard bacmp(&remoteAddress, &commandAddress) == 0 else { continue } + } + let resultLength = min(length, Int(request.pointee.rlen)) + request.pointee.rlen = Int32(resultLength) + if let rparam = request.pointee.rparam { + _ = buffer.withUnsafeBytes { memcpy(rparam, $0.baseAddress!.advanced(by: dataOffset), resultLength) } + } + return restoreFilterAndSucceed() + + case Int32(EVT_LE_META_EVENT): + guard length >= 1 else { continue } + let subevent = Int32(buffer[dataOffset]) + guard subevent == request.pointee.event else { continue } + let metaLength = length - 1 + let resultLength = min(metaLength, Int(request.pointee.rlen)) + request.pointee.rlen = Int32(resultLength) + if let rparam = request.pointee.rparam { + _ = buffer.withUnsafeBytes { memcpy(rparam, $0.baseAddress!.advanced(by: dataOffset + 1), resultLength) } + } + return restoreFilterAndSucceed() + + default: + guard eventCode == request.pointee.event else { break } + let resultLength = min(length, Int(request.pointee.rlen)) + request.pointee.rlen = Int32(resultLength) + if let rparam = request.pointee.rparam { + _ = buffer.withUnsafeBytes { memcpy(rparam, $0.baseAddress!.advanced(by: dataOffset), resultLength) } + } + return restoreFilterAndSucceed() + } + } + + errno = ETIMEDOUT + return restoreFilterAndFail() +} From 56d0198b70a07491fd129cf5c0c96bd1dc8222c0 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 18:43:17 -0400 Subject: [PATCH 42/73] Add unit tests for HCI device management Covers only what doesn't require a real or virtual adapter: input validation that returns before any socket is touched, and hci_send_cmd's wire format, verified over a plain pipe. --- .../HCIDeviceTests.swift | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Tests/BluetoothLinuxABITests/HCIDeviceTests.swift diff --git a/Tests/BluetoothLinuxABITests/HCIDeviceTests.swift b/Tests/BluetoothLinuxABITests/HCIDeviceTests.swift new file mode 100644 index 0000000..50b1f73 --- /dev/null +++ b/Tests/BluetoothLinuxABITests/HCIDeviceTests.swift @@ -0,0 +1,92 @@ +// +// HCIDeviceTests.swift +// BluetoothLinuxABITests +// +// Covers only the parts of the HCI device management family that +// don't require a real or virtual Bluetooth adapter: input +// validation that returns before any socket is touched, and +// hci_send_cmd's wire format, verified over a plain pipe rather than +// an HCI socket. hci_devinfo/hci_devba/hci_for_each_dev/hci_get_route/ +// hci_send_req all need an actual AF_BLUETOOTH/BTPROTO_HCI device to +// exercise meaningfully and are covered by neither this file nor a +// conformance driver yet. +// + +import Testing +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import CBluetoothLinuxABI +@testable import BluetoothLinuxABI + +@Suite("HCI Device") +struct HCIDeviceTests { + + @Test("hci_open_dev rejects a negative device id before opening a socket") + func openDevRejectsNegativeID() { + errno = 0 + #expect(BluetoothLinuxABI.hci_open_dev(-1) == -1) + #expect(errno == ENODEV) + } + + @Test("hci_close_dev on an invalid descriptor fails like close(2)") + func closeDevInvalidDescriptor() { + #expect(BluetoothLinuxABI.hci_close_dev(-1) == -1) + } + + @Test("hci_send_cmd writes the exact HCI command packet layout") + func sendCmdPacketLayout() { + var fds: [Int32] = [0, 0] + let pipeResult = fds.withUnsafeMutableBufferPointer { pipe($0.baseAddress) } + #expect(pipeResult == 0) + let readEnd = fds[0] + let writeEnd = fds[1] + defer { + close(readEnd) + close(writeEnd) + } + + var parameter: UInt8 = 0x42 + let result = withUnsafeMutableBytes(of: ¶meter) { buffer in + BluetoothLinuxABI.hci_send_cmd(writeEnd, 0x03, 0x0003, 1, buffer.baseAddress) + } + #expect(result == 0) + + var received = [UInt8](repeating: 0, count: 5) + let bytesRead = received.withUnsafeMutableBytes { read(readEnd, $0.baseAddress, $0.count) } + #expect(bytesRead == 5) + + // HCI_COMMAND_PKT + #expect(received[0] == 0x01) + // opcode, little-endian: ocf | (ogf << 10) = 0x0003 | (0x03 << 10) = 0x0C03 + #expect(received[1] == 0x03) + #expect(received[2] == 0x0C) + // plen + #expect(received[3] == 1) + // the single parameter byte + #expect(received[4] == 0x42) + } + + @Test("hci_send_cmd omits the parameter iovec when plen is 0") + func sendCmdNoParameter() { + var fds: [Int32] = [0, 0] + let pipeResult = fds.withUnsafeMutableBufferPointer { pipe($0.baseAddress) } + #expect(pipeResult == 0) + let readEnd = fds[0] + let writeEnd = fds[1] + defer { + close(readEnd) + close(writeEnd) + } + + let result = BluetoothLinuxABI.hci_send_cmd(writeEnd, 0x01, 0x0001, 0, nil) + #expect(result == 0) + + var received = [UInt8](repeating: 0, count: 4) + let bytesRead = received.withUnsafeMutableBytes { read(readEnd, $0.baseAddress, $0.count) } + #expect(bytesRead == 4) + #expect(received[3] == 0) + } +} From 99f47bbaf6be3c9c339a9acee4abc8ea21e570c4 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 18:43:22 -0400 Subject: [PATCH 43/73] Mark HCI device management symbols as implemented --- scripts/implemented.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/implemented.txt b/scripts/implemented.txt index 98f1af6..2c837cc 100644 --- a/scripts/implemented.txt +++ b/scripts/implemented.txt @@ -143,3 +143,15 @@ lmp_strtover lmp_vertostr pal_strtover pal_vertostr + +# HCI device management (9), implemented in +# Sources/BluetoothLinuxABI/HCIDevice.swift. +hci_open_dev +hci_close_dev +hci_devid +hci_devinfo +hci_devba +hci_for_each_dev +hci_get_route +hci_send_cmd +hci_send_req From 74cc41e448c288207e42c43cf5d768b8fb0d9e0e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 18:43:28 -0400 Subject: [PATCH 44/73] Regenerate C ABI stub table Drops the 9 stubs now covered by BluetoothLinuxABI's device management family (96 remaining, 132 implemented). --- Sources/CBluetoothLinuxABI/gen/cbt_stubs.c | 45 ---------------------- 1 file changed, 45 deletions(-) diff --git a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c index dec55e0..dd362a7 100644 --- a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c +++ b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c @@ -40,11 +40,6 @@ int hci_change_link_key(int dd, uint16_t handle, int to) cbt_unimplemented("hci_change_link_key"); } -int hci_close_dev(int dd) -{ - cbt_unimplemented("hci_close_dev"); -} - int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to) { cbt_unimplemented("hci_create_connection"); @@ -55,21 +50,6 @@ int hci_delete_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to) cbt_unimplemented("hci_delete_stored_link_key"); } -int hci_devba(int dev_id, bdaddr_t *bdaddr) -{ - cbt_unimplemented("hci_devba"); -} - -int hci_devid(const char *str) -{ - cbt_unimplemented("hci_devid"); -} - -int hci_devinfo(int dev_id, struct hci_dev_info *di) -{ - cbt_unimplemented("hci_devinfo"); -} - int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to) { cbt_unimplemented("hci_disconnect"); @@ -85,16 +65,6 @@ int hci_exit_park_mode(int dd, uint16_t handle, int to) cbt_unimplemented("hci_exit_park_mode"); } -int hci_for_each_dev(int flag, int(*func)(int dd, int dev_id, long arg), long arg) -{ - cbt_unimplemented("hci_for_each_dev"); -} - -int hci_get_route(bdaddr_t *bdaddr) -{ - cbt_unimplemented("hci_get_route"); -} - int hci_inquiry(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info **ii, long flags) { cbt_unimplemented("hci_inquiry"); @@ -175,11 +145,6 @@ int hci_le_set_scan_parameters(int dev_id, uint8_t type, uint16_t interval, uint cbt_unimplemented("hci_le_set_scan_parameters"); } -int hci_open_dev(int dev_id) -{ - cbt_unimplemented("hci_open_dev"); -} - int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval, uint16_t min_interval, int to) { cbt_unimplemented("hci_park_mode"); @@ -345,16 +310,6 @@ int hci_read_voice_setting(int dd, uint16_t *vs, int to) cbt_unimplemented("hci_read_voice_setting"); } -int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param) -{ - cbt_unimplemented("hci_send_cmd"); -} - -int hci_send_req(int dd, struct hci_request *req, int timeout) -{ - cbt_unimplemented("hci_send_req"); -} - int hci_set_afh_classification(int dd, uint8_t *map, int to) { cbt_unimplemented("hci_set_afh_classification"); From 9c02486c6e01c79fbd05439b7628a2141da9e153 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 19:03:07 -0400 Subject: [PATCH 45/73] Add shared HCI command request helpers hciCommand/hciStatus/hciRequest capture the send/check-status/copy-out shapes that every hci_send_req-based command wrapper in lib/hci.c repeats, so each wrapper is a short, direct transcription of its reference instead of duplicating the boilerplate. --- Sources/BluetoothLinuxABI/HCIRequest.swift | 153 +++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/HCIRequest.swift diff --git a/Sources/BluetoothLinuxABI/HCIRequest.swift b/Sources/BluetoothLinuxABI/HCIRequest.swift new file mode 100644 index 0000000..8af46da --- /dev/null +++ b/Sources/BluetoothLinuxABI/HCIRequest.swift @@ -0,0 +1,153 @@ +// +// HCIRequest.swift +// BluetoothLinux +// +// Shared plumbing for the HCI command wrapper family: every wrapper in +// lib/hci.c follows one of a small number of shapes — build a command +// struct, call hci_send_req, check a status byte, optionally copy +// fields out of the response. These helpers capture those shapes once +// so each wrapper function is a short, direct transcription of its +// reference instead of repeating the send/check boilerplate. +// +// None of this has been exercised against a real or virtual HCI +// device; see HCIDevice.swift's note on hci_send_cmd/hci_send_req. +// + +import CBluetoothLinuxABI +import Glibc + +/// A response struct whose first field is the standard one-byte HCI +/// command status (0 = success). Nearly every `*_rp`/`evt_*` struct in +/// hci.h has this shape; conforming a type only asserts that its first +/// byte is the status, which the C struct layout already guarantees. +internal protocol HCIStatusResponse { + init() + var status: UInt8 { get } +} + +/// Placeholder for commands that take no parameters. +internal struct HCIEmptyCommand {} + +/// Sends `command` and returns `hci_send_req`'s raw result, with no +/// response payload (`rparam`/`rlen` left at zero) — mirrors the +/// wrappers that call `hci_send_req` directly and return its result +/// (or check nothing at all). +@discardableResult +internal func hciCommand( + _ dd: Int32, + ogf: Int32, + ocf: Int32, + event: Int32 = 0, + command: Command, + timeout: Int32 +) -> Int32 { + var command = command + var request = hci_request() + request.ogf = UInt16(ogf) + request.ocf = UInt16(ocf) + request.event = event + request.clen = Int32(MemoryLayout.size) + return withUnsafeMutableBytes(of: &command) { commandBuffer -> Int32 in + request.cparam = MemoryLayout.size > 0 ? commandBuffer.baseAddress : nil + return withUnsafeMutablePointer(to: &request) { hci_send_req(dd, $0, timeout) } + } +} + +@discardableResult +internal func hciCommand(_ dd: Int32, ogf: Int32, ocf: Int32, event: Int32 = 0, timeout: Int32) -> Int32 { + hciCommand(dd, ogf: ogf, ocf: ocf, event: event, command: HCIEmptyCommand(), timeout: timeout) +} + +/// Sends `commandBytes` verbatim as the command parameter — for the +/// handful of wrappers whose parameter is a hand-assembled, variable +/// length byte buffer rather than a fixed C struct. +@discardableResult +internal func hciCommand(_ dd: Int32, ogf: Int32, ocf: Int32, event: Int32 = 0, commandBytes: [UInt8], timeout: Int32) -> Int32 { + var bytes = commandBytes + var request = hci_request() + request.ogf = UInt16(ogf) + request.ocf = UInt16(ocf) + request.event = event + request.clen = Int32(bytes.count) + return bytes.withUnsafeMutableBytes { buffer -> Int32 in + request.cparam = buffer.baseAddress + return withUnsafeMutablePointer(to: &request) { hci_send_req(dd, $0, timeout) } + } +} + +/// Sends `command` and reads back a single status byte — the pattern +/// used by every `hci_le_*` wrapper that doesn't decode a response +/// struct (BlueZ declares a bare `uint8_t status;` local for these +/// rather than a named `_rp` type). +internal func hciStatus(_ dd: Int32, ogf: Int32, ocf: Int32, command: Command, timeout: Int32) -> Int32 { + var command = command + var status: UInt8 = 0 + var request = hci_request() + request.ogf = UInt16(ogf) + request.ocf = UInt16(ocf) + request.clen = Int32(MemoryLayout.size) + request.rlen = 1 + let result: Int32 = withUnsafeMutableBytes(of: &command) { commandBuffer in + withUnsafeMutableBytes(of: &status) { statusBuffer -> Int32 in + request.cparam = MemoryLayout.size > 0 ? commandBuffer.baseAddress : nil + request.rparam = statusBuffer.baseAddress + return withUnsafeMutablePointer(to: &request) { hci_send_req(dd, $0, timeout) } + } + } + guard result >= 0 else { return -1 } + guard status == 0 else { + errno = EIO + return -1 + } + return 0 +} + +internal func hciStatus(_ dd: Int32, ogf: Int32, ocf: Int32, timeout: Int32) -> Int32 { + hciStatus(dd, ogf: ogf, ocf: ocf, command: HCIEmptyCommand(), timeout: timeout) +} + +/// Sends `command`, decodes the response into `Response`, and checks +/// its `status` field. Returns `nil` (with `errno` set) on failure — +/// the shape used by nearly every `hci_read_*`/`hci_write_*` wrapper. +internal func hciRequest( + _ dd: Int32, + ogf: Int32, + ocf: Int32, + event: Int32 = 0, + command: Command, + response: Response.Type, + timeout: Int32 +) -> Response? { + var command = command + var response = Response() + var request = hci_request() + request.ogf = UInt16(ogf) + request.ocf = UInt16(ocf) + request.event = event + request.clen = Int32(MemoryLayout.size) + request.rlen = Int32(MemoryLayout.size) + let result: Int32 = withUnsafeMutableBytes(of: &command) { commandBuffer in + withUnsafeMutableBytes(of: &response) { responseBuffer -> Int32 in + request.cparam = MemoryLayout.size > 0 ? commandBuffer.baseAddress : nil + request.rparam = responseBuffer.baseAddress + return withUnsafeMutablePointer(to: &request) { hci_send_req(dd, $0, timeout) } + } + } + guard result >= 0 else { return nil } + guard response.status == 0 else { + errno = EIO + return nil + } + return response +} + +internal func hciRequest( + _ dd: Int32, + ogf: Int32, + ocf: Int32, + event: Int32 = 0, + response: Response.Type, + timeout: Int32 +) -> Response? { + hciRequest(dd, ogf: ogf, ocf: ocf, event: event, command: HCIEmptyCommand(), response: response, timeout: timeout) +} From 3c62693326631fad5f1288d2847b73569d94429f Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 19:03:13 -0400 Subject: [PATCH 46/73] Implement connection and link-policy HCI command wrappers hci_create_connection, hci_disconnect, hci_authenticate_link, hci_encrypt_link, hci_change_link_key, hci_switch_role, hci_park_mode, and hci_exit_park_mode. --- .../HCICommandsConnection.swift | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/HCICommandsConnection.swift diff --git a/Sources/BluetoothLinuxABI/HCICommandsConnection.swift b/Sources/BluetoothLinuxABI/HCICommandsConnection.swift new file mode 100644 index 0000000..ab19e97 --- /dev/null +++ b/Sources/BluetoothLinuxABI/HCICommandsConnection.swift @@ -0,0 +1,138 @@ +// +// HCICommandsConnection.swift +// BluetoothLinux +// +// Connection- and link-management HCI command wrappers: establishing +// and tearing down ACL links, and the link-policy state changes that +// wait for their own completion event rather than an immediate +// command-complete (authentication, encryption, role switch, park +// mode, link key changes). +// + +import CBluetoothLinuxABI +import Glibc + +extension evt_conn_complete: HCIStatusResponse {} +extension evt_disconn_complete: HCIStatusResponse {} +extension evt_auth_complete: HCIStatusResponse {} +extension evt_encrypt_change: HCIStatusResponse {} +extension evt_change_conn_link_key_complete: HCIStatusResponse {} +extension evt_role_change: HCIStatusResponse {} +extension evt_mode_change: HCIStatusResponse {} + +/// `int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to)` +@c(hci_create_connection) +public func hci_create_connection( + _ dd: Int32, + _ bdaddr: UnsafePointer?, + _ ptype: UInt16, + _ clockOffset: UInt16, + _ roleSwitch: UInt8, + _ handle: UnsafeMutablePointer?, + _ timeout: Int32 +) -> Int32 { + guard let bdaddr else { errno = EINVAL; return -1 } + var command = create_conn_cp() + command.bdaddr = bdaddr.pointee + command.pkt_type = ptype + command.pscan_rep_mode = 0x02 + command.clock_offset = clockOffset + command.role_switch = roleSwitch + + guard let response = hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_CREATE_CONN), event: Int32(EVT_CONN_COMPLETE), + command: command, response: evt_conn_complete.self, timeout: timeout + ) else { return -1 } + handle?.pointee = response.handle + return 0 +} + +/// `int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to)` +@c(hci_disconnect) +public func hci_disconnect(_ dd: Int32, _ handle: UInt16, _ reason: UInt8, _ timeout: Int32) -> Int32 { + var command = disconnect_cp() + command.handle = handle + command.reason = reason + guard hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_DISCONNECT), event: Int32(EVT_DISCONN_COMPLETE), + command: command, response: evt_disconn_complete.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +/// `int hci_authenticate_link(int dd, uint16_t handle, int to)` +@c(hci_authenticate_link) +public func hci_authenticate_link(_ dd: Int32, _ handle: UInt16, _ timeout: Int32) -> Int32 { + var command = auth_requested_cp() + command.handle = handle + guard hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_AUTH_REQUESTED), event: Int32(EVT_AUTH_COMPLETE), + command: command, response: evt_auth_complete.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +/// `int hci_encrypt_link(int dd, uint16_t handle, uint8_t encrypt, int to)` +@c(hci_encrypt_link) +public func hci_encrypt_link(_ dd: Int32, _ handle: UInt16, _ encrypt: UInt8, _ timeout: Int32) -> Int32 { + var command = set_conn_encrypt_cp() + command.handle = handle + command.encrypt = encrypt + guard hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_SET_CONN_ENCRYPT), event: Int32(EVT_ENCRYPT_CHANGE), + command: command, response: evt_encrypt_change.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +/// `int hci_change_link_key(int dd, uint16_t handle, int to)` +@c(hci_change_link_key) +public func hci_change_link_key(_ dd: Int32, _ handle: UInt16, _ timeout: Int32) -> Int32 { + var command = change_conn_link_key_cp() + command.handle = handle + guard hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_CHANGE_CONN_LINK_KEY), event: Int32(EVT_CHANGE_CONN_LINK_KEY_COMPLETE), + command: command, response: evt_change_conn_link_key_complete.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +/// `int hci_switch_role(int dd, bdaddr_t *bdaddr, uint8_t role, int to)` +@c(hci_switch_role) +public func hci_switch_role(_ dd: Int32, _ bdaddr: UnsafeMutablePointer?, _ role: UInt8, _ timeout: Int32) -> Int32 { + guard let bdaddr else { errno = EINVAL; return -1 } + var command = switch_role_cp() + command.bdaddr = bdaddr.pointee + command.role = role + guard hciRequest( + dd, ogf: Int32(OGF_LINK_POLICY), ocf: Int32(OCF_SWITCH_ROLE), event: Int32(EVT_ROLE_CHANGE), + command: command, response: evt_role_change.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +/// `int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval, uint16_t min_interval, int to)` +@c(hci_park_mode) +public func hci_park_mode(_ dd: Int32, _ handle: UInt16, _ maxInterval: UInt16, _ minInterval: UInt16, _ timeout: Int32) -> Int32 { + var command = park_mode_cp() + command.handle = handle + command.max_interval = maxInterval + command.min_interval = minInterval + guard hciRequest( + dd, ogf: Int32(OGF_LINK_POLICY), ocf: Int32(OCF_PARK_MODE), event: Int32(EVT_MODE_CHANGE), + command: command, response: evt_mode_change.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +/// `int hci_exit_park_mode(int dd, uint16_t handle, int to)` +@c(hci_exit_park_mode) +public func hci_exit_park_mode(_ dd: Int32, _ handle: UInt16, _ timeout: Int32) -> Int32 { + var command = exit_park_mode_cp() + command.handle = handle + guard hciRequest( + dd, ogf: Int32(OGF_LINK_POLICY), ocf: Int32(OCF_EXIT_PARK_MODE), event: Int32(EVT_MODE_CHANGE), + command: command, response: evt_mode_change.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} From 4facdb1770f5084942f228ad6f1299ee0199b6c7 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 19:03:18 -0400 Subject: [PATCH 47/73] Implement remote-device query HCI command wrappers hci_read_remote_name(_with_clock_offset), hci_read_remote_name_cancel, hci_read_remote_version, hci_read_remote_features, hci_read_remote_ext_features, and hci_read_clock_offset. --- .../BluetoothLinuxABI/HCICommandsRemote.swift | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/HCICommandsRemote.swift diff --git a/Sources/BluetoothLinuxABI/HCICommandsRemote.swift b/Sources/BluetoothLinuxABI/HCICommandsRemote.swift new file mode 100644 index 0000000..1d04596 --- /dev/null +++ b/Sources/BluetoothLinuxABI/HCICommandsRemote.swift @@ -0,0 +1,137 @@ +// +// HCICommandsRemote.swift +// BluetoothLinux +// +// Wrappers that query a *remote* device over an existing link: name, +// version, features, and clock offset. These wait for the matching +// completion event rather than an immediate command-complete. +// + +import CBluetoothLinuxABI +import Glibc + +extension evt_remote_name_req_complete: HCIStatusResponse {} +extension evt_read_remote_version_complete: HCIStatusResponse {} +extension evt_read_remote_features_complete: HCIStatusResponse {} +extension evt_read_remote_ext_features_complete: HCIStatusResponse {} +extension evt_read_clock_offset_complete: HCIStatusResponse {} + +/// `int hci_read_remote_name_with_clock_offset(int dd, const bdaddr_t *bdaddr, uint8_t pscan_rep_mode, uint16_t clkoffset, int len, char *name, int to)` +@c(hci_read_remote_name_with_clock_offset) +public func hci_read_remote_name_with_clock_offset( + _ dd: Int32, + _ bdaddr: UnsafePointer?, + _ pscanRepMode: UInt8, + _ clockOffset: UInt16, + _ len: Int32, + _ name: UnsafeMutablePointer?, + _ timeout: Int32 +) -> Int32 { + guard let bdaddr, let name, len > 0 else { errno = EINVAL; return -1 } + var command = remote_name_req_cp() + command.bdaddr = bdaddr.pointee + command.pscan_rep_mode = pscanRepMode + command.clock_offset = clockOffset + + guard let response = hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_REMOTE_NAME_REQ), event: Int32(EVT_REMOTE_NAME_REQ_COMPLETE), + command: command, response: evt_remote_name_req_complete.self, timeout: timeout + ) else { return -1 } + + var responseName = response.name + withUnsafeMutableBytes(of: &responseName) { buffer in + buffer[247] = 0 + let source = buffer.bindMemory(to: CChar.self) + _ = strncpy(name, source.baseAddress!, Int(len)) + } + return 0 +} + +/// `int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to)` +@c(hci_read_remote_name) +public func hci_read_remote_name( + _ dd: Int32, + _ bdaddr: UnsafePointer?, + _ len: Int32, + _ name: UnsafeMutablePointer?, + _ timeout: Int32 +) -> Int32 { + hci_read_remote_name_with_clock_offset(dd, bdaddr, 0x02, 0x0000, len, name, timeout) +} + +/// `int hci_read_remote_name_cancel(int dd, const bdaddr_t *bdaddr, int to)` +@c(hci_read_remote_name_cancel) +public func hci_read_remote_name_cancel(_ dd: Int32, _ bdaddr: UnsafePointer?, _ timeout: Int32) -> Int32 { + guard let bdaddr else { errno = EINVAL; return -1 } + var command = remote_name_req_cancel_cp() + command.bdaddr = bdaddr.pointee + return hciCommand(dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_REMOTE_NAME_REQ_CANCEL), command: command, timeout: timeout) +} + +/// `int hci_read_remote_version(int dd, uint16_t handle, struct hci_version *ver, int to)` +@c(hci_read_remote_version) +public func hci_read_remote_version(_ dd: Int32, _ handle: UInt16, _ version: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let version else { errno = EINVAL; return -1 } + var command = read_remote_version_cp() + command.handle = handle + guard let response = hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_READ_REMOTE_VERSION), event: Int32(EVT_READ_REMOTE_VERSION_COMPLETE), + command: command, response: evt_read_remote_version_complete.self, timeout: timeout + ) else { return -1 } + version.pointee.manufacturer = response.manufacturer + version.pointee.lmp_ver = response.lmp_ver + version.pointee.lmp_subver = response.lmp_subver + return 0 +} + +/// `int hci_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to)` +@c(hci_read_remote_features) +public func hci_read_remote_features(_ dd: Int32, _ handle: UInt16, _ features: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + var command = read_remote_features_cp() + command.handle = handle + guard var response = hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_READ_REMOTE_FEATURES), event: Int32(EVT_READ_REMOTE_FEATURES_COMPLETE), + command: command, response: evt_read_remote_features_complete.self, timeout: timeout + ) else { return -1 } + if let features { + withUnsafeMutableBytes(of: &response.features) { features.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 8) } + } + return 0 +} + +/// `int hci_read_remote_ext_features(int dd, uint16_t handle, uint8_t page, uint8_t *max_page, uint8_t *features, int to)` +@c(hci_read_remote_ext_features) +public func hci_read_remote_ext_features( + _ dd: Int32, + _ handle: UInt16, + _ page: UInt8, + _ maxPage: UnsafeMutablePointer?, + _ features: UnsafeMutablePointer?, + _ timeout: Int32 +) -> Int32 { + var command = read_remote_ext_features_cp() + command.handle = handle + command.page_num = page + guard var response = hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_READ_REMOTE_EXT_FEATURES), event: Int32(EVT_READ_REMOTE_EXT_FEATURES_COMPLETE), + command: command, response: evt_read_remote_ext_features_complete.self, timeout: timeout + ) else { return -1 } + maxPage?.pointee = response.max_page_num + if let features { + withUnsafeMutableBytes(of: &response.features) { features.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 8) } + } + return 0 +} + +/// `int hci_read_clock_offset(int dd, uint16_t handle, uint16_t *clkoffset, int to)` +@c(hci_read_clock_offset) +public func hci_read_clock_offset(_ dd: Int32, _ handle: UInt16, _ clockOffset: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + var command = read_clock_offset_cp() + command.handle = handle + guard let response = hciRequest( + dd, ogf: Int32(OGF_LINK_CTL), ocf: Int32(OCF_READ_CLOCK_OFFSET), event: Int32(EVT_READ_CLOCK_OFFSET_COMPLETE), + command: command, response: evt_read_clock_offset_complete.self, timeout: timeout + ) else { return -1 } + clockOffset?.pointee = response.clock_offset + return 0 +} From 14635ae72a893531b8f2e25e570453d52f6f3d72 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 19:03:24 -0400 Subject: [PATCH 48/73] Implement local controller info HCI command wrappers hci_read_local_version, hci_read_local_commands, hci_read_local_features, hci_read_local_ext_features, hci_read_bd_addr, hci_read_local_name, and hci_write_local_name. --- .../BluetoothLinuxABI/HCICommandsInfo.swift | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/HCICommandsInfo.swift diff --git a/Sources/BluetoothLinuxABI/HCICommandsInfo.swift b/Sources/BluetoothLinuxABI/HCICommandsInfo.swift new file mode 100644 index 0000000..c77762d --- /dev/null +++ b/Sources/BluetoothLinuxABI/HCICommandsInfo.swift @@ -0,0 +1,118 @@ +// +// HCICommandsInfo.swift +// BluetoothLinux +// +// Local controller information/identity: version, supported +// commands/features, address, and the local device name. +// + +import CBluetoothLinuxABI +import Glibc + +extension read_local_version_rp: HCIStatusResponse {} +extension read_local_commands_rp: HCIStatusResponse {} +extension read_local_features_rp: HCIStatusResponse {} +extension read_local_ext_features_rp: HCIStatusResponse {} +extension read_bd_addr_rp: HCIStatusResponse {} +extension read_local_name_rp: HCIStatusResponse {} + +/// `int hci_read_local_version(int dd, struct hci_version *ver, int to)` +@c(hci_read_local_version) +public func hci_read_local_version(_ dd: Int32, _ version: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let version else { errno = EINVAL; return -1 } + guard let response = hciRequest( + dd, ogf: Int32(OGF_INFO_PARAM), ocf: Int32(OCF_READ_LOCAL_VERSION), + response: read_local_version_rp.self, timeout: timeout + ) else { return -1 } + version.pointee.manufacturer = response.manufacturer + version.pointee.hci_ver = response.hci_ver + version.pointee.hci_rev = response.hci_rev + version.pointee.lmp_ver = response.lmp_ver + version.pointee.lmp_subver = response.lmp_subver + return 0 +} + +/// `int hci_read_local_commands(int dd, uint8_t *commands, int to)` +@c(hci_read_local_commands) +public func hci_read_local_commands(_ dd: Int32, _ commands: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard var response = hciRequest( + dd, ogf: Int32(OGF_INFO_PARAM), ocf: Int32(OCF_READ_LOCAL_COMMANDS), + response: read_local_commands_rp.self, timeout: timeout + ) else { return -1 } + if let commands { + withUnsafeMutableBytes(of: &response.commands) { commands.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 64) } + } + return 0 +} + +/// `int hci_read_local_features(int dd, uint8_t *features, int to)` +@c(hci_read_local_features) +public func hci_read_local_features(_ dd: Int32, _ features: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard var response = hciRequest( + dd, ogf: Int32(OGF_INFO_PARAM), ocf: Int32(OCF_READ_LOCAL_FEATURES), + response: read_local_features_rp.self, timeout: timeout + ) else { return -1 } + if let features { + withUnsafeMutableBytes(of: &response.features) { features.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 8) } + } + return 0 +} + +/// `int hci_read_local_ext_features(int dd, uint8_t page, uint8_t *max_page, uint8_t *features, int to)` +@c(hci_read_local_ext_features) +public func hci_read_local_ext_features( + _ dd: Int32, + _ page: UInt8, + _ maxPage: UnsafeMutablePointer?, + _ features: UnsafeMutablePointer?, + _ timeout: Int32 +) -> Int32 { + var command = read_local_ext_features_cp() + command.page_num = page + guard var response = hciRequest( + dd, ogf: Int32(OGF_INFO_PARAM), ocf: Int32(OCF_READ_LOCAL_EXT_FEATURES), + command: command, response: read_local_ext_features_rp.self, timeout: timeout + ) else { return -1 } + maxPage?.pointee = response.max_page_num + if let features { + withUnsafeMutableBytes(of: &response.features) { features.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 8) } + } + return 0 +} + +/// `int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to)` +@c(hci_read_bd_addr) +public func hci_read_bd_addr(_ dd: Int32, _ bdaddr: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_INFO_PARAM), ocf: Int32(OCF_READ_BD_ADDR), + response: read_bd_addr_rp.self, timeout: timeout + ) else { return -1 } + bdaddr?.pointee = response.bdaddr + return 0 +} + +/// `int hci_read_local_name(int dd, int len, char *name, int to)` +@c(hci_read_local_name) +public func hci_read_local_name(_ dd: Int32, _ len: Int32, _ name: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let name, len > 0 else { errno = EINVAL; return -1 } + guard var response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_LOCAL_NAME), + response: read_local_name_rp.self, timeout: timeout + ) else { return -1 } + withUnsafeMutableBytes(of: &response.name) { buffer in + buffer[247] = 0 + _ = strncpy(name, buffer.bindMemory(to: CChar.self).baseAddress!, Int(len)) + } + return 0 +} + +/// `int hci_write_local_name(int dd, const char *name, int to)` +@c(hci_write_local_name) +public func hci_write_local_name(_ dd: Int32, _ name: UnsafePointer?, _ timeout: Int32) -> Int32 { + guard let name else { errno = EINVAL; return -1 } + var command = change_local_name_cp() + withUnsafeMutableBytes(of: &command.name) { buffer in + _ = strncpy(buffer.bindMemory(to: CChar.self).baseAddress!, name, buffer.count - 1) + } + return hciCommand(dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_CHANGE_LOCAL_NAME), command: command, timeout: timeout) +} From 53403ad0a9160070ec52f5d93247704c409bf854 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 19:03:30 -0400 Subject: [PATCH 49/73] Implement LE HCI command wrappers White list and resolving list management, scan and advertising control, and LE connection establishment/update/remote-features query. --- Sources/BluetoothLinuxABI/HCICommandsLE.swift | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/HCICommandsLE.swift diff --git a/Sources/BluetoothLinuxABI/HCICommandsLE.swift b/Sources/BluetoothLinuxABI/HCICommandsLE.swift new file mode 100644 index 0000000..73256cf --- /dev/null +++ b/Sources/BluetoothLinuxABI/HCICommandsLE.swift @@ -0,0 +1,232 @@ +// +// HCICommandsLE.swift +// BluetoothLinux +// +// LE controller commands: the white list and resolving list (both +// used for LE connection/scan filtering), scan and advertising +// control, and LE connection establishment/update. +// + +import CBluetoothLinuxABI +import Glibc + +extension evt_le_connection_complete: HCIStatusResponse {} +extension evt_le_connection_update_complete: HCIStatusResponse {} +extension evt_le_read_remote_used_features_complete: HCIStatusResponse {} +extension le_read_white_list_size_rp: HCIStatusResponse {} +extension le_read_resolv_list_size_rp: HCIStatusResponse {} + +/// `int hci_le_add_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to)` +@c(hci_le_add_white_list) +public func hci_le_add_white_list(_ dd: Int32, _ bdaddr: UnsafePointer?, _ type: UInt8, _ timeout: Int32) -> Int32 { + guard let bdaddr else { errno = EINVAL; return -1 } + var command = le_add_device_to_white_list_cp() + command.bdaddr_type = type + command.bdaddr = bdaddr.pointee + return hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_ADD_DEVICE_TO_WHITE_LIST), command: command, timeout: timeout) +} + +/// `int hci_le_rm_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to)` +@c(hci_le_rm_white_list) +public func hci_le_rm_white_list(_ dd: Int32, _ bdaddr: UnsafePointer?, _ type: UInt8, _ timeout: Int32) -> Int32 { + guard let bdaddr else { errno = EINVAL; return -1 } + var command = le_remove_device_from_white_list_cp() + command.bdaddr_type = type + command.bdaddr = bdaddr.pointee + return hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST), command: command, timeout: timeout) +} + +/// `int hci_le_read_white_list_size(int dd, uint8_t *size, int to)` +@c(hci_le_read_white_list_size) +public func hci_le_read_white_list_size(_ dd: Int32, _ size: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_READ_WHITE_LIST_SIZE), + response: le_read_white_list_size_rp.self, timeout: timeout + ) else { return -1 } + size?.pointee = response.size + return 0 +} + +/// `int hci_le_clear_white_list(int dd, int to)` +@c(hci_le_clear_white_list) +public func hci_le_clear_white_list(_ dd: Int32, _ timeout: Int32) -> Int32 { + hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_CLEAR_WHITE_LIST), timeout: timeout) +} + +/// `int hci_le_add_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, uint8_t *peer_irk, uint8_t *local_irk, int to)` +@c(hci_le_add_resolving_list) +public func hci_le_add_resolving_list( + _ dd: Int32, + _ bdaddr: UnsafePointer?, + _ type: UInt8, + _ peerIRK: UnsafePointer?, + _ localIRK: UnsafePointer?, + _ timeout: Int32 +) -> Int32 { + guard let bdaddr else { errno = EINVAL; return -1 } + var command = le_add_device_to_resolv_list_cp() + command.bdaddr_type = type + command.bdaddr = bdaddr.pointee + if let peerIRK { + withUnsafeMutableBytes(of: &command.peer_irk) { $0.copyMemory(from: UnsafeRawBufferPointer(start: peerIRK, count: 16)) } + } + if let localIRK { + withUnsafeMutableBytes(of: &command.local_irk) { $0.copyMemory(from: UnsafeRawBufferPointer(start: localIRK, count: 16)) } + } + return hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_ADD_DEVICE_TO_RESOLV_LIST), command: command, timeout: timeout) +} + +/// `int hci_le_rm_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to)` +@c(hci_le_rm_resolving_list) +public func hci_le_rm_resolving_list(_ dd: Int32, _ bdaddr: UnsafePointer?, _ type: UInt8, _ timeout: Int32) -> Int32 { + guard let bdaddr else { errno = EINVAL; return -1 } + var command = le_remove_device_from_resolv_list_cp() + command.bdaddr_type = type + command.bdaddr = bdaddr.pointee + return hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_REMOVE_DEVICE_FROM_RESOLV_LIST), command: command, timeout: timeout) +} + +/// `int hci_le_clear_resolving_list(int dd, int to)` +@c(hci_le_clear_resolving_list) +public func hci_le_clear_resolving_list(_ dd: Int32, _ timeout: Int32) -> Int32 { + hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_CLEAR_RESOLV_LIST), timeout: timeout) +} + +/// `int hci_le_read_resolving_list_size(int dd, uint8_t *size, int to)` +@c(hci_le_read_resolving_list_size) +public func hci_le_read_resolving_list_size(_ dd: Int32, _ size: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_READ_RESOLV_LIST_SIZE), + response: le_read_resolv_list_size_rp.self, timeout: timeout + ) else { return -1 } + size?.pointee = response.size + return 0 +} + +/// `int hci_le_set_address_resolution_enable(int dd, uint8_t enable, int to)` +@c(hci_le_set_address_resolution_enable) +public func hci_le_set_address_resolution_enable(_ dd: Int32, _ enable: UInt8, _ timeout: Int32) -> Int32 { + var command = le_set_address_resolution_enable_cp() + command.enable = enable + return hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_SET_ADDRESS_RESOLUTION_ENABLE), command: command, timeout: timeout) +} + +/// `int hci_le_set_scan_enable(int dd, uint8_t enable, uint8_t filter_dup, int to)` +@c(hci_le_set_scan_enable) +public func hci_le_set_scan_enable(_ dd: Int32, _ enable: UInt8, _ filterDuplicates: UInt8, _ timeout: Int32) -> Int32 { + var command = le_set_scan_enable_cp() + command.enable = enable + command.filter_dup = filterDuplicates + return hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_SET_SCAN_ENABLE), command: command, timeout: timeout) +} + +/// `int hci_le_set_scan_parameters(int dd, uint8_t type, uint16_t interval, uint16_t window, uint8_t own_type, uint8_t filter, int to)` +@c(hci_le_set_scan_parameters) +public func hci_le_set_scan_parameters( + _ dd: Int32, + _ type: UInt8, + _ interval: UInt16, + _ window: UInt16, + _ ownType: UInt8, + _ filter: UInt8, + _ timeout: Int32 +) -> Int32 { + var command = le_set_scan_parameters_cp() + command.type = type + command.interval = interval + command.window = window + command.own_bdaddr_type = ownType + command.filter = filter + return hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_SET_SCAN_PARAMETERS), command: command, timeout: timeout) +} + +/// `int hci_le_set_advertise_enable(int dd, uint8_t enable, int to)` +@c(hci_le_set_advertise_enable) +public func hci_le_set_advertise_enable(_ dd: Int32, _ enable: UInt8, _ timeout: Int32) -> Int32 { + var command = le_set_advertise_enable_cp() + command.enable = enable + return hciStatus(dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_SET_ADVERTISE_ENABLE), command: command, timeout: timeout) +} + +/// `int hci_le_create_conn(int dd, uint16_t interval, uint16_t window, uint8_t initiator_filter, uint8_t peer_bdaddr_type, bdaddr_t peer_bdaddr, uint8_t own_bdaddr_type, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t supervision_timeout, uint16_t min_ce_length, uint16_t max_ce_length, uint16_t *handle, int to)` +@c(hci_le_create_conn) +public func hci_le_create_conn( + _ dd: Int32, + _ interval: UInt16, + _ window: UInt16, + _ initiatorFilter: UInt8, + _ peerAddressType: UInt8, + _ peerAddress: bdaddr_t, + _ ownAddressType: UInt8, + _ minInterval: UInt16, + _ maxInterval: UInt16, + _ latency: UInt16, + _ supervisionTimeout: UInt16, + _ minCELength: UInt16, + _ maxCELength: UInt16, + _ handle: UnsafeMutablePointer?, + _ timeout: Int32 +) -> Int32 { + var command = le_create_connection_cp() + command.interval = interval + command.window = window + command.initiator_filter = initiatorFilter + command.peer_bdaddr_type = peerAddressType + command.peer_bdaddr = peerAddress + command.own_bdaddr_type = ownAddressType + command.min_interval = minInterval + command.max_interval = maxInterval + command.latency = latency + command.supervision_timeout = supervisionTimeout + command.min_ce_length = minCELength + command.max_ce_length = maxCELength + + guard let response = hciRequest( + dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_CREATE_CONN), event: Int32(EVT_LE_CONN_COMPLETE), + command: command, response: evt_le_connection_complete.self, timeout: timeout + ) else { return -1 } + handle?.pointee = response.handle + return 0 +} + +/// `int hci_le_conn_update(int dd, uint16_t handle, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t supervision_timeout, int to)` +@c(hci_le_conn_update) +public func hci_le_conn_update( + _ dd: Int32, + _ handle: UInt16, + _ minInterval: UInt16, + _ maxInterval: UInt16, + _ latency: UInt16, + _ supervisionTimeout: UInt16, + _ timeout: Int32 +) -> Int32 { + var command = le_connection_update_cp() + command.handle = handle + command.min_interval = minInterval + command.max_interval = maxInterval + command.latency = latency + command.supervision_timeout = supervisionTimeout + command.min_ce_length = 0x0001 + command.max_ce_length = 0x0001 + + guard hciRequest( + dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_CONN_UPDATE), event: Int32(EVT_LE_CONN_UPDATE_COMPLETE), + command: command, response: evt_le_connection_update_complete.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +/// `int hci_le_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to)` +@c(hci_le_read_remote_features) +public func hci_le_read_remote_features(_ dd: Int32, _ handle: UInt16, _ features: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + var command = le_read_remote_used_features_cp() + command.handle = handle + guard var response = hciRequest( + dd, ogf: Int32(OGF_LE_CTL), ocf: Int32(OCF_LE_READ_REMOTE_USED_FEATURES), event: Int32(EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE), + command: command, response: evt_le_read_remote_used_features_complete.self, timeout: timeout + ) else { return -1 } + if let features { + withUnsafeMutableBytes(of: &response.features) { features.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 8) } + } + return 0 +} From b8cef5fc08ee2e573f50e926ff6bdb0963dd7e3c Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 19:03:36 -0400 Subject: [PATCH 50/73] Implement controller configuration HCI command wrappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Class of device, voice setting, inquiry access codes, stored link keys, inquiry/AFH/inquiry-mode toggles, extended inquiry response, simple pairing, OOB data, transmit power, link policy/supervision timeout, AFH classification, and per-connection link quality/RSSI/AFH map/clock queries — 33 symbols. --- .../BluetoothLinuxABI/HCICommandsConfig.swift | 468 ++++++++++++++++++ 1 file changed, 468 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/HCICommandsConfig.swift diff --git a/Sources/BluetoothLinuxABI/HCICommandsConfig.swift b/Sources/BluetoothLinuxABI/HCICommandsConfig.swift new file mode 100644 index 0000000..7015c04 --- /dev/null +++ b/Sources/BluetoothLinuxABI/HCICommandsConfig.swift @@ -0,0 +1,468 @@ +// +// HCICommandsConfig.swift +// BluetoothLinux +// +// Controller configuration and status-parameter wrappers: class of +// device, voice setting, inquiry access codes, stored link keys, +// inquiry/AFH/inquiry-mode toggles, extended inquiry response, simple +// pairing, OOB data, transmit power, link policy/supervision timeout, +// AFH classification, and per-connection link quality/RSSI/AFH +// map/clock queries. +// + +import CBluetoothLinuxABI +import Glibc + +extension read_class_of_dev_rp: HCIStatusResponse {} +extension read_voice_setting_rp: HCIStatusResponse {} +extension read_current_iac_lap_rp: HCIStatusResponse {} +extension read_inquiry_scan_type_rp: HCIStatusResponse {} +extension write_inquiry_scan_type_rp: HCIStatusResponse {} +extension read_inquiry_mode_rp: HCIStatusResponse {} +extension write_inquiry_mode_rp: HCIStatusResponse {} +extension read_afh_mode_rp: HCIStatusResponse {} +extension write_afh_mode_rp: HCIStatusResponse {} +extension read_ext_inquiry_response_rp: HCIStatusResponse {} +extension write_ext_inquiry_response_rp: HCIStatusResponse {} +extension read_simple_pairing_mode_rp: HCIStatusResponse {} +extension write_simple_pairing_mode_rp: HCIStatusResponse {} +extension read_local_oob_data_rp: HCIStatusResponse {} +extension read_inq_response_tx_power_level_rp: HCIStatusResponse {} +extension write_inquiry_transmit_power_level_rp: HCIStatusResponse {} +extension read_transmit_power_level_rp: HCIStatusResponse {} +extension read_link_policy_rp: HCIStatusResponse {} +extension write_link_policy_rp: HCIStatusResponse {} +extension read_link_supervision_timeout_rp: HCIStatusResponse {} +extension write_link_supervision_timeout_rp: HCIStatusResponse {} +extension set_afh_classification_rp: HCIStatusResponse {} +extension read_link_quality_rp: HCIStatusResponse {} +extension read_rssi_rp: HCIStatusResponse {} +extension read_afh_map_rp: HCIStatusResponse {} +extension read_clock_rp: HCIStatusResponse {} + +// MARK: - Class of device + +/// `int hci_read_class_of_dev(int dd, uint8_t *cls, int to)` +@c(hci_read_class_of_dev) +public func hci_read_class_of_dev(_ dd: Int32, _ deviceClass: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard var response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_CLASS_OF_DEV), + response: read_class_of_dev_rp.self, timeout: timeout + ) else { return -1 } + guard let deviceClass else { errno = EINVAL; return -1 } + withUnsafeMutableBytes(of: &response.dev_class) { deviceClass.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 3) } + return 0 +} + +/// `int hci_write_class_of_dev(int dd, uint32_t cls, int to)` +@c(hci_write_class_of_dev) +public func hci_write_class_of_dev(_ dd: Int32, _ deviceClass: UInt32, _ timeout: Int32) -> Int32 { + var command = write_class_of_dev_cp() + command.dev_class.0 = UInt8(deviceClass & 0xff) + command.dev_class.1 = UInt8((deviceClass >> 8) & 0xff) + command.dev_class.2 = UInt8((deviceClass >> 16) & 0xff) + return hciCommand(dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_CLASS_OF_DEV), command: command, timeout: timeout) +} + +// MARK: - Voice setting + +/// `int hci_read_voice_setting(int dd, uint16_t *vs, int to)` +@c(hci_read_voice_setting) +public func hci_read_voice_setting(_ dd: Int32, _ voiceSetting: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_VOICE_SETTING), + response: read_voice_setting_rp.self, timeout: timeout + ) else { return -1 } + voiceSetting?.pointee = response.voice_setting + return 0 +} + +/// `int hci_write_voice_setting(int dd, uint16_t vs, int to)` +@c(hci_write_voice_setting) +public func hci_write_voice_setting(_ dd: Int32, _ voiceSetting: UInt16, _ timeout: Int32) -> Int32 { + var command = write_voice_setting_cp() + command.voice_setting = voiceSetting + return hciCommand(dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_VOICE_SETTING), command: command, timeout: timeout) +} + +// MARK: - Current IAC LAP + +/// `int hci_read_current_iac_lap(int dd, uint8_t *num_iac, uint8_t *lap, int to)` +@c(hci_read_current_iac_lap) +public func hci_read_current_iac_lap(_ dd: Int32, _ numIAC: UnsafeMutablePointer?, _ lap: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard var response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_CURRENT_IAC_LAP), + response: read_current_iac_lap_rp.self, timeout: timeout + ) else { return -1 } + guard let numIAC else { errno = EINVAL; return -1 } + numIAC.pointee = response.num_current_iac + if let lap { + withUnsafeMutableBytes(of: &response.lap) { lap.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: Int(response.num_current_iac) * 3) } + } + return 0 +} + +/// `int hci_write_current_iac_lap(int dd, uint8_t num_iac, uint8_t *lap, int to)` +@c(hci_write_current_iac_lap) +public func hci_write_current_iac_lap(_ dd: Int32, _ numIAC: UInt8, _ lap: UnsafePointer?, _ timeout: Int32) -> Int32 { + guard let lap else { errno = EINVAL; return -1 } + var command = write_current_iac_lap_cp() + command.num_current_iac = numIAC + withUnsafeMutableBytes(of: &command.lap) { $0.copyMemory(from: UnsafeRawBufferPointer(start: lap, count: Int(numIAC) * 3)) } + return hciCommand( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_CURRENT_IAC_LAP), + commandBytes: withUnsafeBytes(of: &command) { Array($0.prefix(Int(numIAC) * 3 + 1)) }, timeout: timeout + ) +} + +// MARK: - Stored link keys + +/// `int hci_read_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to)` +@c(hci_read_stored_link_key) +public func hci_read_stored_link_key(_ dd: Int32, _ bdaddr: UnsafeMutablePointer?, _ all: UInt8, _ timeout: Int32) -> Int32 { + guard let bdaddr else { errno = EINVAL; return -1 } + var command = read_stored_link_key_cp() + command.bdaddr = bdaddr.pointee + command.read_all = all + return hciCommand(dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_STORED_LINK_KEY), command: command, timeout: timeout) +} + +/// `int hci_write_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t *key, int to)` +@c(hci_write_stored_link_key) +public func hci_write_stored_link_key(_ dd: Int32, _ bdaddr: UnsafeMutablePointer?, _ key: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let bdaddr, let key else { errno = EINVAL; return -1 } + var bytes = [UInt8](repeating: 0, count: 1 + 6 + 16) + bytes[0] = 1 + let address = bdaddr.pointee.b + bytes[1] = address.0 + bytes[2] = address.1 + bytes[3] = address.2 + bytes[4] = address.3 + bytes[5] = address.4 + bytes[6] = address.5 + for index in 0 ..< 16 { bytes[7 + index] = key[index] } + return hciCommand(dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_STORED_LINK_KEY), commandBytes: bytes, timeout: timeout) +} + +/// `int hci_delete_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to)` +@c(hci_delete_stored_link_key) +public func hci_delete_stored_link_key(_ dd: Int32, _ bdaddr: UnsafeMutablePointer?, _ all: UInt8, _ timeout: Int32) -> Int32 { + guard let bdaddr else { errno = EINVAL; return -1 } + var command = delete_stored_link_key_cp() + command.bdaddr = bdaddr.pointee + command.delete_all = all + return hciCommand(dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_DELETE_STORED_LINK_KEY), command: command, timeout: timeout) +} + +// MARK: - Inquiry scan type + +/// `int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to)` +@c(hci_read_inquiry_scan_type) +public func hci_read_inquiry_scan_type(_ dd: Int32, _ type: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_INQUIRY_SCAN_TYPE), + response: read_inquiry_scan_type_rp.self, timeout: timeout + ) else { return -1 } + type?.pointee = response.type + return 0 +} + +/// `int hci_write_inquiry_scan_type(int dd, uint8_t type, int to)` +@c(hci_write_inquiry_scan_type) +public func hci_write_inquiry_scan_type(_ dd: Int32, _ type: UInt8, _ timeout: Int32) -> Int32 { + var command = write_inquiry_scan_type_cp() + command.type = type + guard hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_INQUIRY_SCAN_TYPE), + command: command, response: write_inquiry_scan_type_rp.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +// MARK: - Inquiry mode + +/// `int hci_read_inquiry_mode(int dd, uint8_t *mode, int to)` +@c(hci_read_inquiry_mode) +public func hci_read_inquiry_mode(_ dd: Int32, _ mode: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_INQUIRY_MODE), + response: read_inquiry_mode_rp.self, timeout: timeout + ) else { return -1 } + mode?.pointee = response.mode + return 0 +} + +/// `int hci_write_inquiry_mode(int dd, uint8_t mode, int to)` +@c(hci_write_inquiry_mode) +public func hci_write_inquiry_mode(_ dd: Int32, _ mode: UInt8, _ timeout: Int32) -> Int32 { + var command = write_inquiry_mode_cp() + command.mode = mode + guard hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_INQUIRY_MODE), + command: command, response: write_inquiry_mode_rp.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +// MARK: - AFH mode + +/// `int hci_read_afh_mode(int dd, uint8_t *mode, int to)` +@c(hci_read_afh_mode) +public func hci_read_afh_mode(_ dd: Int32, _ mode: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_AFH_MODE), + response: read_afh_mode_rp.self, timeout: timeout + ) else { return -1 } + mode?.pointee = response.mode + return 0 +} + +/// `int hci_write_afh_mode(int dd, uint8_t mode, int to)` +@c(hci_write_afh_mode) +public func hci_write_afh_mode(_ dd: Int32, _ mode: UInt8, _ timeout: Int32) -> Int32 { + var command = write_afh_mode_cp() + command.mode = mode + guard hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_AFH_MODE), + command: command, response: write_afh_mode_rp.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +// MARK: - Extended inquiry response + +/// `int hci_read_ext_inquiry_response(int dd, uint8_t *fec, uint8_t *data, int to)` +@c(hci_read_ext_inquiry_response) +public func hci_read_ext_inquiry_response(_ dd: Int32, _ fec: UnsafeMutablePointer?, _ data: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard var response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_EXT_INQUIRY_RESPONSE), + response: read_ext_inquiry_response_rp.self, timeout: timeout + ) else { return -1 } + guard let fec, let data else { errno = EINVAL; return -1 } + fec.pointee = response.fec + withUnsafeMutableBytes(of: &response.data) { data.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: Int(HCI_MAX_EIR_LENGTH)) } + return 0 +} + +/// `int hci_write_ext_inquiry_response(int dd, uint8_t fec, uint8_t *data, int to)` +@c(hci_write_ext_inquiry_response) +public func hci_write_ext_inquiry_response(_ dd: Int32, _ fec: UInt8, _ data: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let data else { errno = EINVAL; return -1 } + var command = write_ext_inquiry_response_cp() + command.fec = fec + withUnsafeMutableBytes(of: &command.data) { $0.copyMemory(from: UnsafeRawBufferPointer(start: data, count: Int(HCI_MAX_EIR_LENGTH))) } + guard hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_EXT_INQUIRY_RESPONSE), + command: command, response: write_ext_inquiry_response_rp.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +// MARK: - Simple pairing mode + +/// `int hci_read_simple_pairing_mode(int dd, uint8_t *mode, int to)` +@c(hci_read_simple_pairing_mode) +public func hci_read_simple_pairing_mode(_ dd: Int32, _ mode: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_SIMPLE_PAIRING_MODE), + response: read_simple_pairing_mode_rp.self, timeout: timeout + ) else { return -1 } + mode?.pointee = response.mode + return 0 +} + +/// `int hci_write_simple_pairing_mode(int dd, uint8_t mode, int to)` +@c(hci_write_simple_pairing_mode) +public func hci_write_simple_pairing_mode(_ dd: Int32, _ mode: UInt8, _ timeout: Int32) -> Int32 { + var command = write_simple_pairing_mode_cp() + command.mode = mode + guard hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_SIMPLE_PAIRING_MODE), + command: command, response: write_simple_pairing_mode_rp.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +// MARK: - Out-of-band data + +/// `int hci_read_local_oob_data(int dd, uint8_t *hash, uint8_t *randomizer, int to)` +@c(hci_read_local_oob_data) +public func hci_read_local_oob_data(_ dd: Int32, _ hash: UnsafeMutablePointer?, _ randomizer: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard var response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_LOCAL_OOB_DATA), + response: read_local_oob_data_rp.self, timeout: timeout + ) else { return -1 } + guard let hash, let randomizer else { errno = EINVAL; return -1 } + withUnsafeMutableBytes(of: &response.hash) { hash.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 16) } + withUnsafeMutableBytes(of: &response.randomizer) { randomizer.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 16) } + return 0 +} + +// MARK: - Transmit power + +/// `int hci_read_inq_response_tx_power_level(int dd, int8_t *level, int to)` +@c(hci_read_inq_response_tx_power_level) +public func hci_read_inq_response_tx_power_level(_ dd: Int32, _ level: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL), + response: read_inq_response_tx_power_level_rp.self, timeout: timeout + ) else { return -1 } + level?.pointee = response.level + return 0 +} + +/// `int hci_read_inquiry_transmit_power_level(int dd, int8_t *level, int to)` +@c(hci_read_inquiry_transmit_power_level) +public func hci_read_inquiry_transmit_power_level(_ dd: Int32, _ level: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + hci_read_inq_response_tx_power_level(dd, level, timeout) +} + +/// `int hci_write_inquiry_transmit_power_level(int dd, int8_t level, int to)` +@c(hci_write_inquiry_transmit_power_level) +public func hci_write_inquiry_transmit_power_level(_ dd: Int32, _ level: Int8, _ timeout: Int32) -> Int32 { + var command = write_inquiry_transmit_power_level_cp() + command.level = level + guard hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL), + command: command, response: write_inquiry_transmit_power_level_rp.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +/// `int hci_read_transmit_power_level(int dd, uint16_t handle, uint8_t type, int8_t *level, int to)` +@c(hci_read_transmit_power_level) +public func hci_read_transmit_power_level(_ dd: Int32, _ handle: UInt16, _ type: UInt8, _ level: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + var command = read_transmit_power_level_cp() + command.handle = handle + command.type = type + guard let response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_TRANSMIT_POWER_LEVEL), + command: command, response: read_transmit_power_level_rp.self, timeout: timeout + ) else { return -1 } + level?.pointee = response.level + return 0 +} + +// MARK: - Link policy / supervision timeout + +/// `int hci_read_link_policy(int dd, uint16_t handle, uint16_t *policy, int to)` +@c(hci_read_link_policy) +public func hci_read_link_policy(_ dd: Int32, _ handle: UInt16, _ policy: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_LINK_POLICY), ocf: Int32(OCF_READ_LINK_POLICY), + command: handle, response: read_link_policy_rp.self, timeout: timeout + ) else { return -1 } + policy?.pointee = response.policy + return 0 +} + +/// `int hci_write_link_policy(int dd, uint16_t handle, uint16_t policy, int to)` +@c(hci_write_link_policy) +public func hci_write_link_policy(_ dd: Int32, _ handle: UInt16, _ policy: UInt16, _ timeout: Int32) -> Int32 { + var command = write_link_policy_cp() + command.handle = handle + command.policy = policy + guard hciRequest( + dd, ogf: Int32(OGF_LINK_POLICY), ocf: Int32(OCF_WRITE_LINK_POLICY), + command: command, response: write_link_policy_rp.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +/// `int hci_read_link_supervision_timeout(int dd, uint16_t handle, uint16_t *timeout, int to)` +@c(hci_read_link_supervision_timeout) +public func hci_read_link_supervision_timeout(_ dd: Int32, _ handle: UInt16, _ linkTimeout: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_READ_LINK_SUPERVISION_TIMEOUT), + command: handle, response: read_link_supervision_timeout_rp.self, timeout: timeout + ) else { return -1 } + linkTimeout?.pointee = response.timeout + return 0 +} + +/// `int hci_write_link_supervision_timeout(int dd, uint16_t handle, uint16_t timeout, int to)` +@c(hci_write_link_supervision_timeout) +public func hci_write_link_supervision_timeout(_ dd: Int32, _ handle: UInt16, _ linkTimeout: UInt16, _ timeout: Int32) -> Int32 { + var command = write_link_supervision_timeout_cp() + command.handle = handle + command.timeout = linkTimeout + guard hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_WRITE_LINK_SUPERVISION_TIMEOUT), + command: command, response: write_link_supervision_timeout_rp.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +// MARK: - AFH classification + +/// `int hci_set_afh_classification(int dd, uint8_t *map, int to)` +@c(hci_set_afh_classification) +public func hci_set_afh_classification(_ dd: Int32, _ map: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let map else { errno = EINVAL; return -1 } + var command = set_afh_classification_cp() + withUnsafeMutableBytes(of: &command.map) { $0.copyMemory(from: UnsafeRawBufferPointer(start: map, count: 10)) } + guard hciRequest( + dd, ogf: Int32(OGF_HOST_CTL), ocf: Int32(OCF_SET_AFH_CLASSIFICATION), + command: command, response: set_afh_classification_rp.self, timeout: timeout + ) != nil else { return -1 } + return 0 +} + +// MARK: - Status parameters (link quality / RSSI / AFH map / clock) + +/// `int hci_read_link_quality(int dd, uint16_t handle, uint8_t *link_quality, int to)` +@c(hci_read_link_quality) +public func hci_read_link_quality(_ dd: Int32, _ handle: UInt16, _ linkQuality: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_STATUS_PARAM), ocf: Int32(OCF_READ_LINK_QUALITY), + command: handle, response: read_link_quality_rp.self, timeout: timeout + ) else { return -1 } + linkQuality?.pointee = response.link_quality + return 0 +} + +/// `int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to)` +@c(hci_read_rssi) +public func hci_read_rssi(_ dd: Int32, _ handle: UInt16, _ rssi: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard let response = hciRequest( + dd, ogf: Int32(OGF_STATUS_PARAM), ocf: Int32(OCF_READ_RSSI), + command: handle, response: read_rssi_rp.self, timeout: timeout + ) else { return -1 } + rssi?.pointee = response.rssi + return 0 +} + +/// `int hci_read_afh_map(int dd, uint16_t handle, uint8_t *mode, uint8_t *map, int to)` +@c(hci_read_afh_map) +public func hci_read_afh_map(_ dd: Int32, _ handle: UInt16, _ mode: UnsafeMutablePointer?, _ map: UnsafeMutablePointer?, _ timeout: Int32) -> Int32 { + guard var response = hciRequest( + dd, ogf: Int32(OGF_STATUS_PARAM), ocf: Int32(OCF_READ_AFH_MAP), + command: handle, response: read_afh_map_rp.self, timeout: timeout + ) else { return -1 } + guard let mode, let map else { errno = EINVAL; return -1 } + mode.pointee = response.mode + withUnsafeMutableBytes(of: &response.map) { map.update(from: $0.bindMemory(to: UInt8.self).baseAddress!, count: 10) } + return 0 +} + +/// `int hci_read_clock(int dd, uint16_t handle, uint8_t which, uint32_t *clock, uint16_t *accuracy, int to)` +@c(hci_read_clock) +public func hci_read_clock( + _ dd: Int32, + _ handle: UInt16, + _ which: UInt8, + _ clock: UnsafeMutablePointer?, + _ accuracy: UnsafeMutablePointer?, + _ timeout: Int32 +) -> Int32 { + var command = read_clock_cp() + command.handle = handle + command.which_clock = which + guard let response = hciRequest( + dd, ogf: Int32(OGF_STATUS_PARAM), ocf: Int32(OCF_READ_CLOCK), + command: command, response: read_clock_rp.self, timeout: timeout + ) else { return -1 } + clock?.pointee = response.clock + accuracy?.pointee = response.accuracy + return 0 +} From c85791f33dbf1761489b053e6773545af0ddb08d Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 19:03:44 -0400 Subject: [PATCH 51/73] Implement hci_inquiry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlike the rest of the command wrapper family, this goes through a single ioctl(HCIINQUIRY) rather than hci_send_req — the request header and resulting inquiry_info records share one kernel buffer, the same shape as HCIGETDEVLIST in HCIDevice.swift. --- Sources/BluetoothLinuxABI/HCIInquiry.swift | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/HCIInquiry.swift diff --git a/Sources/BluetoothLinuxABI/HCIInquiry.swift b/Sources/BluetoothLinuxABI/HCIInquiry.swift new file mode 100644 index 0000000..cc4d452 --- /dev/null +++ b/Sources/BluetoothLinuxABI/HCIInquiry.swift @@ -0,0 +1,101 @@ +// +// HCIInquiry.swift +// BluetoothLinux +// +// hci_inquiry doesn't go through hci_send_req at all — it's a single +// ioctl(HCIINQUIRY) call whose input (the inquiry parameters) and +// output (the resulting inquiry_info records) share one kernel +// buffer, the same "fixed header immediately followed by a run of +// fixed-size records" shape as HCIGETDEVLIST in HCIDevice.swift. +// + +import CBluetoothLinuxABI +import Glibc + +// _IOR('H', 240, int) — see HCIDevice.swift's hciGetDeviceInfo/hciGetDeviceList +// for why this can't be imported directly. +private let hciInquiryRequest: UInt = 0x800448f0 + +/// `int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap, inquiry_info **ii, long flags)` +@c(hci_inquiry) +public func hci_inquiry( + _ deviceID: Int32, + _ len: Int32, + _ numberOfResponses: Int32, + _ lap: UnsafePointer?, + _ inquiryInfo: UnsafeMutablePointer?>?, + _ flags: Int +) -> Int32 { + guard let inquiryInfo else { errno = EINVAL; return -1 } + + var requestedResponses = numberOfResponses + let numberOfResponsesField: UInt8 = numberOfResponses > 0 ? UInt8(numberOfResponses) : 0 + if numberOfResponses <= 0 { + requestedResponses = 255 + } + + var deviceID = deviceID + if deviceID < 0 { + deviceID = hci_get_route(nil) + guard deviceID >= 0 else { + errno = ENODEV + return -1 + } + } + + let dd = socket(Int32(AF_BLUETOOTH), Int32(SOCK_RAW.rawValue) | Int32(SOCK_CLOEXEC.rawValue), Int32(BTPROTO_HCI)) + guard dd >= 0 else { return dd } + + let headerSize = MemoryLayout.size + let entrySize = MemoryLayout.size + let byteCount = headerSize + entrySize * Int(requestedResponses) + let buffer = UnsafeMutableRawPointer.allocate( + byteCount: byteCount, + alignment: MemoryLayout.alignment + ) + defer { buffer.deallocate() } + buffer.initializeMemory(as: UInt8.self, repeating: 0, count: byteCount) + + let request = buffer.assumingMemoryBound(to: hci_inquiry_req.self) + request.pointee.dev_id = UInt16(deviceID) + request.pointee.num_rsp = numberOfResponsesField + request.pointee.length = UInt8(truncatingIfNeeded: len) + request.pointee.flags = UInt16(truncatingIfNeeded: flags) + if let lap { + request.pointee.lap.0 = lap[0] + request.pointee.lap.1 = lap[1] + request.pointee.lap.2 = lap[2] + } else { + request.pointee.lap.0 = 0x33 + request.pointee.lap.1 = 0x8b + request.pointee.lap.2 = 0x9e + } + + let ioctlResult = ioctl(dd, numericCast(hciInquiryRequest), buffer) + guard ioctlResult >= 0 else { + let savedError = errno + close(dd) + errno = savedError + return -1 + } + + let responseCount = Int(request.pointee.num_rsp) + let resultByteCount = entrySize * responseCount + + var result: Int32 + if inquiryInfo.pointee == nil { + inquiryInfo.pointee = UnsafeMutablePointer.allocate(capacity: responseCount) + } + if let destination = inquiryInfo.pointee { + let source = buffer.advanced(by: headerSize) + UnsafeMutableRawPointer(destination).copyMemory(from: source, byteCount: resultByteCount) + result = Int32(responseCount) + } else { + result = -1 + } + + let savedError = errno + close(dd) + errno = savedError + return result +} From c37234a1158aac66b67eade754afd71728422e41 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 19:03:53 -0400 Subject: [PATCH 52/73] Mark HCI command wrapper symbols as implemented --- scripts/implemented.txt | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/scripts/implemented.txt b/scripts/implemented.txt index 2c837cc..0a95975 100644 --- a/scripts/implemented.txt +++ b/scripts/implemented.txt @@ -155,3 +155,79 @@ hci_for_each_dev hci_get_route hci_send_cmd hci_send_req + +# HCI command wrappers (71), implemented in +# Sources/BluetoothLinuxABI/HCICommandsConnection.swift, +# HCICommandsRemote.swift, HCICommandsInfo.swift, HCICommandsConfig.swift, +# HCICommandsLE.swift, and HCIInquiry.swift. +hci_authenticate_link +hci_change_link_key +hci_create_connection +hci_delete_stored_link_key +hci_disconnect +hci_encrypt_link +hci_exit_park_mode +hci_inquiry +hci_le_add_resolving_list +hci_le_add_white_list +hci_le_clear_resolving_list +hci_le_clear_white_list +hci_le_conn_update +hci_le_create_conn +hci_le_read_remote_features +hci_le_read_resolving_list_size +hci_le_read_white_list_size +hci_le_rm_resolving_list +hci_le_rm_white_list +hci_le_set_address_resolution_enable +hci_le_set_advertise_enable +hci_le_set_scan_enable +hci_le_set_scan_parameters +hci_park_mode +hci_read_afh_map +hci_read_afh_mode +hci_read_bd_addr +hci_read_class_of_dev +hci_read_clock +hci_read_clock_offset +hci_read_current_iac_lap +hci_read_ext_inquiry_response +hci_read_inq_response_tx_power_level +hci_read_inquiry_mode +hci_read_inquiry_scan_type +hci_read_inquiry_transmit_power_level +hci_read_link_policy +hci_read_link_quality +hci_read_link_supervision_timeout +hci_read_local_commands +hci_read_local_ext_features +hci_read_local_features +hci_read_local_name +hci_read_local_oob_data +hci_read_local_version +hci_read_remote_ext_features +hci_read_remote_features +hci_read_remote_name +hci_read_remote_name_cancel +hci_read_remote_name_with_clock_offset +hci_read_remote_version +hci_read_rssi +hci_read_simple_pairing_mode +hci_read_stored_link_key +hci_read_transmit_power_level +hci_read_voice_setting +hci_set_afh_classification +hci_switch_role +hci_write_afh_mode +hci_write_class_of_dev +hci_write_current_iac_lap +hci_write_ext_inquiry_response +hci_write_inquiry_mode +hci_write_inquiry_scan_type +hci_write_inquiry_transmit_power_level +hci_write_link_policy +hci_write_link_supervision_timeout +hci_write_local_name +hci_write_simple_pairing_mode +hci_write_stored_link_key +hci_write_voice_setting From 4de8ba9ff445f325ae60531ec703e540e6015cf5 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 2 Aug 2026 19:03:53 -0400 Subject: [PATCH 53/73] Regenerate C ABI stub table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the 71 stubs now covered by the HCI command wrapper family (25 remaining — the SDP session functions — 203 implemented). --- Sources/CBluetoothLinuxABI/gen/cbt_stubs.c | 355 --------------------- 1 file changed, 355 deletions(-) diff --git a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c index dd362a7..ab9acc2 100644 --- a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c +++ b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c @@ -30,361 +30,6 @@ static void cbt_unimplemented(const char *symbol) abort(); } -int hci_authenticate_link(int dd, uint16_t handle, int to) -{ - cbt_unimplemented("hci_authenticate_link"); -} - -int hci_change_link_key(int dd, uint16_t handle, int to) -{ - cbt_unimplemented("hci_change_link_key"); -} - -int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to) -{ - cbt_unimplemented("hci_create_connection"); -} - -int hci_delete_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to) -{ - cbt_unimplemented("hci_delete_stored_link_key"); -} - -int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to) -{ - cbt_unimplemented("hci_disconnect"); -} - -int hci_encrypt_link(int dd, uint16_t handle, uint8_t encrypt, int to) -{ - cbt_unimplemented("hci_encrypt_link"); -} - -int hci_exit_park_mode(int dd, uint16_t handle, int to) -{ - cbt_unimplemented("hci_exit_park_mode"); -} - -int hci_inquiry(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info **ii, long flags) -{ - cbt_unimplemented("hci_inquiry"); -} - -int hci_le_add_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, uint8_t *peer_irk, uint8_t *local_irk, int to) -{ - cbt_unimplemented("hci_le_add_resolving_list"); -} - -int hci_le_add_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to) -{ - cbt_unimplemented("hci_le_add_white_list"); -} - -int hci_le_clear_resolving_list(int dd, int to) -{ - cbt_unimplemented("hci_le_clear_resolving_list"); -} - -int hci_le_clear_white_list(int dd, int to) -{ - cbt_unimplemented("hci_le_clear_white_list"); -} - -int hci_le_conn_update(int dd, uint16_t handle, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t supervision_timeout, int to) -{ - cbt_unimplemented("hci_le_conn_update"); -} - -int hci_le_create_conn(int dd, uint16_t interval, uint16_t window, uint8_t initiator_filter, uint8_t peer_bdaddr_type, bdaddr_t peer_bdaddr, uint8_t own_bdaddr_type, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t supervision_timeout, uint16_t min_ce_length, uint16_t max_ce_length, uint16_t *handle, int to) -{ - cbt_unimplemented("hci_le_create_conn"); -} - -int hci_le_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to) -{ - cbt_unimplemented("hci_le_read_remote_features"); -} - -int hci_le_read_resolving_list_size(int dd, uint8_t *size, int to) -{ - cbt_unimplemented("hci_le_read_resolving_list_size"); -} - -int hci_le_read_white_list_size(int dd, uint8_t *size, int to) -{ - cbt_unimplemented("hci_le_read_white_list_size"); -} - -int hci_le_rm_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to) -{ - cbt_unimplemented("hci_le_rm_resolving_list"); -} - -int hci_le_rm_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to) -{ - cbt_unimplemented("hci_le_rm_white_list"); -} - -int hci_le_set_address_resolution_enable(int dev_id, uint8_t enable, int to) -{ - cbt_unimplemented("hci_le_set_address_resolution_enable"); -} - -int hci_le_set_advertise_enable(int dev_id, uint8_t enable, int to) -{ - cbt_unimplemented("hci_le_set_advertise_enable"); -} - -int hci_le_set_scan_enable(int dev_id, uint8_t enable, uint8_t filter_dup, int to) -{ - cbt_unimplemented("hci_le_set_scan_enable"); -} - -int hci_le_set_scan_parameters(int dev_id, uint8_t type, uint16_t interval, uint16_t window, uint8_t own_type, uint8_t filter, int to) -{ - cbt_unimplemented("hci_le_set_scan_parameters"); -} - -int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval, uint16_t min_interval, int to) -{ - cbt_unimplemented("hci_park_mode"); -} - -int hci_read_afh_map(int dd, uint16_t handle, uint8_t *mode, uint8_t *map, int to) -{ - cbt_unimplemented("hci_read_afh_map"); -} - -int hci_read_afh_mode(int dd, uint8_t *mode, int to) -{ - cbt_unimplemented("hci_read_afh_mode"); -} - -int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to) -{ - cbt_unimplemented("hci_read_bd_addr"); -} - -int hci_read_class_of_dev(int dd, uint8_t *cls, int to) -{ - cbt_unimplemented("hci_read_class_of_dev"); -} - -int hci_read_clock(int dd, uint16_t handle, uint8_t which, uint32_t *clock, uint16_t *accuracy, int to) -{ - cbt_unimplemented("hci_read_clock"); -} - -int hci_read_clock_offset(int dd, uint16_t handle, uint16_t *clkoffset, int to) -{ - cbt_unimplemented("hci_read_clock_offset"); -} - -int hci_read_current_iac_lap(int dd, uint8_t *num_iac, uint8_t *lap, int to) -{ - cbt_unimplemented("hci_read_current_iac_lap"); -} - -int hci_read_ext_inquiry_response(int dd, uint8_t *fec, uint8_t *data, int to) -{ - cbt_unimplemented("hci_read_ext_inquiry_response"); -} - -int hci_read_inq_response_tx_power_level(int dd, int8_t *level, int to) -{ - cbt_unimplemented("hci_read_inq_response_tx_power_level"); -} - -int hci_read_inquiry_mode(int dd, uint8_t *mode, int to) -{ - cbt_unimplemented("hci_read_inquiry_mode"); -} - -int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to) -{ - cbt_unimplemented("hci_read_inquiry_scan_type"); -} - -int hci_read_inquiry_transmit_power_level(int dd, int8_t *level, int to) -{ - cbt_unimplemented("hci_read_inquiry_transmit_power_level"); -} - -int hci_read_link_policy(int dd, uint16_t handle, uint16_t *policy, int to) -{ - cbt_unimplemented("hci_read_link_policy"); -} - -int hci_read_link_quality(int dd, uint16_t handle, uint8_t *link_quality, int to) -{ - cbt_unimplemented("hci_read_link_quality"); -} - -int hci_read_link_supervision_timeout(int dd, uint16_t handle, uint16_t *timeout, int to) -{ - cbt_unimplemented("hci_read_link_supervision_timeout"); -} - -int hci_read_local_commands(int dd, uint8_t *commands, int to) -{ - cbt_unimplemented("hci_read_local_commands"); -} - -int hci_read_local_ext_features(int dd, uint8_t page, uint8_t *max_page, uint8_t *features, int to) -{ - cbt_unimplemented("hci_read_local_ext_features"); -} - -int hci_read_local_features(int dd, uint8_t *features, int to) -{ - cbt_unimplemented("hci_read_local_features"); -} - -int hci_read_local_name(int dd, int len, char *name, int to) -{ - cbt_unimplemented("hci_read_local_name"); -} - -int hci_read_local_oob_data(int dd, uint8_t *hash, uint8_t *randomizer, int to) -{ - cbt_unimplemented("hci_read_local_oob_data"); -} - -int hci_read_local_version(int dd, struct hci_version *ver, int to) -{ - cbt_unimplemented("hci_read_local_version"); -} - -int hci_read_remote_ext_features(int dd, uint16_t handle, uint8_t page, uint8_t *max_page, uint8_t *features, int to) -{ - cbt_unimplemented("hci_read_remote_ext_features"); -} - -int hci_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to) -{ - cbt_unimplemented("hci_read_remote_features"); -} - -int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to) -{ - cbt_unimplemented("hci_read_remote_name"); -} - -int hci_read_remote_name_cancel(int dd, const bdaddr_t *bdaddr, int to) -{ - cbt_unimplemented("hci_read_remote_name_cancel"); -} - -int hci_read_remote_name_with_clock_offset(int dd, const bdaddr_t *bdaddr, uint8_t pscan_rep_mode, uint16_t clkoffset, int len, char *name, int to) -{ - cbt_unimplemented("hci_read_remote_name_with_clock_offset"); -} - -int hci_read_remote_version(int dd, uint16_t handle, struct hci_version *ver, int to) -{ - cbt_unimplemented("hci_read_remote_version"); -} - -int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to) -{ - cbt_unimplemented("hci_read_rssi"); -} - -int hci_read_simple_pairing_mode(int dd, uint8_t *mode, int to) -{ - cbt_unimplemented("hci_read_simple_pairing_mode"); -} - -int hci_read_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to) -{ - cbt_unimplemented("hci_read_stored_link_key"); -} - -int hci_read_transmit_power_level(int dd, uint16_t handle, uint8_t type, int8_t *level, int to) -{ - cbt_unimplemented("hci_read_transmit_power_level"); -} - -int hci_read_voice_setting(int dd, uint16_t *vs, int to) -{ - cbt_unimplemented("hci_read_voice_setting"); -} - -int hci_set_afh_classification(int dd, uint8_t *map, int to) -{ - cbt_unimplemented("hci_set_afh_classification"); -} - -int hci_switch_role(int dd, bdaddr_t *bdaddr, uint8_t role, int to) -{ - cbt_unimplemented("hci_switch_role"); -} - -int hci_write_afh_mode(int dd, uint8_t mode, int to) -{ - cbt_unimplemented("hci_write_afh_mode"); -} - -int hci_write_class_of_dev(int dd, uint32_t cls, int to) -{ - cbt_unimplemented("hci_write_class_of_dev"); -} - -int hci_write_current_iac_lap(int dd, uint8_t num_iac, uint8_t *lap, int to) -{ - cbt_unimplemented("hci_write_current_iac_lap"); -} - -int hci_write_ext_inquiry_response(int dd, uint8_t fec, uint8_t *data, int to) -{ - cbt_unimplemented("hci_write_ext_inquiry_response"); -} - -int hci_write_inquiry_mode(int dd, uint8_t mode, int to) -{ - cbt_unimplemented("hci_write_inquiry_mode"); -} - -int hci_write_inquiry_scan_type(int dd, uint8_t type, int to) -{ - cbt_unimplemented("hci_write_inquiry_scan_type"); -} - -int hci_write_inquiry_transmit_power_level(int dd, int8_t level, int to) -{ - cbt_unimplemented("hci_write_inquiry_transmit_power_level"); -} - -int hci_write_link_policy(int dd, uint16_t handle, uint16_t policy, int to) -{ - cbt_unimplemented("hci_write_link_policy"); -} - -int hci_write_link_supervision_timeout(int dd, uint16_t handle, uint16_t timeout, int to) -{ - cbt_unimplemented("hci_write_link_supervision_timeout"); -} - -int hci_write_local_name(int dd, const char *name, int to) -{ - cbt_unimplemented("hci_write_local_name"); -} - -int hci_write_simple_pairing_mode(int dd, uint8_t mode, int to) -{ - cbt_unimplemented("hci_write_simple_pairing_mode"); -} - -int hci_write_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t *key, int to) -{ - cbt_unimplemented("hci_write_stored_link_key"); -} - -int hci_write_voice_setting(int dd, uint16_t vs, int to) -{ - cbt_unimplemented("hci_write_voice_setting"); -} - int sdp_close(sdp_session_t *session) { cbt_unimplemented("sdp_close"); From 602d1a6cd1693cf7d574a008633ce7a0c5872e3b Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 6 Aug 2026 22:17:38 -0400 Subject: [PATCH 54/73] Update dependencies --- Package.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index a5f1a6e..dd9ae00 100644 --- a/Package.swift +++ b/Package.swift @@ -44,7 +44,7 @@ var package = Package( dependencies: [ .package( url: "https://github.com/PureSwift/Bluetooth.git", - branch: "master" + from: "8.1.0" ), .package( url: "https://github.com/PureSwift/Socket.git", @@ -215,7 +215,11 @@ if ProcessInfo.processInfo.environment["SWIFTPM_BLUETOOTH_CABI"] == "1" { "CBluetoothLinuxABI", // For bt_malloc/bt_malloc0/bt_free — CBluetoothLinuxABI // declares them (bluetooth.h) but does not define them; - // BluetoothABI's CBluetooth does. Also gated behind + // BluetoothABI's CBluetooth does. It also carries the + // pure sdp_* symbols — the PDU codec (sdp_gen_pdu, + // sdp_extract_pdu, sdp_seq_alloc, sdp_data_alloc, ...) + // that the SDP session functions build request and + // response PDUs with. Also gated behind // SWIFTPM_BLUETOOTH_CABI=1, which SwiftPM propagates to // this dependency's own manifest evaluation. .product(name: "BluetoothABI", package: "Bluetooth") From cfa7e2b604926586b802fc0ca6ed526862215e29 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 6 Aug 2026 22:22:18 -0400 Subject: [PATCH 55/73] Drop the BluetoothSDP target from the CMake link PureSwift/Bluetooth 8.1.0 folds the 75 pure sdp_* symbols into BluetoothABI rather than shipping them as a separate static library, so there is no BluetoothSDP CMake target to name here any more. Its symbols come in with BluetoothABI, which is already linked whole. --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7491b49..e5373a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,13 +192,12 @@ set_target_properties(bluetooth3 PROPERTIES target_link_options(bluetooth3 PRIVATE "LINKER:--whole-archive" "LINKER:$" - "LINKER:$" "LINKER:$" "LINKER:$" "LINKER:$" "LINKER:--no-whole-archive") -add_dependencies(bluetooth3 BluetoothABI BluetoothSDP BluetoothLinuxABI CBluetooth CBluetoothLinuxABI) +add_dependencies(bluetooth3 BluetoothABI BluetoothLinuxABI CBluetooth CBluetoothLinuxABI) target_link_libraries(bluetooth3 PRIVATE Bluetooth) From a527876db62d1fa518869c7e44a0f1aed7f913bd Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 6 Aug 2026 22:22:35 -0400 Subject: [PATCH 56/73] Add the SDP request-PDU assembly helpers Encoding a search pattern or an attribute ID list as a data element sequence, and appending a continuation state. The reference keeps the equivalents private to sdp.c and exports none of them, so they are reimplemented here rather than linked against. --- Sources/BluetoothLinuxABI/SDPPDU.swift | 164 +++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/SDPPDU.swift diff --git a/Sources/BluetoothLinuxABI/SDPPDU.swift b/Sources/BluetoothLinuxABI/SDPPDU.swift new file mode 100644 index 0000000..8914be4 --- /dev/null +++ b/Sources/BluetoothLinuxABI/SDPPDU.swift @@ -0,0 +1,164 @@ +// +// SDPPDU.swift +// BluetoothLinux +// +// Request-PDU assembly helpers shared by the synchronous and +// asynchronous SDP request functions: encoding a search pattern or +// attribute ID list as a data element sequence, and appending a +// continuation state. These mirror lib/sdp.c's private +// gen_searchseq_pdu/gen_attridseq_pdu/copy_cstate — none of which are +// part of the exported symbol surface, so they're reimplemented here +// rather than linked against. +// + +import CBluetoothLinuxABI +import BluetoothABI +import Glibc + +/// BlueZ's private `sdp_cstate_t`: +/// `typedef struct { uint8_t length; unsigned char data[16]; } __attribute__((packed)) sdp_cstate_t;` +/// — internal to sdp.c, not part of any vendored header. All fields +/// are byte-sized, so this Swift struct's natural layout already +/// matches the packed C one with no padding to worry about. +internal struct SDPContinuationState { + var length: UInt8 + var data: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) +} + +/// `static int copy_cstate(uint8_t *pdata, int pdata_len, const sdp_cstate_t *cstate)` +internal func copyContinuationState(_ destination: UnsafeMutableRawPointer, _ availableLength: Int, _ cstate: UnsafePointer?) -> Int32 { + guard let cstate else { + destination.assumingMemoryBound(to: UInt8.self).pointee = 0 + return 1 + } + var length = cstate.pointee.length + if Int(length) >= availableLength { + length = UInt8(availableLength - 1) + } + let bytes = destination.assumingMemoryBound(to: UInt8.self) + bytes.pointee = length + _ = withUnsafeBytes(of: cstate.pointee.data) { source in + memcpy(bytes + 1, source.baseAddress!, Int(length)) + } + return Int32(length) + 1 +} + +/// A stable address for the (per-call, single-valued) data-type-descriptor +/// byte that `sdp_seq_alloc` expects one pointer-per-element to. BlueZ +/// reuses the address of one stack variable for every element; this +/// does the same, kept alive for the whole encode by running entirely +/// inside `withUnsafeMutablePointer`. +private func encodeSequence( + dataType: UInt8, + count: Int, + elementValue: (_ index: Int) -> UnsafeMutableRawPointer?, + into destination: UnsafeMutableRawPointer +) -> Int32 { + guard count > 0 else { return -1 } + var dtd = dataType + return withUnsafeMutablePointer(to: &dtd) { dtdPointer -> Int32 in + var dtds = [UnsafeMutableRawPointer?](repeating: nil, count: count) + var values = [UnsafeMutableRawPointer?](repeating: nil, count: count) + for index in 0 ..< count { + dtds[index] = UnsafeMutableRawPointer(dtdPointer) + values[index] = elementValue(index) + } + + guard let sequence = dtds.withUnsafeMutableBufferPointer({ dtdBuffer in + values.withUnsafeMutableBufferPointer { valueBuffer in + sdp_seq_alloc(dtdBuffer.baseAddress, valueBuffer.baseAddress, Int32(count)) + } + }) else { return -1 } + defer { sdp_data_free(sequence) } + + // Scratch buffer for sdp_gen_pdu's write pass. The reference + // pre-sizes this with sdp_gen_buffer (private, not exported); + // since only the exact data_size sdp_gen_pdu reports back is + // ever copied out, a generous flat over-estimate per element + // is just as safe and much simpler for the flat (non-nested) + // sequences built here — a UUID is at most 16 bytes, an + // attribute ID at most 4, so 24 bytes/element plus the outer + // sequence envelope leaves ample headroom. + let scratchSize = count * 24 + 16 + guard let scratch = malloc(scratchSize) else { return -1 } + defer { free(scratch) } + + var buf = sdp_buf_t() + buf.data = scratch.assumingMemoryBound(to: UInt8.self) + buf.buf_size = UInt32(scratchSize) + + let written = withUnsafeMutablePointer(to: &buf) { sdp_gen_pdu($0, sequence) } + guard written >= 0 else { return -1 } + + memcpy(destination, buf.data, Int(buf.data_size)) + return written + } +} + +/// `static int gen_searchseq_pdu(uint8_t *dst, const sdp_list_t *seq)` +/// +/// Every entry is a `uuid_t *`; the data type is the *first* entry's +/// UUID width (16/32/128-bit), applied uniformly — matching the +/// reference exactly, including not validating that the rest of the +/// list shares that width. +internal func genSearchSequencePDU(_ destination: UnsafeMutableRawPointer, _ search: UnsafePointer?) -> Int32 { + guard let search, let firstData = search.pointee.data else { return -1 } + let dataType = firstData.assumingMemoryBound(to: uuid_t.self).pointee.type + + var count = 0 + var node: UnsafePointer? = search + while let n = node { count += 1; node = n.pointee.next.map { UnsafePointer($0) } } + + var nodes = [UnsafePointer]() + nodes.reserveCapacity(count) + node = search + while let n = node { nodes.append(n); node = n.pointee.next.map { UnsafePointer($0) } } + + // `value` is a union — ClangImporter exposes it as a computed + // property, so `&uuid.pointee.value` would materialize a + // temporary valid only for an immediate call. `uuid` itself is a + // genuinely stable pointer (into the caller's list nodes, alive + // for this whole synchronous call), so take its raw address and + // advance by `value`'s known offset instead — safe to store in + // `values` and dereference later when sdp_seq_alloc runs. + let valueOffset = MemoryLayout.offset(of: \.value)! + return encodeSequence(dataType: dataType, count: count, elementValue: { index in + let uuid = nodes[index].pointee.data!.assumingMemoryBound(to: uuid_t.self) + return UnsafeMutableRawPointer(uuid).advanced(by: valueOffset) + }, into: destination) +} + +/// `static int gen_attridseq_pdu(uint8_t *dst, const sdp_list_t *seq, uint8_t dataType)` +/// +/// Every entry's `data` pointer already points directly at a raw +/// `uint16_t` (individual attribute ID) or `uint32_t` (attribute ID +/// range), owned by the caller. +internal func genAttrIDSequencePDU(_ destination: UnsafeMutableRawPointer, _ attributeIDs: UnsafePointer?, _ dataType: UInt8) -> Int32 { + var count = 0 + var node: UnsafePointer? = attributeIDs + while let n = node { count += 1; node = n.pointee.next.map { UnsafePointer($0) } } + + var nodes = [UnsafePointer]() + nodes.reserveCapacity(count) + node = attributeIDs + while let n = node { nodes.append(n); node = n.pointee.next.map { UnsafePointer($0) } } + + return encodeSequence(dataType: dataType, count: count, elementValue: { index in + nodes[index].pointee.data + }, into: destination) +} + +/// `static void extract_record_handle_seq(uint8_t *pdu, int bufsize, sdp_list_t **seq, int count, unsigned int *scanned)` +internal func extractRecordHandleSequence(_ pdu: UnsafePointer, _ bufferSize: Int, _ list: inout UnsafeMutablePointer?, _ count: Int, _ scanned: inout UInt32) { + var remaining = bufferSize + var pointer = pdu + for _ in 0 ..< count { + guard remaining >= MemoryLayout.size else { break } + guard let handle = malloc(MemoryLayout.size)?.assumingMemoryBound(to: UInt32.self) else { break } + handle.pointee = pointer.withMemoryRebound(to: UInt8.self, capacity: 4) { bt_get_be32($0) } + list = sdp_list_append(list, handle) + pointer += 4 + scanned += 4 + remaining -= 4 + } +} From be35f101a2bc1955384620471c4937dd67443ff4 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 6 Aug 2026 22:22:59 -0400 Subject: [PATCH 57/73] Implement the SDP client session lifecycle sdp_create, sdp_connect, sdp_close, sdp_get_socket, sdp_get_error, sdp_gen_tid, sdp_set_notify and sdp_general_inquiry. sdp_session_t is a non-opaque struct that some callers read directly, so its fields are kept populated with real values rather than treated as opaque. The private per-session state (the reference's struct sdp_transaction) is not part of any vendored header and is reimplemented as SDPTransaction. --- Sources/BluetoothLinuxABI/SDPSession.swift | 283 +++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/SDPSession.swift diff --git a/Sources/BluetoothLinuxABI/SDPSession.swift b/Sources/BluetoothLinuxABI/SDPSession.swift new file mode 100644 index 0000000..0076971 --- /dev/null +++ b/Sources/BluetoothLinuxABI/SDPSession.swift @@ -0,0 +1,283 @@ +// +// SDPSession.swift +// BluetoothLinux +// +// The SDP client session lifecycle: creating a session over an +// existing socket, connecting (over L2CAP to a remote device, or a +// local Unix-domain socket to a local SDP server), closing it, and +// the small per-session bookkeeping (transaction ID, last error, +// async notification callback). +// +// `sdp_session_t` is a plain, non-opaque struct (see the vendored +// sdp_lib.h) — some callers do read its fields directly rather than +// going through sdp_get_socket, so this keeps them populated with +// real values rather than treating the struct as opaque. +// +// The one piece of state the public struct *doesn't* hold — +// `session->priv`, BlueZ's `struct sdp_transaction` — is private to +// sdp.c and not part of any vendored header. It's reimplemented here +// as SDPTransaction: the async callback/userdata, the outstanding +// request buffer (kept around so a continuation-state follow-up can +// resend it with a new transaction ID), and a growable buffer that +// concatenates a response split across multiple continuation-state +// fragments. +// +// None of this has been exercised against a real or virtual SDP +// server; see HCIDevice.swift's note on hci_send_cmd/hci_send_req for +// the same caveat applying here. +// + +import CBluetoothLinuxABI +import BluetoothABI +import Glibc + +// MARK: - Transaction state (BlueZ's private `struct sdp_transaction`) + +internal struct SDPTransaction { + var callback: sdp_callback_t? + var userData: UnsafeMutableRawPointer? + var requestBuffer: UnsafeMutableRawPointer? + var requestSize: UInt32 = 0 + var responseBuffer: sdp_buf_t = sdp_buf_t() + var error: Int32 = 0 +} + +// SDP_MAX_ATTR_LEN and SDP_LARGE_L2CAP_MTU — defined locally in +// BlueZ's sdp.c, not in any vendored header. +internal let sdpMaxAttrLength: UInt16 = 65535 +private let sdpLargeL2CAPMTU: UInt16 = 1013 + +internal func sdpTransaction(_ session: UnsafeMutablePointer) -> UnsafeMutablePointer? { + session.pointee.priv?.assumingMemoryBound(to: SDPTransaction.self) +} + +// MARK: - Session lifecycle + +/// `sdp_session_t *sdp_create(int sk, uint32_t flags)` +@c(sdp_create) +public func sdp_create(_ socket: Int32, _ flags: UInt32) -> UnsafeMutablePointer? { + guard let session = bt_malloc0(MemoryLayout.size)?.assumingMemoryBound(to: sdp_session_t.self) else { + errno = ENOMEM + return nil + } + session.pointee.flags = Int32(bitPattern: flags) + session.pointee.sock = socket + + let transaction = UnsafeMutablePointer.allocate(capacity: 1) + transaction.initialize(to: SDPTransaction()) + session.pointee.priv = UnsafeMutableRawPointer(transaction) + + return session +} + +/// `int sdp_close(sdp_session_t *session)` +@c(sdp_close) +public func sdp_close(_ session: UnsafeMutablePointer?) -> Int32 { + guard let session else { return -1 } + let result = close(session.pointee.sock) + if let transaction = sdpTransaction(session) { + free(transaction.pointee.requestBuffer) + free(transaction.pointee.responseBuffer.data) + transaction.deinitialize(count: 1) + transaction.deallocate() + } + free(session) + return result +} + +private func sdpIsLocal(_ device: UnsafePointer) -> Bool { + var local = bdaddr_t(b: (0, 0, 0, 0xff, 0xff, 0xff)) + return bacmp(device, &local) == 0 +} + +private func sdpConnectLocal(_ session: UnsafeMutablePointer) -> Int32 { + let sock = socket(Int32(AF_UNIX), Int32(SOCK_STREAM.rawValue) | Int32(SOCK_CLOEXEC.rawValue), 0) + guard sock >= 0 else { return -1 } + session.pointee.sock = sock + session.pointee.local = 1 + + var address = sockaddr_un() + address.sun_family = sa_family_t(AF_UNIX) + let path = "/var/run/sdp" + withUnsafeMutableBytes(of: &address.sun_path) { buffer in + path.utf8CString.withUnsafeBytes { source in + buffer.copyMemory(from: UnsafeRawBufferPointer(start: source.baseAddress, count: source.count)) + } + } + + return withUnsafePointer(to: &address) { pointer -> Int32 in + pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPointer in + connect(sock, sockaddrPointer, socklen_t(MemoryLayout.size)) + } + } +} + +private func setL2CAPMTU(_ sock: Int32, _ mtu: UInt16) -> Int32 { + var options = l2cap_options() + var length = socklen_t(MemoryLayout.size) + guard getsockopt(sock, Int32(SOL_L2CAP), Int32(L2CAP_OPTIONS), &options, &length) >= 0 else { return -1 } + options.imtu = mtu + options.omtu = mtu + return setsockopt(sock, Int32(SOL_L2CAP), Int32(L2CAP_OPTIONS), &options, socklen_t(MemoryLayout.size)) +} + +private func sdpConnectL2CAP(_ source: UnsafePointer, _ destination: UnsafePointer, _ session: UnsafeMutablePointer) -> Int32 { + let flags = UInt32(bitPattern: session.pointee.flags) + var socketFlags = Int32(SOCK_SEQPACKET.rawValue) | Int32(SOCK_CLOEXEC.rawValue) + if flags & UInt32(SDP_NON_BLOCKING) != 0 { + socketFlags |= Int32(SOCK_NONBLOCK.rawValue) + } + + let sock = socket(Int32(AF_BLUETOOTH), socketFlags, Int32(BTPROTO_L2CAP)) + guard sock >= 0 else { return -1 } + session.pointee.sock = sock + session.pointee.local = 0 + + var address = sockaddr_l2() + address.l2_family = sa_family_t(AF_BLUETOOTH) + address.l2_psm = 0 + + var anyAddress = bdaddr_t(b: (0, 0, 0, 0, 0, 0)) + if bacmp(source, &anyAddress) != 0 { + address.l2_bdaddr = source.pointee + let bindResult = withUnsafePointer(to: &address) { pointer -> Int32 in + pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { bind(sock, $0, socklen_t(MemoryLayout.size)) } + } + guard bindResult >= 0 else { return -1 } + } + + if flags & UInt32(SDP_WAIT_ON_CLOSE) != 0 { + var lingerOption = linger(l_onoff: 1, l_linger: 1) + guard setsockopt(sock, Int32(SOL_SOCKET), SO_LINGER, &lingerOption, socklen_t(MemoryLayout.size)) >= 0 else { return -1 } + } + + if flags & UInt32(SDP_LARGE_MTU) != 0, setL2CAPMTU(sock, sdpLargeL2CAPMTU) < 0 { + return -1 + } + + address.l2_psm = UInt16(SDP_PSM) + address.l2_bdaddr = destination.pointee + + repeat { + let result = withUnsafePointer(to: &address) { pointer -> Int32 in + pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { connect(sock, $0, socklen_t(MemoryLayout.size)) } + } + if result == 0 { return 0 } + if result < 0, flags & UInt32(SDP_NON_BLOCKING) != 0, (errno == EAGAIN || errno == EINPROGRESS) { + return 0 + } + } while errno == EBUSY && flags & UInt32(SDP_RETRY_IF_BUSY) != 0 + + return -1 +} + +/// `sdp_session_t *sdp_connect(const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags)` +@c(sdp_connect) +public func sdp_connect(_ source: UnsafePointer?, _ destination: UnsafePointer?, _ flags: UInt32) -> UnsafeMutablePointer? { + guard let source, let destination else { errno = EINVAL; return nil } + if flags & UInt32(SDP_RETRY_IF_BUSY) != 0, flags & UInt32(SDP_NON_BLOCKING) != 0 { + errno = EINVAL + return nil + } + + guard let session = sdp_create(-1, flags) else { return nil } + + let connected: Int32 + if sdpIsLocal(destination) { + connected = sdpConnectLocal(session) + } else { + connected = sdpConnectL2CAP(source, destination, session) + } + + guard connected >= 0 else { + let savedError = errno + if session.pointee.sock >= 0 { close(session.pointee.sock) } + free(session.pointee.priv) + free(session) + errno = savedError + return nil + } + + return session +} + +/// `int sdp_get_socket(const sdp_session_t *session)` +@c(sdp_get_socket) +public func sdp_get_socket(_ session: UnsafePointer?) -> Int32 { + session?.pointee.sock ?? -1 +} + +/// `uint16_t sdp_gen_tid(sdp_session_t *session)` +@c(sdp_gen_tid) +public func sdp_gen_tid(_ session: UnsafeMutablePointer?) -> UInt16 { + guard let session else { return 0 } + let tid = session.pointee.tid + session.pointee.tid &+= 1 + return tid +} + +/// `int sdp_get_error(sdp_session_t *session)` +@c(sdp_get_error) +public func sdp_get_error(_ session: UnsafeMutablePointer?) -> Int32 { + guard let session, let transaction = sdpTransaction(session) else { return -1 } + return transaction.pointee.error +} + +/// `int sdp_set_notify(sdp_session_t *session, sdp_callback_t *func, void *udata)` +@c(sdp_set_notify) +public func sdp_set_notify(_ session: UnsafeMutablePointer?, _ callback: sdp_callback_t?, _ userData: UnsafeMutableRawPointer?) -> Int32 { + guard let session, let transaction = sdpTransaction(session) else { return -1 } + transaction.pointee.callback = callback + transaction.pointee.userData = userData + return 0 +} + +/// `int sdp_general_inquiry(inquiry_info *ii, int num_dev, int duration, uint8_t *found)` +@c(sdp_general_inquiry) +public func sdp_general_inquiry(_ inquiryInfo: UnsafeMutablePointer?, _ numberOfDevices: Int32, _ duration: Int32, _ found: UnsafeMutablePointer?) -> Int32 { + guard let found else { errno = EINVAL; return -1 } + var results = inquiryInfo + let count = withUnsafeMutablePointer(to: &results) { hci_inquiry(-1, 10, numberOfDevices, nil, $0, 0) } + guard count >= 0 else { return -1 } + found.pointee = UInt8(truncatingIfNeeded: count) + return 0 +} + +// MARK: - Wire I/O + +/// `static int sdp_send_req(sdp_session_t *session, uint8_t *buf, uint32_t size)` +internal func sdpSendRequest(_ session: UnsafeMutablePointer, _ buffer: UnsafeRawPointer, _ size: Int) -> Int32 { + var sent = 0 + while sent < size { + let n = send(session.pointee.sock, buffer.advanced(by: sent), size - sent, 0) + guard n >= 0 else { return -1 } + sent += n + } + return 0 +} + +/// `static int sdp_read_rsp(sdp_session_t *session, uint8_t *buf, uint32_t size)` +/// +/// FD_ZERO/FD_SET are C macros (not imported); this sets the single +/// descriptor's bit directly instead. +internal func sdpReadResponse(_ session: UnsafeMutablePointer, _ buffer: UnsafeMutableRawPointer, _ size: Int) -> Int { + let sock = session.pointee.sock + var readSet = fd_set() + withUnsafeMutableBytes(of: &readSet) { raw -> Void in + raw.initializeMemory(as: UInt8.self, repeating: 0) + let index = Int(sock) / (MemoryLayout.size * 8) + let bit = Int(sock) % (MemoryLayout.size * 8) + let longs = raw.bindMemory(to: Int.self) + longs[index] |= (1 << bit) + } + + var timeout = timeval(tv_sec: __time_t(SDP_RESPONSE_TIMEOUT), tv_usec: 0) + let ready = withUnsafeMutablePointer(to: &readSet) { setPointer -> Int32 in + select(sock + 1, setPointer, nil, nil, &timeout) + } + guard ready != 0 else { + errno = ETIMEDOUT + return -1 + } + return recv(sock, buffer, size, 0) +} From 3e09884d9813146b95daa4bd60982a19258c9e88 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 6 Aug 2026 22:22:59 -0400 Subject: [PATCH 58/73] Implement the blocking SDP requests sdp_service_search_req, sdp_service_attr_req and sdp_service_search_attr_req, built on the sdp_send_req_w4_rsp send/wait-for-response primitive, following continuation state until the response is complete. --- .../SDPSessionRequests.swift | 380 ++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/SDPSessionRequests.swift diff --git a/Sources/BluetoothLinuxABI/SDPSessionRequests.swift b/Sources/BluetoothLinuxABI/SDPSessionRequests.swift new file mode 100644 index 0000000..fa21b1f --- /dev/null +++ b/Sources/BluetoothLinuxABI/SDPSessionRequests.swift @@ -0,0 +1,380 @@ +// +// SDPSessionRequests.swift +// BluetoothLinux +// +// The three synchronous, blocking SDP requests (search, attribute +// fetch, and the combined search+attribute fetch) and the low-level +// send/wait-for-response primitive they're built on. +// + +import CBluetoothLinuxABI +import BluetoothABI +import Glibc + +private func pduHeader(_ buffer: UnsafeMutableRawPointer) -> UnsafeMutablePointer { + buffer.assumingMemoryBound(to: sdp_pdu_hdr_t.self) +} + +/// `int sdp_send_req_w4_rsp(sdp_session_t *session, uint8_t *reqbuf, uint8_t *rspbuf, uint32_t reqsize, uint32_t *rspsize)` +@c(sdp_send_req_w4_rsp) +public func sdp_send_req_w4_rsp( + _ session: UnsafeMutablePointer?, + _ requestBuffer: UnsafeMutablePointer?, + _ responseBuffer: UnsafeMutablePointer?, + _ requestSize: UInt32, + _ responseSize: UnsafeMutablePointer? +) -> Int32 { + guard let session, let requestBuffer, let responseBuffer, let responseSize else { + errno = EINVAL + return -1 + } + guard sdpSendRequest(session, UnsafeRawPointer(requestBuffer), Int(requestSize)) >= 0 else { return -1 } + + let n = sdpReadResponse(session, UnsafeMutableRawPointer(responseBuffer), Int(SDP_RSP_BUFFER_SIZE)) + guard n >= 0 else { return -1 } + + let requestHeader = pduHeader(UnsafeMutableRawPointer(requestBuffer)) + let responseHeader = pduHeader(UnsafeMutableRawPointer(responseBuffer)) + guard n != 0, requestHeader.pointee.tid == responseHeader.pointee.tid else { + errno = EPROTO + return -1 + } + + responseSize.pointee = UInt32(n) + return 0 +} + +/// `int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num, sdp_list_t **rsp)` +@c(sdp_service_search_req) +public func sdp_service_search_req( + _ session: UnsafeMutablePointer?, + _ search: UnsafePointer?, + _ maxRecordNumber: UInt16, + _ response: UnsafeMutablePointer?>? +) -> Int32 { + guard let session, let search, let response else { errno = EINVAL; return -1 } + guard let requestBuffer = malloc(Int(SDP_REQ_BUFFER_SIZE)), let responseBuffer = malloc(Int(SDP_RSP_BUFFER_SIZE)) else { + errno = ENOMEM + return -1 + } + defer { free(requestBuffer); free(responseBuffer) } + + let requestHeader = pduHeader(requestBuffer) + requestHeader.pointee.pdu_id = UInt8(SDP_SVC_SEARCH_REQ) + + var writePointer = requestBuffer.advanced(by: MemoryLayout.size) + let baseSize = MemoryLayout.size + + let seqLength = genSearchSequencePDU(writePointer, search) + guard seqLength >= 0 else { errno = EINVAL; return -1 } + writePointer = writePointer.advanced(by: Int(seqLength)) + + writePointer.assumingMemoryBound(to: UInt16.self).pointee = maxRecordNumber.bigEndian + writePointer = writePointer.advanced(by: MemoryLayout.size) + + let fixedSize = baseSize + Int(seqLength) + MemoryLayout.size + var continuationState: UnsafePointer? + response.pointee = nil + + repeat { + let cstateLength = copyContinuationState(writePointer, Int(SDP_REQ_BUFFER_SIZE) - fixedSize, continuationState) + let requestSize = fixedSize + Int(cstateLength) + + requestHeader.pointee.plen = UInt16(requestSize - baseSize).bigEndian + requestHeader.pointee.tid = sdp_gen_tid(session).bigEndian + + var responseSize: UInt32 = 0 + guard sdp_send_req_w4_rsp( + session, + requestBuffer.assumingMemoryBound(to: UInt8.self), + responseBuffer.assumingMemoryBound(to: UInt8.self), + UInt32(requestSize), + &responseSize + ) >= 0 else { return -1 } + + guard responseSize >= MemoryLayout.size else { errno = EPROTO; return -1 } + + let responseHeader = pduHeader(responseBuffer) + guard responseHeader.pointee.pdu_id != UInt8(SDP_ERROR_RSP) else { return -1 } + + var scanned: UInt32 = 0 + var readPointer = responseBuffer.advanced(by: MemoryLayout.size) + var dataLength = Int(responseSize) - MemoryLayout.size + + guard dataLength >= MemoryLayout.size * 2 else { errno = EPROTO; return -1 } + readPointer = readPointer.advanced(by: MemoryLayout.size) + scanned += UInt32(MemoryLayout.size) + dataLength -= MemoryLayout.size + + let recordCount = readPointer.assumingMemoryBound(to: UInt16.self).pointee.bigEndian + readPointer = readPointer.advanced(by: MemoryLayout.size) + scanned += UInt32(MemoryLayout.size) + dataLength -= MemoryLayout.size + + guard recordCount > 0 else { return -1 } + + extractRecordHandleSequence(readPointer.assumingMemoryBound(to: UInt8.self), dataLength, &response.pointee, Int(recordCount), &scanned) + + let responseLength = UInt32(responseHeader.pointee.plen.bigEndian) + if responseLength > scanned { + guard responseSize >= UInt32(MemoryLayout.size) + scanned + 1 else { errno = EPROTO; return -1 } + let cstatePointer = responseBuffer.advanced(by: MemoryLayout.size + Int(scanned)) + let cstateLength = cstatePointer.assumingMemoryBound(to: UInt8.self).pointee + continuationState = cstateLength > 0 ? UnsafePointer(cstatePointer.assumingMemoryBound(to: SDPContinuationState.self)) : nil + } else { + continuationState = nil + } + } while continuationState != nil + + return 0 +} + +/// `sdp_record_t *sdp_service_attr_req(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrids)` +@c(sdp_service_attr_req) +public func sdp_service_attr_req( + _ session: UnsafeMutablePointer?, + _ handle: UInt32, + _ requestType: sdp_attrreq_type_t, + _ attributeIDs: UnsafePointer? +) -> UnsafeMutablePointer? { + guard requestType == SDP_ATTR_REQ_INDIVIDUAL || requestType == SDP_ATTR_REQ_RANGE else { + errno = EINVAL + return nil + } + guard let session else { errno = EINVAL; return nil } + + guard let requestBuffer = malloc(Int(SDP_REQ_BUFFER_SIZE)), let responseBuffer = malloc(Int(SDP_RSP_BUFFER_SIZE)) else { + errno = ENOMEM + return nil + } + + var concatenated = sdp_buf_t() + var record: UnsafeMutablePointer? + defer { + free(requestBuffer) + free(responseBuffer) + free(concatenated.data) + } + + let requestHeader = pduHeader(requestBuffer) + requestHeader.pointee.pdu_id = UInt8(SDP_SVC_ATTR_REQ) + + var writePointer = requestBuffer.advanced(by: MemoryLayout.size) + let baseSize = MemoryLayout.size + + writePointer.assumingMemoryBound(to: UInt32.self).pointee = handle.bigEndian + writePointer = writePointer.advanced(by: MemoryLayout.size) + + writePointer.assumingMemoryBound(to: UInt16.self).pointee = UInt16(65535).bigEndian + writePointer = writePointer.advanced(by: MemoryLayout.size) + + let attributeType: Int32 = requestType == SDP_ATTR_REQ_INDIVIDUAL ? SDP_UINT16 : SDP_UINT32 + let seqLength = genAttrIDSequencePDU(writePointer, attributeIDs, UInt8(attributeType)) + guard seqLength >= 0 else { errno = EINVAL; return nil } + writePointer = writePointer.advanced(by: Int(seqLength)) + + let fixedSize = baseSize + MemoryLayout.size + MemoryLayout.size + Int(seqLength) + var continuationState: UnsafePointer? + var attributeListLength = 0 + + repeat { + let cstateLength = copyContinuationState(writePointer, Int(SDP_REQ_BUFFER_SIZE) - fixedSize, continuationState) + let requestSize = fixedSize + Int(cstateLength) + + requestHeader.pointee.tid = sdp_gen_tid(session).bigEndian + requestHeader.pointee.plen = UInt16(requestSize - baseSize).bigEndian + + var responseSize: UInt32 = 0 + let status = sdp_send_req_w4_rsp( + session, + requestBuffer.assumingMemoryBound(to: UInt8.self), + responseBuffer.assumingMemoryBound(to: UInt8.self), + UInt32(requestSize), + &responseSize + ) + guard responseSize >= MemoryLayout.size else { return nil } + guard status >= 0 else { return nil } + + let responseHeader = pduHeader(responseBuffer) + guard responseHeader.pointee.pdu_id != UInt8(SDP_ERROR_RSP) else { return nil } + + var readPointer = responseBuffer.advanced(by: MemoryLayout.size) + var dataLength = Int(responseSize) - MemoryLayout.size + guard dataLength >= MemoryLayout.size else { return nil } + + let responseCount = Int(readPointer.assumingMemoryBound(to: UInt16.self).pointee.bigEndian) + attributeListLength += responseCount + readPointer = readPointer.advanced(by: MemoryLayout.size) + dataLength -= MemoryLayout.size + + guard dataLength >= responseCount + 1 else { return nil } + let cstateLengthByte = readPointer.advanced(by: responseCount).assumingMemoryBound(to: UInt8.self).pointee + + if cstateLengthByte > 0 || concatenated.data_size != 0 { + continuationState = cstateLengthByte > 0 + ? UnsafePointer(readPointer.advanced(by: responseCount).assumingMemoryBound(to: SDPContinuationState.self)) + : nil + + let newSize = concatenated.data_size + UInt32(responseCount) + concatenated.data = realloc(concatenated.data, Int(newSize))?.assumingMemoryBound(to: UInt8.self) + concatenated.buf_size = newSize + memcpy(concatenated.data! + Int(concatenated.data_size), readPointer, responseCount) + concatenated.data_size = newSize + } else { + continuationState = nil + } + } while continuationState != nil + + guard attributeListLength > 0 else { return nil } + + var scanned: Int32 = 0 + if concatenated.data_size != 0 { + record = sdp_extract_pdu(concatenated.data, Int32(concatenated.data_size), &scanned) + } else { + // No continuation-state fragmentation occurred: the last (and + // only) response body is still in responseBuffer. + let readPointer = responseBuffer.advanced(by: MemoryLayout.size + MemoryLayout.size) + record = sdp_extract_pdu(readPointer, Int32(attributeListLength), &scanned) + } + + return record +} + +/// `int sdp_service_search_attr_req(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrids, sdp_list_t **rsp)` +@c(sdp_service_search_attr_req) +public func sdp_service_search_attr_req( + _ session: UnsafeMutablePointer?, + _ search: UnsafePointer?, + _ requestType: sdp_attrreq_type_t, + _ attributeIDs: UnsafePointer?, + _ response: UnsafeMutablePointer?>? +) -> Int32 { + guard requestType == SDP_ATTR_REQ_INDIVIDUAL || requestType == SDP_ATTR_REQ_RANGE else { + errno = EINVAL + return -1 + } + guard let session, let search, let response else { errno = EINVAL; return -1 } + + guard let requestBuffer = malloc(Int(SDP_REQ_BUFFER_SIZE)), let responseBuffer = malloc(Int(SDP_RSP_BUFFER_SIZE)) else { + errno = ENOMEM + return -1 + } + var concatenated = sdp_buf_t() + defer { + free(requestBuffer) + free(responseBuffer) + free(concatenated.data) + } + + let requestHeader = pduHeader(requestBuffer) + requestHeader.pointee.pdu_id = UInt8(SDP_SVC_SEARCH_ATTR_REQ) + + var writePointer = requestBuffer.advanced(by: MemoryLayout.size) + let baseSize = MemoryLayout.size + + let searchSeqLength = genSearchSequencePDU(writePointer, search) + guard searchSeqLength >= 0 else { errno = EINVAL; return -1 } + writePointer = writePointer.advanced(by: Int(searchSeqLength)) + + writePointer.assumingMemoryBound(to: UInt16.self).pointee = sdpMaxAttrLength.bigEndian + writePointer = writePointer.advanced(by: MemoryLayout.size) + + let attributeType: Int32 = requestType == SDP_ATTR_REQ_INDIVIDUAL ? SDP_UINT16 : SDP_UINT32 + let attrSeqLength = genAttrIDSequencePDU(writePointer, attributeIDs, UInt8(attributeType)) + guard attrSeqLength >= 0 else { errno = EINVAL; return -1 } + writePointer = writePointer.advanced(by: Int(attrSeqLength)) + + response.pointee = nil + let fixedSize = baseSize + Int(searchSeqLength) + MemoryLayout.size + Int(attrSeqLength) + var continuationState: UnsafePointer? + var attributeListLength = 0 + + repeat { + requestHeader.pointee.tid = sdp_gen_tid(session).bigEndian + + let cstateLength = copyContinuationState(writePointer, Int(SDP_REQ_BUFFER_SIZE) - fixedSize, continuationState) + let requestSize = fixedSize + Int(cstateLength) + requestHeader.pointee.plen = UInt16(requestSize - baseSize).bigEndian + + var responseSize: UInt32 = 0 + let status = sdp_send_req_w4_rsp( + session, + requestBuffer.assumingMemoryBound(to: UInt8.self), + responseBuffer.assumingMemoryBound(to: UInt8.self), + UInt32(requestSize), + &responseSize + ) + guard responseSize >= MemoryLayout.size else { return -1 } + guard status >= 0 else { return -1 } + + let responseHeader = pduHeader(responseBuffer) + guard responseHeader.pointee.pdu_id != UInt8(SDP_ERROR_RSP) else { return -1 } + + var readPointer = responseBuffer.advanced(by: MemoryLayout.size) + var dataLength = Int(responseSize) - MemoryLayout.size + guard dataLength >= MemoryLayout.size else { errno = EPROTO; return -1 } + + let responseCount = Int(readPointer.assumingMemoryBound(to: UInt16.self).pointee.bigEndian) + attributeListLength += responseCount + readPointer = readPointer.advanced(by: MemoryLayout.size) + dataLength -= MemoryLayout.size + + guard dataLength >= responseCount + 1 else { errno = EPROTO; return -1 } + let cstateLengthByte = readPointer.advanced(by: responseCount).assumingMemoryBound(to: UInt8.self).pointee + + if cstateLengthByte > 0 || concatenated.data_size != 0 { + continuationState = cstateLengthByte > 0 + ? UnsafePointer(readPointer.advanced(by: responseCount).assumingMemoryBound(to: SDPContinuationState.self)) + : nil + + let newSize = concatenated.data_size + UInt32(responseCount) + concatenated.data = realloc(concatenated.data, Int(newSize))?.assumingMemoryBound(to: UInt8.self) + concatenated.buf_size = newSize + memcpy(concatenated.data! + Int(concatenated.data_size), readPointer, responseCount) + concatenated.data_size = newSize + } else { + continuationState = nil + } + } while continuationState != nil + + guard attributeListLength > 0 else { return 0 } + + var readPointer: UnsafeMutableRawPointer + var remaining: Int + if concatenated.data_size != 0 { + readPointer = UnsafeMutableRawPointer(concatenated.data!) + remaining = Int(concatenated.data_size) + } else { + readPointer = responseBuffer.advanced(by: MemoryLayout.size + MemoryLayout.size) + remaining = attributeListLength + } + + var dataType: UInt8 = 0 + var outerSeqLength: Int32 = 0 + let outerScanned = sdp_extract_seqtype(readPointer, Int32(remaining), &dataType, &outerSeqLength) + guard outerScanned != 0, outerSeqLength != 0 else { return 0 } + + readPointer = readPointer.advanced(by: Int(outerScanned)) + remaining -= Int(outerScanned) + var totalScanned = Int(outerScanned) + var records: UnsafeMutablePointer? + + repeat { + var recordSize: Int32 = 0 + guard let record = sdp_extract_pdu(readPointer, Int32(remaining), &recordSize) else { + errno = EPROTO + return -1 + } + guard recordSize > 0 else { + sdp_record_free(record) + break + } + totalScanned += Int(recordSize) + readPointer = readPointer.advanced(by: Int(recordSize)) + remaining -= Int(recordSize) + records = sdp_list_append(records, record) + } while totalScanned < attributeListLength && remaining > 0 + + response.pointee = records + return 0 +} From a98239b8e40537b414936b9e67195e8a70c404e5 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 6 Aug 2026 22:23:10 -0400 Subject: [PATCH 59/73] Mark the SDP session symbols as implemented Registers the twelve socket-bound SDP client session functions so gen_stubs.py stops emitting stubs for them, and retargets the pure sdp_* note at PureSwift/Bluetooth's Sources/BluetoothABI, which is where they live as of 8.1.0. --- scripts/implemented.txt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/implemented.txt b/scripts/implemented.txt index 0a95975..baea802 100644 --- a/scripts/implemented.txt +++ b/scripts/implemented.txt @@ -43,7 +43,7 @@ bt_uuid_to_string bt_uuid_to_uuid128 # The pure (non-socket) sdp.c symbols (75), implemented in -# PureSwift/Bluetooth's Sources/BluetoothSDP. +# PureSwift/Bluetooth's Sources/BluetoothABI. sdp_add_lang_attr sdp_append_to_buf sdp_append_to_pdu @@ -231,3 +231,19 @@ hci_write_local_name hci_write_simple_pairing_mode hci_write_stored_link_key hci_write_voice_setting + +# The socket-bound SDP client session functions (12), implemented in +# Sources/BluetoothLinuxABI/SDPSession.swift, SDPSessionRequests.swift +# and SDPPDU.swift. +sdp_close +sdp_connect +sdp_create +sdp_gen_tid +sdp_general_inquiry +sdp_get_error +sdp_get_socket +sdp_send_req_w4_rsp +sdp_service_attr_req +sdp_service_search_attr_req +sdp_service_search_req +sdp_set_notify From 37149dbbb0ebfba376880452a16a0e958fe60db1 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 6 Aug 2026 22:23:10 -0400 Subject: [PATCH 60/73] Regenerate C ABI stub table 13 stubs remaining, 215 implemented of 218 exported. --- Sources/CBluetoothLinuxABI/gen/cbt_stubs.c | 60 ---------------------- 1 file changed, 60 deletions(-) diff --git a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c index ab9acc2..1d703cb 100644 --- a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c +++ b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c @@ -30,21 +30,6 @@ static void cbt_unimplemented(const char *symbol) abort(); } -int sdp_close(sdp_session_t *session) -{ - cbt_unimplemented("sdp_close"); -} - -sdp_session_t *sdp_connect(const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags) -{ - cbt_unimplemented("sdp_connect"); -} - -sdp_session_t *sdp_create(int sk, uint32_t flags) -{ - cbt_unimplemented("sdp_create"); -} - int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags) { cbt_unimplemented("sdp_device_record_register"); @@ -75,26 +60,6 @@ int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, ui cbt_unimplemented("sdp_device_record_update_binary"); } -uint16_t sdp_gen_tid(sdp_session_t *session) -{ - cbt_unimplemented("sdp_gen_tid"); -} - -int sdp_general_inquiry(inquiry_info *ii, int dev_num, int duration, uint8_t *found) -{ - cbt_unimplemented("sdp_general_inquiry"); -} - -int sdp_get_error(sdp_session_t *session) -{ - cbt_unimplemented("sdp_get_error"); -} - -int sdp_get_socket(const sdp_session_t *session) -{ - cbt_unimplemented("sdp_get_socket"); -} - int sdp_process(sdp_session_t *session) { cbt_unimplemented("sdp_process"); @@ -115,21 +80,11 @@ int sdp_record_update(sdp_session_t *sess, const sdp_record_t *rec) cbt_unimplemented("sdp_record_update"); } -int sdp_send_req_w4_rsp(sdp_session_t *session, uint8_t *req, uint8_t *rsp, uint32_t reqsize, uint32_t *rspsize) -{ - cbt_unimplemented("sdp_send_req_w4_rsp"); -} - int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) { cbt_unimplemented("sdp_service_attr_async"); } -sdp_record_t *sdp_service_attr_req(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) -{ - cbt_unimplemented("sdp_service_attr_req"); -} - int sdp_service_search_async(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num) { cbt_unimplemented("sdp_service_search_async"); @@ -140,18 +95,3 @@ int sdp_service_search_attr_async(sdp_session_t *session, const sdp_list_t *sear cbt_unimplemented("sdp_service_search_attr_async"); } -int sdp_service_search_attr_req(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list, sdp_list_t **rsp_list) -{ - cbt_unimplemented("sdp_service_search_attr_req"); -} - -int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num, sdp_list_t **rsp_list) -{ - cbt_unimplemented("sdp_service_search_req"); -} - -int sdp_set_notify(sdp_session_t *session, sdp_callback_t *func, void *udata) -{ - cbt_unimplemented("sdp_set_notify"); -} - From 3770884f9aad4ea7617e866027fc7491fd033c17 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 6 Aug 2026 22:26:41 -0400 Subject: [PATCH 61/73] Put BluetoothABI on the CMake module search path The SDP session sources import BluetoothABI for the PDU codec, which the SwiftPM manifest already provides but the CMake build did not, so libBluetoothLinuxABI.a failed to compile with "no such module". The link has to be declared after add_subdirectory, which is where the target is defined. --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e5373a5..fbcadb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,6 +168,11 @@ set(BLUETOOTH_ABI_INSTALL_HEADERS OFF CACHE BOOL "" FORCE) add_subdirectory("${BLUETOOTH_PACKAGE_PATH}" "${CMAKE_CURRENT_BINARY_DIR}/Bluetooth") +# The SDP session sources `import BluetoothABI` for the PDU codec, so +# the module has to be on the search path. This has to come after +# add_subdirectory, which is where the target is defined. +target_link_libraries(BluetoothLinuxABI PUBLIC BluetoothABI) + # --------------------------------------------------------------------- # libbluetooth.so.3 # --------------------------------------------------------------------- From 39bb8c936db06ce4234c4c096c69683d1e195968 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 00:23:29 -0400 Subject: [PATCH 62/73] Add an alignment-safe store for PDU fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every multi-byte field in an SDP PDU sits wherever the preceding fields leave it — the 5-byte header alone puts the body on an odd address — so writing one through a typed pointer is not valid even where x86 tolerates it. The registration and asynchronous request builders use this instead. --- Sources/BluetoothLinuxABI/SDPPDU.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/BluetoothLinuxABI/SDPPDU.swift b/Sources/BluetoothLinuxABI/SDPPDU.swift index 8914be4..6de7f4a 100644 --- a/Sources/BluetoothLinuxABI/SDPPDU.swift +++ b/Sources/BluetoothLinuxABI/SDPPDU.swift @@ -25,6 +25,17 @@ internal struct SDPContinuationState { var data: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) } +/// Write a fixed-width integer at a byte offset that carries no +/// alignment guarantee. Every multi-byte field in an SDP PDU sits at +/// whatever offset the preceding fields leave it at — the 5-byte +/// header alone puts the body on an odd address — so nothing here may +/// go through a typed pointer. +internal func storeUnaligned(_ value: T, to pointer: UnsafeMutableRawPointer, offset: Int) { + withUnsafeBytes(of: value) { source in + pointer.advanced(by: offset).copyMemory(from: source.baseAddress!, byteCount: source.count) + } +} + /// `static int copy_cstate(uint8_t *pdata, int pdata_len, const sdp_cstate_t *cstate)` internal func copyContinuationState(_ destination: UnsafeMutableRawPointer, _ availableLength: Int, _ cstate: UnsafePointer?) -> Int32 { guard let cstate else { From be6bec02dea8a30d38ccf4c473de10f93866de6e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 00:23:29 -0400 Subject: [PATCH 63/73] Implement the SDP service record registration functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sdp_record_register/unregister/update, their sdp_device_record_* forms, and the two _binary entry points underneath them. The PDUs these exchange (0x75 through 0x80) are explicitly outside the Bluetooth specification — the vendored sdp.h says so where it defines them — so the wire layouts were recovered from the reference libbluetooth.so.3, which is the only thing that documents them. Two of its behaviours are reproduced rather than corrected because a client can observe them: the response status is loaded in host order rather than with ntohs, and sdp_device_record_update_binary is an exported stub that returns -1 without sending anything. Bounds that the reference delegates to _FORTIFY_SOURCE (where an oversized record aborts the process) are checked here and reported as EINVAL instead, which cannot break a caller that already worked. --- .../SDPRecordRegistration.swift | 356 ++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/SDPRecordRegistration.swift diff --git a/Sources/BluetoothLinuxABI/SDPRecordRegistration.swift b/Sources/BluetoothLinuxABI/SDPRecordRegistration.swift new file mode 100644 index 0000000..a2e3db6 --- /dev/null +++ b/Sources/BluetoothLinuxABI/SDPRecordRegistration.swift @@ -0,0 +1,356 @@ +// +// SDPRecordRegistration.swift +// BluetoothLinux +// +// Registering, updating and removing a service record with the local +// SDP server. +// +// The PDUs these send (0x75 through 0x80) are not part of the +// Bluetooth specification — the vendored sdp.h says exactly that +// where it defines them, calling them "some additions to support +// service registration ... outside the scope of the Bluetooth +// specification". There is therefore no published wire format to +// implement against, and the layouts below were recovered from the +// reference libbluetooth.so.3 so that a client compiled against it +// keeps talking to the same server unchanged. +// +// Two reference behaviours are reproduced deliberately rather than +// corrected, because a client sees them and may depend on them; each +// is called out at the point it appears. +// +// All of these require a session connected to the *local* SDP server +// (sdp_connect with BDADDR_LOCAL, which sdp_connect turns into a Unix +// socket); a session to a remote device fails with EREMOTE. +// +// Like the rest of the SDP session surface, none of this has been +// exercised against a real SDP server yet. +// + +import CBluetoothLinuxABI +import BluetoothABI +import Glibc + +private func pduHeader(_ buffer: UnsafeMutableRawPointer) -> UnsafeMutablePointer { + buffer.assumingMemoryBound(to: sdp_pdu_hdr_t.self) +} + +private let headerSize = MemoryLayout.size + +/// The 16-bit status of a register/update/remove response. +/// +/// The reference loads it in host order rather than calling ntohs, so +/// a non-zero status reaches the caller byte-swapped. Clients were +/// written against that, and success (zero) reads the same either way, +/// so the load is left as-is rather than quietly changing the value a +/// client gets back. +private func responseStatus(_ response: UnsafeMutableRawPointer) -> Int32 { + Int32(response.loadUnaligned(fromByteOffset: 5, as: UInt16.self)) +} + +/// Allocate the request and response buffers shared by all three +/// operations, and start the request PDU with its id and a fresh +/// transaction ID. +private func beginRequest( + _ session: UnsafeMutablePointer, + _ pduID: Int32 +) -> (request: UnsafeMutableRawPointer, response: UnsafeMutableRawPointer)? { + guard let request = malloc(Int(SDP_REQ_BUFFER_SIZE)) else { return nil } + guard let response = malloc(Int(SDP_RSP_BUFFER_SIZE)) else { + free(request) + return nil + } + let header = pduHeader(request) + header.pointee.pdu_id = UInt8(pduID) + header.pointee.tid = sdp_gen_tid(session).bigEndian + return (request, response) +} + +// MARK: - Register + +/// `int sdp_device_record_register_binary(sdp_session_t *session, bdaddr_t *device, uint8_t *data, uint32_t size, uint8_t flags, uint32_t *handle)` +@c(sdp_device_record_register_binary) +public func sdp_device_record_register_binary( + _ session: UnsafeMutablePointer?, + _ device: UnsafeMutablePointer?, + _ data: UnsafeMutablePointer?, + _ size: UInt32, + _ flags: UInt8, + _ handle: UnsafeMutablePointer? +) -> Int32 { + // The reference dereferences these without checking; refusing a + // null pointer can only turn a crash into an error return. + guard let session, let device, let data else { errno = EINVAL; return -1 } + guard session.pointee.local != 0 else { errno = EREMOTE; return -1 } + + guard let (requestBuffer, responseBuffer) = beginRequest(session, SDP_SVC_REGISTER_REQ) else { + errno = ENOMEM + return -1 + } + defer { free(requestBuffer); free(responseBuffer) } + + // A record registered on behalf of a specific remote device + // carries that address between the flags byte and the record + // itself, and sets SDP_DEVICE_RECORD so the server knows to expect + // it. Registering for this host omits both. + var anyAddress = bdaddr_t(b: (0, 0, 0, 0, 0, 0)) + let recordOffset: Int + if bacmp(device, &anyAddress) != 0 { + storeUnaligned(flags | UInt8(SDP_DEVICE_RECORD), to: requestBuffer, offset: 5) + withUnsafeBytes(of: device.pointee) { source in + requestBuffer.advanced(by: 6).copyMemory(from: source.baseAddress!, byteCount: MemoryLayout.size) + } + recordOffset = 6 + MemoryLayout.size + } else { + storeUnaligned(flags, to: requestBuffer, offset: 5) + recordOffset = 6 + } + + // The reference leaves this to _FORTIFY_SOURCE, so an oversized + // record aborts the process there. Refusing it is strictly safer + // and cannot break a caller that was already working. + guard Int(size) <= Int(SDP_REQ_BUFFER_SIZE) - recordOffset else { errno = EINVAL; return -1 } + memcpy(requestBuffer.advanced(by: recordOffset), data, Int(size)) + + let requestSize = recordOffset + Int(size) + pduHeader(requestBuffer).pointee.plen = UInt16(requestSize - headerSize).bigEndian + + var responseSize: UInt32 = 0 + let status = sdp_send_req_w4_rsp( + session, + requestBuffer.assumingMemoryBound(to: UInt8.self), + responseBuffer.assumingMemoryBound(to: UInt8.self), + UInt32(requestSize), + &responseSize + ) + guard status >= 0 else { return status } + guard responseSize > 4 else { errno = EPROTO; return -1 } + + switch Int32(responseBuffer.load(as: UInt8.self)) { + case SDP_ERROR_RSP: + errno = EINVAL + return -1 + case SDP_SVC_REGISTER_RSP: + // The assigned handle follows the header; a response too short + // to hold one is malformed even though the id was right. + guard responseSize > 8 else { errno = EPROTO; return -1 } + handle?.pointee = UInt32(bigEndian: responseBuffer.loadUnaligned(fromByteOffset: 5, as: UInt32.self)) + return status + default: + errno = EPROTO + return -1 + } +} + +/// `int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags)` +@c(sdp_device_record_register) +public func sdp_device_record_register( + _ session: UnsafeMutablePointer?, + _ device: UnsafeMutablePointer?, + _ record: UnsafeMutablePointer?, + _ flags: UInt8 +) -> Int32 { + guard let record else { errno = EINVAL; return -1 } + + // A record that already names a handle keeps it, with the + // attribute rewritten so the encoded PDU agrees with the struct + // field. Zero means "server assigns one" and all-ones is reserved, + // so neither is sent. + let requested = record.pointee.handle + if requested != 0, requested != .max { + var value = requested + sdp_attr_replace(record, UInt16(SDP_ATTR_RECORD_HANDLE), sdp_data_alloc(UInt8(SDP_UINT32), &value)) + } + + var pdu = sdp_buf_t() + guard sdp_gen_record_pdu(record, &pdu) >= 0 else { errno = ENOMEM; return -1 } + + var assigned: UInt32 = 0 + let status = sdp_device_record_register_binary(session, device, pdu.data, pdu.data_size, flags, &assigned) + free(pdu.data) + guard status == 0 else { return status } + + // Write the handle the server picked back into both the struct + // field and the record's own attribute, so a later update or + // unregister of the same record refers to the right one. + let data = sdp_data_alloc(UInt8(SDP_UINT32), &assigned) + record.pointee.handle = assigned + sdp_attr_replace(record, UInt16(SDP_ATTR_RECORD_HANDLE), data) + return status +} + +/// `int sdp_record_register(sdp_session_t *session, sdp_record_t *rec, uint8_t flags)` +@c(sdp_record_register) +public func sdp_record_register( + _ session: UnsafeMutablePointer?, + _ record: UnsafeMutablePointer?, + _ flags: UInt8 +) -> Int32 { + var anyAddress = bdaddr_t(b: (0, 0, 0, 0, 0, 0)) + return sdp_device_record_register(session, &anyAddress, record, flags) +} + +// MARK: - Unregister + +/// `int sdp_device_record_unregister_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle)` +/// +/// `device` is part of the signature but, as in the reference, never +/// read: a remove request carries only the record handle. +@c(sdp_device_record_unregister_binary) +public func sdp_device_record_unregister_binary( + _ session: UnsafeMutablePointer?, + _ device: UnsafeMutablePointer?, + _ handle: UInt32 +) -> Int32 { + guard handle != 0 else { errno = EINVAL; return -1 } + guard let session else { errno = EINVAL; return -1 } + guard session.pointee.local != 0 else { errno = EREMOTE; return -1 } + + guard let (requestBuffer, responseBuffer) = beginRequest(session, SDP_SVC_REMOVE_REQ) else { + errno = ENOMEM + return -1 + } + defer { free(requestBuffer); free(responseBuffer) } + + storeUnaligned(handle.bigEndian, to: requestBuffer, offset: 5) + let requestSize = 5 + MemoryLayout.size + pduHeader(requestBuffer).pointee.plen = UInt16(requestSize - headerSize).bigEndian + + var responseSize: UInt32 = 0 + let status = sdp_send_req_w4_rsp( + session, + requestBuffer.assumingMemoryBound(to: UInt8.self), + responseBuffer.assumingMemoryBound(to: UInt8.self), + UInt32(requestSize), + &responseSize + ) + guard status >= 0 else { return status } + guard responseSize > 6 else { errno = EPROTO; return -1 } + + switch Int32(responseBuffer.load(as: UInt8.self)) { + case SDP_ERROR_RSP: + errno = EINVAL + return -1 + case SDP_SVC_REMOVE_RSP: + return responseStatus(responseBuffer) + default: + errno = EPROTO + return -1 + } +} + +/// `int sdp_device_record_unregister(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec)` +/// +/// Frees the record on success — the caller must not touch it again. +@c(sdp_device_record_unregister) +public func sdp_device_record_unregister( + _ session: UnsafeMutablePointer?, + _ device: UnsafeMutablePointer?, + _ record: UnsafeMutablePointer? +) -> Int32 { + guard let record else { errno = EINVAL; return -1 } + let status = sdp_device_record_unregister_binary(session, device, record.pointee.handle) + if status == 0 { + sdp_record_free(record) + } + return status +} + +/// `int sdp_record_unregister(sdp_session_t *session, sdp_record_t *rec)` +@c(sdp_record_unregister) +public func sdp_record_unregister( + _ session: UnsafeMutablePointer?, + _ record: UnsafeMutablePointer? +) -> Int32 { + var anyAddress = bdaddr_t(b: (0, 0, 0, 0, 0, 0)) + return sdp_device_record_unregister(session, &anyAddress, record) +} + +// MARK: - Update + +/// `int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle, uint8_t *data, uint32_t size)` +/// +/// Exported but not implemented in the reference either: it returns -1 +/// immediately, without touching errno and without sending anything. +/// Callers reach the update path through sdp_device_record_update, +/// which encodes the record itself. +@c(sdp_device_record_update_binary) +public func sdp_device_record_update_binary( + _ session: UnsafeMutablePointer?, + _ device: UnsafeMutablePointer?, + _ handle: UInt32, + _ data: UnsafeMutablePointer?, + _ size: UInt32 +) -> Int32 { + -1 +} + +/// `int sdp_device_record_update(sdp_session_t *session, bdaddr_t *device, const sdp_record_t *rec)` +/// +/// `device` is part of the signature but, as in the reference, never +/// read: the update request identifies the record by handle alone. +@c(sdp_device_record_update) +public func sdp_device_record_update( + _ session: UnsafeMutablePointer?, + _ device: UnsafeMutablePointer?, + _ record: UnsafePointer? +) -> Int32 { + guard let session, let record else { errno = EINVAL; return -1 } + let handle = record.pointee.handle + // An unregistered record has no handle to update. + guard handle != 0 else { errno = EINVAL; return -1 } + guard session.pointee.local != 0 else { errno = EREMOTE; return -1 } + + guard let (requestBuffer, responseBuffer) = beginRequest(session, SDP_SVC_UPDATE_REQ) else { + errno = ENOMEM + return -1 + } + defer { free(requestBuffer); free(responseBuffer) } + + storeUnaligned(handle.bigEndian, to: requestBuffer, offset: 5) + let recordOffset = 5 + MemoryLayout.size + + var pdu = sdp_buf_t() + guard sdp_gen_record_pdu(record, &pdu) >= 0 else { errno = ENOMEM; return -1 } + defer { free(pdu.data) } + + // As in the register path, the reference leaves the bound to + // _FORTIFY_SOURCE; refusing the write is safer and equivalent for + // any record that fit before. + guard Int(pdu.data_size) <= Int(SDP_REQ_BUFFER_SIZE) - recordOffset else { errno = EINVAL; return -1 } + memcpy(requestBuffer.advanced(by: recordOffset), pdu.data, Int(pdu.data_size)) + + let requestSize = recordOffset + Int(pdu.data_size) + pduHeader(requestBuffer).pointee.plen = UInt16(requestSize - headerSize).bigEndian + + var responseSize: UInt32 = 0 + let status = sdp_send_req_w4_rsp( + session, + requestBuffer.assumingMemoryBound(to: UInt8.self), + responseBuffer.assumingMemoryBound(to: UInt8.self), + UInt32(requestSize), + &responseSize + ) + guard status >= 0 else { return status } + guard responseSize > 6 else { errno = EPROTO; return -1 } + + switch Int32(responseBuffer.load(as: UInt8.self)) { + case SDP_ERROR_RSP: + errno = EINVAL + return -1 + case SDP_SVC_UPDATE_RSP: + return responseStatus(responseBuffer) + default: + errno = EPROTO + return -1 + } +} + +/// `int sdp_record_update(sdp_session_t *sess, const sdp_record_t *rec)` +@c(sdp_record_update) +public func sdp_record_update( + _ session: UnsafeMutablePointer?, + _ record: UnsafePointer? +) -> Int32 { + var anyAddress = bdaddr_t(b: (0, 0, 0, 0, 0, 0)) + return sdp_device_record_update(session, &anyAddress, record) +} From 6830eca6d2e47b416ab1902af71700fa52f2fac9 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 00:23:42 -0400 Subject: [PATCH 64/73] Implement the asynchronous SDP requests and sdp_process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sdp_service_search_async, sdp_service_attr_async and sdp_service_search_attr_async only build and send; sdp_process does everything about the reply — reassembling a response split across continuation states, resending the request for each next fragment, and finally invoking the sdp_set_notify callback. sdp_process's return value is easy to misread: 0 means "not finished, call me again when the socket is readable" and -1 means the transaction is over and the callback has run. -1 is the normal ending and says nothing about success. The record-count accumulation in a fragmented search response adds two big-endian values without swapping either, so a count that carries out of the low byte comes out wrong. That is what the reference writes and what a client reads back, so it is reproduced with a comment rather than fixed. --- .../BluetoothLinuxABI/SDPSessionAsync.swift | 399 ++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 Sources/BluetoothLinuxABI/SDPSessionAsync.swift diff --git a/Sources/BluetoothLinuxABI/SDPSessionAsync.swift b/Sources/BluetoothLinuxABI/SDPSessionAsync.swift new file mode 100644 index 0000000..66c6d8d --- /dev/null +++ b/Sources/BluetoothLinuxABI/SDPSessionAsync.swift @@ -0,0 +1,399 @@ +// +// SDPSessionAsync.swift +// BluetoothLinux +// +// The non-blocking half of the SDP client: the three request +// functions that send a PDU and return immediately, and sdp_process, +// which the caller drives from its own event loop whenever the +// session socket becomes readable. +// +// The division of labour is that a request function only ever builds +// and sends; everything about the reply — reassembling a response +// split across continuation states, resending for the next fragment, +// and finally invoking the callback registered with sdp_set_notify — +// belongs to sdp_process. +// +// sdp_process's return value reads backwards until you know the +// convention: 0 means "not finished, call me again", and -1 means the +// transaction is over and the callback has already run. -1 is +// therefore the normal, successful ending, and says nothing about +// whether the transaction succeeded; that is what the callback's +// status argument and sdp_get_error are for. +// +// Like the rest of the SDP session surface, none of this has been +// exercised against a real SDP server yet. +// + +import CBluetoothLinuxABI +import BluetoothABI +import Glibc + +private func pduHeader(_ buffer: UnsafeMutableRawPointer) -> UnsafeMutablePointer { + buffer.assumingMemoryBound(to: sdp_pdu_hdr_t.self) +} + +private let headerSize = MemoryLayout.size + +/// The status a caller sees when the failure was local — a short read, +/// a malformed reply, a socket error — rather than an SDP error +/// response carrying a real code. The header documents this as the +/// signal to call sdp_get_error for the actual reason. +private let sdpUnknownStatus: UInt16 = 0xFFFF + +// MARK: - Request setup + +/// Ready the transaction for a new request: discard whatever the +/// previous one accumulated, make sure the request buffer exists, and +/// clear it. +private func beginRequest(_ transaction: UnsafeMutablePointer) -> UnsafeMutableRawPointer? { + free(transaction.pointee.responseBuffer.data) + transaction.pointee.responseBuffer = sdp_buf_t() + + if transaction.pointee.requestBuffer == nil { + guard let buffer = malloc(Int(SDP_REQ_BUFFER_SIZE)) else { + transaction.pointee.error = ENOMEM + return nil + } + transaction.pointee.requestBuffer = buffer + } + + // The request buffer outlives the request — sdp_process rewrites + // its continuation state in place — so it is cleared here rather + // than at allocation. + let buffer = transaction.pointee.requestBuffer! + memset(buffer, 0, Int(SDP_REQ_BUFFER_SIZE)) + return buffer +} + +/// Abandon a request that could not be built or sent. The reason goes +/// into the transaction rather than errno, because the caller reads it +/// back with sdp_get_error once the callback tells it something went +/// wrong. +private func failRequest(_ transaction: UnsafeMutablePointer, _ error: Int32) -> Int32 { + transaction.pointee.error = error + free(transaction.pointee.requestBuffer) + transaction.pointee.requestBuffer = nil + return -1 +} + +/// Terminate the request with an empty continuation state, fill in the +/// header length and send it. +/// +/// `size` is the request length *excluding* that trailing +/// continuation-state byte, and that is what the transaction records: +/// it is the offset sdp_process overwrites when the server asks for +/// the request to be repeated with a real continuation state. +private func finishRequest( + _ session: UnsafeMutablePointer, + _ transaction: UnsafeMutablePointer, + _ size: Int +) -> Int32 { + let buffer = transaction.pointee.requestBuffer! + transaction.pointee.requestSize = UInt32(size) + storeUnaligned(UInt8(0), to: buffer, offset: size) + + let total = size + 1 + pduHeader(buffer).pointee.plen = UInt16(total - headerSize).bigEndian + return sendRequest(session, transaction, total) +} + +/// Write the whole request to the socket. +/// +/// This does not go through sdpSendRequest because a failed write has +/// to land in the transaction rather than be reported through errno. +private func sendRequest( + _ session: UnsafeMutablePointer, + _ transaction: UnsafeMutablePointer, + _ length: Int +) -> Int32 { + let buffer = transaction.pointee.requestBuffer! + var sent = 0 + while sent < length { + let written = send(session.pointee.sock, buffer.advanced(by: sent), length - sent, 0) + guard written >= 0 else { return failRequest(transaction, errno) } + sent += written + } + return 0 +} + +/// The attribute-ID data type for a request type. +/// +/// Unlike the blocking requests, the asynchronous ones do not reject +/// an out-of-range value: anything that is not SDP_ATTR_REQ_INDIVIDUAL +/// is encoded as a range. +private func attributeDataType(_ requestType: sdp_attrreq_type_t) -> UInt8 { + UInt8(requestType == SDP_ATTR_REQ_INDIVIDUAL ? SDP_UINT16 : SDP_UINT32) +} + +// MARK: - Requests + +/// `int sdp_service_search_async(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num)` +@c(sdp_service_search_async) +public func sdp_service_search_async( + _ session: UnsafeMutablePointer?, + _ search: UnsafePointer?, + _ maxRecordNumber: UInt16 +) -> Int32 { + guard let session, let transaction = sdpTransaction(session) else { return -1 } + guard let buffer = beginRequest(transaction) else { return -1 } + + pduHeader(buffer).pointee.pdu_id = UInt8(SDP_SVC_SEARCH_REQ) + pduHeader(buffer).pointee.tid = sdp_gen_tid(session).bigEndian + + var offset = headerSize + let seqLength = genSearchSequencePDU(buffer.advanced(by: offset), search) + guard seqLength >= 0 else { return failRequest(transaction, EINVAL) } + offset += Int(seqLength) + + storeUnaligned(maxRecordNumber.bigEndian, to: buffer, offset: offset) + offset += MemoryLayout.size + + return finishRequest(session, transaction, offset) +} + +/// `int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list)` +@c(sdp_service_attr_async) +public func sdp_service_attr_async( + _ session: UnsafeMutablePointer?, + _ handle: UInt32, + _ requestType: sdp_attrreq_type_t, + _ attributeIDs: UnsafePointer? +) -> Int32 { + guard let session, let transaction = sdpTransaction(session) else { return -1 } + guard let buffer = beginRequest(transaction) else { return -1 } + + pduHeader(buffer).pointee.pdu_id = UInt8(SDP_SVC_ATTR_REQ) + pduHeader(buffer).pointee.tid = sdp_gen_tid(session).bigEndian + + var offset = headerSize + storeUnaligned(handle.bigEndian, to: buffer, offset: offset) + offset += MemoryLayout.size + + storeUnaligned(sdpMaxAttrLength.bigEndian, to: buffer, offset: offset) + offset += MemoryLayout.size + + let seqLength = genAttrIDSequencePDU(buffer.advanced(by: offset), attributeIDs, attributeDataType(requestType)) + guard seqLength >= 0 else { return failRequest(transaction, EINVAL) } + offset += Int(seqLength) + + return finishRequest(session, transaction, offset) +} + +/// `int sdp_service_search_attr_async(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list)` +@c(sdp_service_search_attr_async) +public func sdp_service_search_attr_async( + _ session: UnsafeMutablePointer?, + _ search: UnsafePointer?, + _ requestType: sdp_attrreq_type_t, + _ attributeIDs: UnsafePointer? +) -> Int32 { + guard let session, let transaction = sdpTransaction(session) else { return -1 } + guard let buffer = beginRequest(transaction) else { return -1 } + + pduHeader(buffer).pointee.pdu_id = UInt8(SDP_SVC_SEARCH_ATTR_REQ) + pduHeader(buffer).pointee.tid = sdp_gen_tid(session).bigEndian + + var offset = headerSize + let searchSeqLength = genSearchSequencePDU(buffer.advanced(by: offset), search) + guard searchSeqLength >= 0 else { return failRequest(transaction, EINVAL) } + offset += Int(searchSeqLength) + + storeUnaligned(sdpMaxAttrLength.bigEndian, to: buffer, offset: offset) + offset += MemoryLayout.size + + let attrSeqLength = genAttrIDSequencePDU(buffer.advanced(by: offset), attributeIDs, attributeDataType(requestType)) + guard attrSeqLength >= 0 else { return failRequest(transaction, EINVAL) } + offset += Int(attrSeqLength) + + return finishRequest(session, transaction, offset) +} + +// MARK: - Response processing + +/// End the transaction: hand the caller's callback whatever response +/// there is and report "finished". +/// +/// Once any fragment has been accumulated, that buffer is what the +/// callback sees, whatever the immediate caller passed here — an error +/// arriving mid-transaction still delivers the bytes gathered so far. +private func notify( + _ transaction: UnsafeMutablePointer, + type: UInt8, + status: UInt16, + data: UnsafeMutableRawPointer, + size: Int +) -> Int32 { + var data = data + var size = size + let accumulated = transaction.pointee.responseBuffer + if accumulated.data_size != 0, let buffer = accumulated.data { + data = UnsafeMutableRawPointer(buffer) + size = Int(accumulated.data_size) + } + if let callback = transaction.pointee.callback { + callback(type, status, data.assumingMemoryBound(to: UInt8.self), size, transaction.pointee.userData) + } + return -1 +} + +/// Which bytes of this response carry payload, and how long the body +/// is excluding the continuation state. +private struct SDPResponseFragment { + /// Start of the bytes to append to the accumulated response. + var payload: UnsafeMutableRawPointer + /// How many of them there are. + var length: Int + /// The body length the two consistency checks are stated in terms + /// of: `length` plus whichever fixed fields precede it in this PDU. + var bodyLength: Int +} + +/// `int sdp_process(sdp_session_t *session)` +/// +/// Returns 0 when a continuation request has been sent and the caller +/// should wait for the socket to become readable again, and -1 when +/// the transaction is over — at which point the notify callback has +/// already run. See the note at the top of this file. +@c(sdp_process) +public func sdp_process(_ session: UnsafeMutablePointer?) -> Int32 { + guard let session, let transaction = sdpTransaction(session) else { return -1 } + guard let responseBuffer = bt_malloc0(Int(SDP_RSP_BUFFER_SIZE)) else { return -1 } + defer { free(responseBuffer) } + + let body = responseBuffer.advanced(by: headerSize) + + /// Every failure below ends the transaction the same way: record + /// why, then let the callback see it. + func fail(_ error: Int32, type: UInt8, status: UInt16 = sdpUnknownStatus) -> Int32 { + transaction.pointee.error = error + return notify(transaction, type: type, status: status, data: body, size: 0) + } + + let responseSize = sdpReadResponse(session, responseBuffer, Int(SDP_RSP_BUFFER_SIZE)) + guard responseSize >= 0 else { return fail(errno, type: 0) } + + // A reply that does not answer the request in flight, or whose + // header disagrees with how much actually arrived, is not worth + // parsing. + guard let requestBuffer = transaction.pointee.requestBuffer, + pduHeader(requestBuffer).pointee.tid == pduHeader(responseBuffer).pointee.tid + else { + return fail(EPROTO, type: 0) + } + let payloadLength = Int(UInt16(bigEndian: pduHeader(responseBuffer).pointee.plen)) + guard payloadLength + headerSize == responseSize else { return fail(EPROTO, type: 0) } + + let pduID = responseBuffer.load(as: UInt8.self) + let accumulated = Int(transaction.pointee.responseBuffer.data_size) + let fragment: SDPResponseFragment + + switch Int32(pduID) { + case SDP_ERROR_RSP: + // Carries a real status code, so it is reported as-is rather + // than through sdp_get_error. + let status = UInt16(bigEndian: responseBuffer.loadUnaligned(fromByteOffset: 5, as: UInt16.self)) + return notify(transaction, type: pduID, status: status, data: body, size: payloadLength) + + case SDP_SVC_SEARCH_RSP: + let total = UInt16(bigEndian: responseBuffer.loadUnaligned(fromByteOffset: 5, as: UInt16.self)) + // Kept in network order: it is added to the stored count below + // without being swapped. + let current = responseBuffer.loadUnaligned(fromByteOffset: 7, as: UInt16.self) + let currentCount = Int(UInt16(bigEndian: current)) + guard Int(total) >= currentCount else { return fail(EPROTO, type: pduID) } + + let handles = currentCount * MemoryLayout.size + if accumulated == 0 { + // First fragment: keep the two count fields, so what + // accumulates looks like one whole search response. + fragment = SDPResponseFragment(payload: body, length: handles + 4, bodyLength: handles + 4) + } else { + guard accumulated > 3, let stored = transaction.pointee.responseBuffer.data else { + return fail(EPROTO, type: pduID) + } + // Fold this fragment's record count into the count already + // stored in that header. The reference adds the two values + // without byte-swapping either — so a total that carries + // out of the low byte comes out wrong — but this is what a + // client reads back, so it is reproduced rather than + // corrected. + let countField = UnsafeMutableRawPointer(stored).advanced(by: 2) + let running = countField.loadUnaligned(as: UInt16.self) + storeUnaligned(running &+ current, to: countField, offset: 0) + + fragment = SDPResponseFragment( + payload: responseBuffer.advanced(by: headerSize + 4), + length: handles, + bodyLength: handles + 4 + ) + } + + case SDP_SVC_ATTR_RSP, SDP_SVC_SEARCH_ATTR_RSP: + let count = Int(UInt16(bigEndian: responseBuffer.loadUnaligned(fromByteOffset: 5, as: UInt16.self))) + // A first fragment has to carry an actual attribute list; a + // continuation may legitimately add nothing. + guard accumulated != 0 || count > 1 else { + return fail(EPROTO, type: pduID, status: UInt16(SDP_INVALID_PDU_SIZE)) + } + fragment = SDPResponseFragment( + payload: responseBuffer.advanced(by: headerSize + MemoryLayout.size), + length: count, + bodyLength: count + MemoryLayout.size + ) + + default: + return fail(EPROTO, type: pduID) + } + + // There has to be room after the body for a continuation state, + // and the two lengths have to agree with what arrived, before any + // of it is trusted enough to copy. + guard responseSize - 4 > fragment.bodyLength + 1 else { return fail(EPROTO, type: pduID) } + let continuationState = fragment.payload.advanced(by: fragment.length) + let continuationLength = Int(continuationState.load(as: UInt8.self)) + guard responseSize - 5 == fragment.bodyLength + continuationLength + 1 else { + return fail(EPROTO, type: pduID) + } + + // Append this fragment to the accumulated response. + let newSize = accumulated + fragment.length + guard let grown = realloc(transaction.pointee.responseBuffer.data, newSize) else { + return fail(ENOMEM, type: pduID) + } + transaction.pointee.responseBuffer.data = grown.assumingMemoryBound(to: UInt8.self) + transaction.pointee.responseBuffer.buf_size = UInt32(newSize) + memcpy(grown.advanced(by: accumulated), fragment.payload, fragment.length) + transaction.pointee.responseBuffer.data_size = UInt32(newSize) + + guard continuationLength > 0 else { + // No continuation state: that was the last fragment. + return notify(transaction, type: pduID, status: 0, data: body, size: 0) + } + + // Repeat the request with the server's continuation state + // appended. requestSize excludes the empty continuation byte the + // request function wrote, so each round overwrites the previous + // one in place and the request never grows. + pduHeader(requestBuffer).pointee.tid = sdp_gen_tid(session).bigEndian + + let requestSize = Int(transaction.pointee.requestSize) + let available = Int(SDP_REQ_BUFFER_SIZE) - requestSize + // Truncating an oversized continuation state produces a request + // the server will reject, but that is the reference's behaviour + // and is preferable to overrunning the buffer. + let stateLength = available <= continuationLength ? max(0, available - 1) : continuationLength + + storeUnaligned(UInt8(stateLength), to: requestBuffer, offset: requestSize) + memcpy(requestBuffer.advanced(by: requestSize + 1), continuationState.advanced(by: 1), stateLength) + + let total = requestSize + stateLength + 1 + pduHeader(requestBuffer).pointee.plen = UInt16(total - headerSize).bigEndian + + var sent = 0 + while sent < total { + let written = send(session.pointee.sock, requestBuffer.advanced(by: sent), total - sent, 0) + guard written >= 0 else { return fail(errno, type: pduID) } + sent += written + } + return 0 +} From b6997061b9722322c135e54bd9ec7b21df4feb3c Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 00:23:42 -0400 Subject: [PATCH 65/73] Mark the last SDP symbols as implemented Registers the nine record registration functions and the four asynchronous ones, which leaves nothing stubbed: 228 implemented, 218 of them exported. --- scripts/implemented.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/scripts/implemented.txt b/scripts/implemented.txt index baea802..0eb2b86 100644 --- a/scripts/implemented.txt +++ b/scripts/implemented.txt @@ -247,3 +247,22 @@ sdp_service_attr_req sdp_service_search_attr_req sdp_service_search_req sdp_set_notify + +# The service record registration functions (9), implemented in +# Sources/BluetoothLinuxABI/SDPRecordRegistration.swift. +sdp_device_record_register +sdp_device_record_register_binary +sdp_device_record_unregister +sdp_device_record_unregister_binary +sdp_device_record_update +sdp_device_record_update_binary +sdp_record_register +sdp_record_unregister +sdp_record_update + +# The asynchronous SDP requests and their response pump (4), +# implemented in Sources/BluetoothLinuxABI/SDPSessionAsync.swift. +sdp_process +sdp_service_attr_async +sdp_service_search_async +sdp_service_search_attr_async From 61537249cbea43b62f9c14c5b1372a512bff185d Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 00:23:42 -0400 Subject: [PATCH 66/73] Regenerate C ABI stub table Empty: every exported symbol now has a real implementation. --- Sources/CBluetoothLinuxABI/gen/cbt_stubs.c | 65 ---------------------- 1 file changed, 65 deletions(-) diff --git a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c index 1d703cb..bd02151 100644 --- a/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c +++ b/Sources/CBluetoothLinuxABI/gen/cbt_stubs.c @@ -30,68 +30,3 @@ static void cbt_unimplemented(const char *symbol) abort(); } -int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags) -{ - cbt_unimplemented("sdp_device_record_register"); -} - -int sdp_device_record_register_binary(sdp_session_t *session, bdaddr_t *device, uint8_t *data, uint32_t size, uint8_t flags, uint32_t *handle) -{ - cbt_unimplemented("sdp_device_record_register_binary"); -} - -int sdp_device_record_unregister(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec) -{ - cbt_unimplemented("sdp_device_record_unregister"); -} - -int sdp_device_record_unregister_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle) -{ - cbt_unimplemented("sdp_device_record_unregister_binary"); -} - -int sdp_device_record_update(sdp_session_t *session, bdaddr_t *device, const sdp_record_t *rec) -{ - cbt_unimplemented("sdp_device_record_update"); -} - -int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle, uint8_t *data, uint32_t size) -{ - cbt_unimplemented("sdp_device_record_update_binary"); -} - -int sdp_process(sdp_session_t *session) -{ - cbt_unimplemented("sdp_process"); -} - -int sdp_record_register(sdp_session_t *session, sdp_record_t *rec, uint8_t flags) -{ - cbt_unimplemented("sdp_record_register"); -} - -int sdp_record_unregister(sdp_session_t *session, sdp_record_t *rec) -{ - cbt_unimplemented("sdp_record_unregister"); -} - -int sdp_record_update(sdp_session_t *sess, const sdp_record_t *rec) -{ - cbt_unimplemented("sdp_record_update"); -} - -int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) -{ - cbt_unimplemented("sdp_service_attr_async"); -} - -int sdp_service_search_async(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num) -{ - cbt_unimplemented("sdp_service_search_async"); -} - -int sdp_service_search_attr_async(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) -{ - cbt_unimplemented("sdp_service_search_attr_async"); -} - From b4964bb9cee858f7d2ec105b65449917ecf9df47 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 00:51:12 -0400 Subject: [PATCH 67/73] Raise the CI toolchain floor and cover the C ABI The container was pinned to swift:6.0.2, which can no longer resolve the package at all: PureSwift/Bluetooth 8.1.0 declares swift-tools-version:6.2, and 6.0.2 rejects it with "incompatible tools version" before compiling anything. The build and test steps now run as a matrix over 6.2.3 and 6.3.3, debug and release. Adds a job that builds and tests with SWIFTPM_BLUETOOTH_CABI=1 and then runs the differential conformance drivers against the system libbluetooth.so.3. Nothing in CI compiled a line of the C ABI before this, so all 228 exported symbols were unguarded. That job is 6.3-only because the boundary is bound with `@c`, which 6.2 rejects. Coverage moves to its own job so it runs once rather than once per matrix cell, which also keeps the uploaded artifact names unambiguous. --- .github/workflows/swift.yml | 74 +++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 1346058..66c21a8 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -4,10 +4,39 @@ on: [push] jobs: - linux-swift: - name: Linux + # The toolchain floor is 6.2, not the 6.0.2 this used to pin: + # PureSwift/Bluetooth 8.1.0 declares swift-tools-version:6.2, so an + # older toolchain cannot parse the dependency's manifest and fails + # during resolution, before anything is compiled. + linux: + name: Linux (${{ matrix.container }}, ${{ matrix.config }}) + strategy: + # One failing cell shouldn't hide the others' results — which + # toolchain/configuration combinations fail is the whole point + # of running a matrix. + fail-fast: false + matrix: + container: ["swift:6.2.3", "swift:6.3.3"] + config: ["debug", "release"] runs-on: ubuntu-latest - container: swift:6.0.2-jammy + container: ${{ matrix.container }}-jammy + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Swift Version + run: swift --version + - name: Build + run: swift build -c ${{ matrix.config }} + - name: Test + run: swift test -c ${{ matrix.config }} + + # Coverage runs once rather than per matrix cell: the report is a + # property of the source, not of the toolchain, and one upload keeps + # the artifact names unambiguous. + coverage: + name: Coverage + runs-on: ubuntu-latest + container: swift:6.3.3-jammy permissions: contents: read code-quality: write # required by actions/upload-code-coverage @@ -16,10 +45,6 @@ jobs: uses: actions/checkout@v4 - name: Swift Version run: swift --version - - name: Build (Debug) - run: swift build -c debug - - name: Build (Release) - run: swift build -c release - name: Test (Debug) run: swift test --configuration debug --enable-code-coverage - name: Archive Build artifacts @@ -48,3 +73,38 @@ jobs: name: code-coverage path: .build/coverage/ if-no-files-found: error + + # The C ABI targets are excluded from the package unless + # SWIFTPM_BLUETOOTH_CABI=1, so none of the jobs above compile a line + # of them — this is the only SwiftPM coverage the drop-in library + # gets. + # + # 6.3 only, unlike the matrix above: the boundary functions are bound + # to the vendored declarations with `@c`, which 6.2 rejects as an + # unknown attribute. + c-abi: + name: C ABI (${{ matrix.config }}) + strategy: + fail-fast: false + matrix: + config: ["debug", "release"] + runs-on: ubuntu-latest + container: swift:6.3.3-jammy + env: + SWIFTPM_BLUETOOTH_CABI: "1" + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Swift Version + run: swift --version + - name: Build + run: swift build -c ${{ matrix.config }} + - name: Test + run: swift test -c ${{ matrix.config }} + # The differential drivers are C programs linked twice, once + # against the reference library and once against ours, so both a + # C compiler and the real libbluetooth.so.3 have to be present. + - name: Install conformance prerequisites + run: apt-get update && apt-get install -y gcc libbluetooth3 + - name: Differential conformance + run: Conformance/compare.sh "$(swift build -c ${{ matrix.config }} --show-bin-path)/libBluetoothLinuxABI.so" From 5b9213f3f7e20976c3fb209dbd9d4f19546bc927 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 00:51:12 -0400 Subject: [PATCH 68/73] Add a CMake workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SwiftPM builds the same sources but not the shipped artifact: soname, symbol versioning and the export list are only expressible in CMakeLists.txt, so nothing in CI was checking the thing that actually gets installed. This builds it, runs check-exports — the assertion that the library exports exactly the reference's symbol set, which is what would catch the drop-in premise breaking — and installs it. Needs a PureSwift/Bluetooth checkout, since the shared object links the non-socket half of the C ABI out of that package's own CMakeLists. It is pinned to the version Package.swift depends on so the two halves cannot drift apart here. noble rather than jammy: CMakeLists.txt requires CMake 3.26+ and jammy's apt carries only 3.22. --- .github/workflows/cmake.yml | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/cmake.yml diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000..ab38d46 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,53 @@ +name: CMake + +on: [push] + +jobs: + + # Verifies the CMake packaging path, which is what actually produces + # the installable libbluetooth.so.3: soname, symbol versioning and the + # export list are not expressible in Package.swift, so SwiftPM's build + # of the same sources proves nothing about the shipped artifact. + # + # This is also the only job that runs check-exports, the assertion that + # the library exports exactly the reference's symbol set — the one + # check that would catch the drop-in premise breaking. + cmake: + name: CMake + runs-on: ubuntu-latest + # noble, not jammy like the Swift workflow: CMakeLists.txt requires + # CMake 3.26+ and jammy's apt carries only 3.22. noble ships 3.28. + # 6.3 because the C ABI is bound with `@c`, which 6.2 rejects. + container: swift:6.3.3-noble + steps: + - name: Checkout + uses: actions/checkout@v4 + # The shared object links the non-socket half of the C ABI out of + # PureSwift/Bluetooth's own CMakeLists, so that has to be checked out + # too — pinned to the same version Package.swift depends on, so the + # two halves cannot drift apart in CI. + - name: Checkout PureSwift/Bluetooth + uses: actions/checkout@v4 + with: + repository: PureSwift/Bluetooth + ref: "8.1.0" + path: Bluetooth + - name: Swift Version + run: swift --version + - name: Install CMake and Ninja + run: apt-get update && apt-get install -y cmake ninja-build + - name: Configure + run: > + cmake -B build -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DBLUETOOTH_PACKAGE_PATH=${{ github.workspace }}/Bluetooth + - name: Build + run: cmake --build build + - name: Check exported symbols + run: cmake --build build --target check-exports + # Installing is cheap and catches a broken install rule, which would + # otherwise only surface when someone tries to package the library. + - name: Install + run: cmake --install build --prefix "${{ github.workspace }}/install" + - name: Show installed layout + run: find "${{ github.workspace }}/install" -type f -o -type l From 18a45a75ea573feca481df2f22d5910f29199c7e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 17:42:46 -0400 Subject: [PATCH 69/73] Fix the classic-address decoding expectation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit testClassicAddressDecoding expected a wire address type of 0 to decode back to nil, but 0 is not an "unspecified" sentinel: it is the BR/EDR address type, which is exactly why the encoder writes nil as 0. The round trip is asymmetric by construction — nil encodes to 0, and 0 decodes to .bredr — so .bredr is the correct expectation, and the test has been failing since it was added. --- Tests/BluetoothLinuxTests/L2CAPSocketAddressTests.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/BluetoothLinuxTests/L2CAPSocketAddressTests.swift b/Tests/BluetoothLinuxTests/L2CAPSocketAddressTests.swift index 8ac1a16..7a5de96 100644 --- a/Tests/BluetoothLinuxTests/L2CAPSocketAddressTests.swift +++ b/Tests/BluetoothLinuxTests/L2CAPSocketAddressTests.swift @@ -40,7 +40,13 @@ final class L2CAPSocketAddressTests: XCTestCase { bytes.l2_bdaddr = BluetoothAddress(rawValue: "00:1A:7D:DA:71:13")!.littleEndian let address = L2CAPSocketAddress(bytes) XCTAssertEqual(address.protocolServiceMultiplexer, .hidc) - XCTAssertEqual(address.addressType, nil) + // Decoding is deliberately not the inverse of encoding here. On the + // wire `l2_bdaddr_type` has no "unspecified" encoding: 0 *is* BR/EDR, + // which is why the encoder maps a nil address type onto it. Reading 0 + // back as `.bredr` is therefore the accurate answer, and the one the + // kernel means, even though it does not round-trip to the nil this + // address was built from. + XCTAssertEqual(address.addressType, .bredr) XCTAssertEqual(address.channel.rawValue, 0) XCTAssertEqual(address.address.rawValue, "00:1A:7D:DA:71:13") } From f97b0a1bb486ec7d739397eb5bed1c1019882283 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 18:15:53 -0400 Subject: [PATCH 70/73] Install python3 in the CMake workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The swift:6.3.3-noble container does not ship a python3, and configure fails at find_package(Python3 REQUIRED) — the stub table and version script are generated by scripts/*.py at build time. This never surfaced locally because the host interpreter satisfied the search. --- .github/workflows/cmake.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index ab38d46..872414e 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -34,8 +34,11 @@ jobs: path: Bluetooth - name: Swift Version run: swift --version - - name: Install CMake and Ninja - run: apt-get update && apt-get install -y cmake ninja-build + # python3 as well: the swift containers don't ship it, and the + # configure step runs the stub/symbol generators (scripts/*.py) + # through find_package(Python3), which is REQUIRED. + - name: Install CMake, Ninja and Python + run: apt-get update && apt-get install -y cmake ninja-build python3 - name: Configure run: > cmake -B build -G Ninja From 138965b42b39cd87885073477b998cbec15da4a7 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 18:15:53 -0400 Subject: [PATCH 71/73] Accept BlueZ version skew in the conformance diff The differential driver compares our output against whatever libbluetooth3 the distro ships, and our tables match current BlueZ (5.79, the Debian 13 reference this port is verified against). Jammy's 5.64 predates both bus type 11 (IPC) and HCI/LMP version 0x0d (5.4), and noble's 5.72 still lacks the former, so CI prints the fallback where we print the newer name. Record those eight lines in known-differences.txt, the file compare.sh already subtracts from the diff. Both sides of each pair are listed because the subtraction is direction-blind; on a reference that knows these values the outputs are identical and no entry applies. Verified against jammy's actual .so (extracted from ubuntu:22.04) via BT_REFERENCE_LIB + LD_LIBRARY_PATH: all 8 differing lines accepted, and against the local 5.79 the output remains byte-identical. --- Conformance/known-differences.txt | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Conformance/known-differences.txt diff --git a/Conformance/known-differences.txt b/Conformance/known-differences.txt new file mode 100644 index 0000000..5189654 --- /dev/null +++ b/Conformance/known-differences.txt @@ -0,0 +1,27 @@ +# Accepted differences between our output and the reference's. +# +# compare.sh strips the +/- prefixes from the diff, dedupes the lines, +# and subtracts this list, so each entry here accepts a line regardless +# of which side produced it. Comments and blank lines are ignored. +# +# The entries below are all BlueZ version skew: our tables match current +# BlueZ (5.79, the reference this port is verified against on Debian 13), +# while CI's reference is whatever libbluetooth3 the container's distro +# ships — jammy has 5.64, noble 5.72. Both sides of each pair are listed +# because the older reference prints the fallback where ours prints the +# newer name. On a reference that knows these values the outputs are +# identical and none of these entries apply. + +# Bus type 11 (HCI_IPC): added to lib/hci.c after 5.72, so both jammy +# (5.64) and noble (5.72) print the fallback. +bustostr(11) = "IPC" +bustostr(11) = "Unknown" +dtypetostr(11) = "IPC" +dtypetostr(11) = "Unknown" + +# HCI/LMP version 0x0d (Bluetooth 5.4): known to noble's 5.72 but not +# jammy's 5.64, which prints an empty string. +lmp_vertostr(0x0d) = "" +lmp_vertostr(0x0d) = "5.4" +vertostr(0x0d) = "" +vertostr(0x0d) = "5.4" From 6e79d2f5736be4f11e696268240f862d298c4e73 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 18:35:48 -0400 Subject: [PATCH 72/73] Include string.h in MathTestsHelper.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header calls memset without declaring it, so clang implicitly declares the builtin — `void *(void *, int, unsigned long)` — and that declaration becomes part of the CBluetoothLinuxTest module, where it shadows SwiftGlibc's annotated memset. Debug builds get away with it, but in release, cross-module optimization deserializes FoundationEssentials SIL that references SwiftGlibc.memset, resolution finds only the mismatched builtin, and swift-frontend aborts with a deserialization failure (signal 6) while compiling the test target. This is what has been failing every release-configuration CI cell. Verified: swift test -c release now builds and passes (60 tests, 0 failures) on 6.3.3, where it previously crashed the compiler. --- Sources/CBluetoothLinuxTest/include/MathTestsHelper.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/CBluetoothLinuxTest/include/MathTestsHelper.h b/Sources/CBluetoothLinuxTest/include/MathTestsHelper.h index b076d18..8c8cee4 100644 --- a/Sources/CBluetoothLinuxTest/include/MathTestsHelper.h +++ b/Sources/CBluetoothLinuxTest/include/MathTestsHelper.h @@ -7,6 +7,14 @@ // #import +// string.h for memset. Without it, clang implicitly declares the +// builtin as `void *(void *, int, unsigned long)` and that declaration +// becomes part of this module — where it shadows SwiftGlibc's annotated +// memset. Release builds then crash the compiler: cross-module +// optimization deserializes FoundationEssentials SIL that references +// SwiftGlibc.memset, resolution finds only the mismatched builtin, and +// the deserializer aborts (signal 6, ModuleFileSharedCore.cpp). +#import struct hci_filter { uint32_t type_mask; From 8376fa3cb8974b945bb3b0744e092d631a946e91 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 7 Aug 2026 19:00:39 -0400 Subject: [PATCH 73/73] Use GITHUB_WORKSPACE instead of the workspace expression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a container job, ${{ github.workspace }} expands to the host runner path (/home/runner/work/...), but the step executes inside the container, where the workspace is mounted at /__w/... — so configure looked for the Bluetooth checkout at a path that does not exist and failed. The GITHUB_WORKSPACE environment variable is set by the runner inside the container and resolves to the mounted path. --- .github/workflows/cmake.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 872414e..2bb314d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -39,11 +39,17 @@ jobs: # through find_package(Python3), which is REQUIRED. - name: Install CMake, Ninja and Python run: apt-get update && apt-get install -y cmake ninja-build python3 + # $GITHUB_WORKSPACE (the runtime variable), not ${{ github.workspace }} + # (the expression): in a container job the expression expands to the + # host path (/home/runner/work/...), which does not exist inside the + # container, where the workspace is mounted at /__w/... instead. The + # variable is set by the runner in the container's environment and + # resolves to the mounted path. - name: Configure run: > cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release - -DBLUETOOTH_PACKAGE_PATH=${{ github.workspace }}/Bluetooth + -DBLUETOOTH_PACKAGE_PATH="${GITHUB_WORKSPACE}/Bluetooth" - name: Build run: cmake --build build - name: Check exported symbols @@ -51,6 +57,6 @@ jobs: # Installing is cheap and catches a broken install rule, which would # otherwise only surface when someone tries to package the library. - name: Install - run: cmake --install build --prefix "${{ github.workspace }}/install" + run: cmake --install build --prefix "${GITHUB_WORKSPACE}/install" - name: Show installed layout - run: find "${{ github.workspace }}/install" -type f -o -type l + run: find "${GITHUB_WORKSPACE}/install" -type f -o -type l