Skip to content

Add C ABI - #229

Merged
colemancda merged 58 commits into
masterfrom
feature/c-abi
Aug 4, 2026
Merged

Add C ABI #229
colemancda merged 58 commits into
masterfrom
feature/c-abi

Conversation

@colemancda

@colemancda colemancda commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

Adds a Swift implementation of the non-socket half of BlueZ's libbluetooth C API: the 17 bluetooth.c entry points, the 10 bt_uuid_* functions, and the 75 pure sdp_* symbols — the SDP data model, PDU wire-format codec, and sdp_record_t attribute accessors. The socket/session half of SDP (sdp_connect, sdp_process, the register/update/unregister and async request families) belongs to BluetoothLinux, along with the rest of libbluetooth.

  • Sources/CBluetooth vendors bluetooth.h, uuid.h, sdp.h, sdp_lib.h (and hci.h, only because sdp_lib.h includes it) verbatim from BlueZ, under include/bluetooth/ — GPL-2.0-or-later, isolated in its own directory with its own LICENSE and README so the rest of the package's MIT licensing stays unambiguous — plus C implementations of the four functions that can't be exported from Swift (baprintf/bafprintf/basprintf/basnprintf are C variadics) and small sscanf trampolines for the UUID parser.
  • Sources/BluetoothABI and Sources/BluetoothSDP implement the rest in Swift, exported via @c so each signature is checked against the vendored header declaration rather than trusted as a string. sdp_data_t, sdp_list_t and sdp_record_t are transparent structs (every field is read directly by real consumers), so their layout is itself the ABI contract — these functions operate on the imported C types directly, the same pattern already used for bdaddr_t and bt_uuid_t.
  • Both targets are opt-in via SWIFTPM_BLUETOOTH_CABI=1, off by default — the ordinary build is unaffected.
  • CMakeLists.txt builds the installable artifact (libbluetooth-util.so.0), since soname/export-list/install-name aren't expressible in Package.swift. Scripts/check-exports.sh asserts the built library's exports (102 symbols) against cmake/symbols.txt. Closes Add CMake support #163.
  • Conformance/ differentially compares output against the real libbluetooth.so.3, byte for byte, across three drivers (address functions, bt_uuid_*, and the SDP family — the last two build a moderately complex service record, encode it, and decode it back). All three are identical to the reference except three documented bt_compidtostr metadata-freshness deltas and one documented sdp_uuid16_cmp memcmp-magnitude delta (the sign — the only part of memcmp's contract that's actually specified — matches).
  • bt_compidtostr's name table is generated ahead of time from the existing assigned-numbers metadata (Scripts/generate-company-names.py) into a static blob, rather than read at runtime — this avoids a Foundation/Bundle.module dependency in the ABI layer.

This work also caught and fixed two real bugs along the way, both confirmed via the differential conformance harness rather than by inspection:

  • bt_uuid_cmp/sdp_uuid_cmp-family functions computing an explicit byte difference instead of calling memcmp, after finding that -O optimizes small fixed-size memcmp calls to a normalized ±1 result while the reference's own build does not — silently optimization-level-dependent output for a function whose only real contract is its sign.
  • A genuine dangling-pointer bug in the SDP ABI layer: taking the address of a specific member of an imported C union through a pointer's .pointee (&d.pointee.val.uint16) is only valid for the duration of the call it's a direct argument to; extracting that address as a value to store in an array for a later, separate call (the dtds/values array-building pattern this ABI needs throughout) produced heap-layout-dependent corruption. Fixed by taking the address of the whole union instead (stable, since every C union member starts at the same offset) via small helpers in Pointers.swift.

Also in this PR

The BluetoothMetadata dependency (assigned-numbers name lookups on CompanyIdentifier, BluetoothUUID and UnitIdentifier) moves behind a new opt-in Metadata package trait, off by default. It was previously always linked, which pulled in Foundation and a resource bundle unconditionally; gating it keeps the base package dependency-free and buildable outside SwiftPM (which the CMake build needs). The generated numeric definitions (CompanyIdentifier.apple, etc.) are unaffected.

Test plan

  • swift build / swift test — default configuration (485 tests)
  • swift build --traits Metadata / swift test --traits Metadata — metadata enabled (485 tests)
  • SWIFTPM_BLUETOOTH_CABI=1 swift build / swift test — C ABI enabled (517 tests)
  • cmake -B build -G Ninja && cmake --build build && cmake --build build --target check-exports — 102/102 symbols match
  • Conformance/compare.sh against system libbluetooth.so.3 — identical output modulo the four documented deltas, on both the SwiftPM debug build and the CMake release build, repeated across multiple clean runs to confirm no remaining non-determinism

@colemancda colemancda changed the title Add a libbluetooth C ABI (phase 1: bluetooth.c + bt_uuid_*) Add C ABI Aug 1, 2026
@github-code-quality

github-code-quality Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: Swift

Swift / code-coverage/llvm-cov

The overall coverage in commit 1abc2ec in the feature/c-abi branch remains at 89%, unchanged from commit 9a6119b in the master branch.


Updated August 03, 2026 17:35 UTC

Rebasing feature/c-abi onto master surfaced a name collision:
feature/c-abi's own C-ABI-style sdp_* implementation was declared as
a target/product named BluetoothSDP, unaware that master had since
gained an unrelated pure-Swift BluetoothSDP target (merged via #230,
after the "no C ABI at all" decision for feature/sdp). Both ended up
declared in Package.swift and both wrote into Sources/BluetoothSDP/.

Folds the C-ABI sdp_* implementation into BluetoothABI instead of
giving it a separate module — CMake already links them into a single
shared object (bluetooth-util), so this also removes a redundant
static-library layer. The 8 source files move to Sources/BluetoothABI/
with an SDP prefix (avoiding a real filename collision on UUID.swift,
whose bt_uuid_* and sdp_* implementations are unrelated), and their
round-trip tests move into BluetoothABITests. Sources/BluetoothSDP/
now holds only master's pure-Swift implementation, untouched.

Updates CMakeLists.txt (drops the separate BluetoothSDP static
library), Conformance/compare.sh (sdp_* symbols now live in
libBluetoothABI.so, so the differential driver no longer needs a
second binary path), and CBluetooth's README.
CMakeLists.txt builds libbluetooth-util.so independently of SwiftPM's
own build graph (soname, symbol versioning, and install name aren't
expressible in Package.swift), so nothing else in CI currently
exercises it. Configures, builds, and checks the exported symbol
table against cmake/symbols.txt on both Swift versions the main Linux
job tests. Verified locally against the actual swift:X-jammy
containers before adding.
CMakeLists.txt requires CMake 3.26+, but jammy's apt only ships
3.22.1, so the job failed at the version check before configuring.
noble ships 3.28.3.
The C ABI layer is bound with `@c`, which Swift 6.2 rejects as an
unknown attribute, so that matrix cell could never succeed. The rest
of the package still builds on 6.2 — the C ABI targets are only added
when SWIFTPM_BLUETOOTH_CABI=1, which no other workflow sets.

Also stops one cell's failure from cancelling the others.
@colemancda
colemancda merged commit 034a7e3 into master Aug 4, 2026
48 checks passed
@colemancda
colemancda deleted the feature/c-abi branch August 4, 2026 01:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add CMake support

1 participant