Skip to content

Add indication-confirmation callback to PeripheralManager#48

Merged
colemancda merged 5 commits into
masterfrom
feature/indication-confirmation
Jul 21, 2026
Merged

Add indication-confirmation callback to PeripheralManager#48
colemancda merged 5 commits into
masterfrom
feature/indication-confirmation

Conversation

@colemancda

@colemancda colemancda commented Jul 21, 2026

Copy link
Copy Markdown
Member

Problem

write(_:forCharacteristic:) / write(_:forCharacteristic:for:) on PeripheralManager only document that they "optionally emit notifications if configured" — there is no way for a caller to learn that a central actually confirmed an indication. Protocols that need HAP-BLE-style confirmed delivery (indications, not notifications) currently have no signal for this.

What this adds

A new protocol requirement in Sources/GATT/PeripheralProtocol.swift:

var didConfirm: ((Central, UInt16) -> ())? { get set }

alongside the existing willRead / willWrite / didWrite callbacks.

Upstream dependency

PureSwift/Bluetooth is now required at 8.0.0, which adds a didConfirm hook to GATTServer.Callback (Sources/BluetoothGATT/GATTServer.swift) and forwards the ATT indication confirmation it already receives in didWriteAttribute's send(indication) { ... } completion handler, previously only logged and discarded.

With that in place:

  • GATTPeripheral (the generic, GATTServer-backed implementation used on Linux/embedded/etc.) now wires GATTServer.Callback.didConfirm straight through to its own didConfirm property — real, per-central, per-characteristic confirmation delivery, not a heuristic.
  • DarwinPeripheral (CoreBluetooth-backed) does not go through GATTServer, so it isn't affected by that change either way. CoreBluetooth itself has no confirmation-delivery delegate method — the only related signal is peripheralManagerIsReady(toUpdateSubscribers:), which fires when the shared transmit queue (used for all subscribers and both notifications and indications) has space again after updateValue(_:for:onSubscribedCentrals:) returned false. Since CoreBluetooth withholds further updates to a central while an indication to it is outstanding, write(_:forCharacteristic:for:) uses that as a best-effort heuristic: it targets the single specified central (previously it silently broadcast to all subscribers, ignoring the central parameter) and invokes didConfirm when that update had to wait on the queue before it could be sent. This remains a heuristic, not a verified per-characteristic confirmation, since the queue can also free up for unrelated reasons — documented as such on the property.

Changes

  • Sources/GATT/PeripheralProtocol.swift: add didConfirm protocol requirement.
  • Sources/GATT/GATTPeripheral.swift: add didConfirm storage/property, wired to the underlying GATTServer.Callback.didConfirm.
  • Sources/DarwinGATT/DarwinPeripheral.swift: implement best-effort didConfirm via peripheralManagerIsReady(toUpdateSubscribers:); fix write(_:forCharacteristic:for:) to actually target the specified central.
  • Package.swift: bump Bluetooth dependency to 8.0.0.

Test plan

  • swift build --traits BluetoothGATT succeeds
  • swift test --traits BluetoothGATT passes (14/14)
  • Exercise indication delivery against a real central to validate the Darwin heuristic in practice (not covered by this PR)

Adds a didConfirm(Central, UInt16) callback so callers can be notified
when a central acknowledges an indication, distinct from didWrite which
only covers writes initiated by the central. Backends document what, if
anything, actually triggers it, since not every transport can observe
ATT-level indication confirmations with central/characteristic granularity.
Bluetooth.GATTServer already receives ATT indication confirmations
internally (see send(_:response:) in GATTServer.swift) but only logs
them; its public Callback struct exposes willRead, willWrite, and
didWrite only, with no hook for confirmations. This property exists
solely to satisfy PeripheralManager conformance and is documented as
never invoked until GATTServer.Callback gains a corresponding hook
upstream.
CoreBluetooth does not report ATT indication confirmations directly;
the closest signal is peripheralManagerIsReady(toUpdateSubscribers:),
which fires once the shared transmit queue has space again after
updateValue(_:for:onSubscribedCentrals:) returns false. Since
CoreBluetooth withholds further updates to a central while an
indication to it is unconfirmed, use that as a heuristic: invoke
didConfirm from write(_:forCharacteristic:for:) when a targeted update
had to wait on the queue before succeeding. This also fixes that
method to target the specified central instead of silently
broadcasting to all subscribers.
@colemancda
colemancda force-pushed the feature/indication-confirmation branch from e1b22a1 to cf1acc7 Compare July 21, 2026 01:14
@github-code-quality

github-code-quality Bot commented Jul 21, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: Swift

Swift / code-coverage/llvm-cov

The overall coverage in commit c8f1847 in the feature/indication-c... branch is 76%. The coverage in commit ba4b36a in the master branch is 78%.

Show a code coverage summary of the most impacted files.
File master ba4b36a feature/indication-c... c8f1847 +/-
Sources/GATT/GA...onnection.swift 86% 75% -11%
Sources/GATT/GA...eripheral.swift 71% 66% -5%
Sources/GATT/Pe...lProtocol.swift 100% 100% 0%
Sources/GATT/GA...onnection.swift 90% 91% +1%
Sources/GATT/GATTCentral.swift 88% 90% +2%

Updated July 21, 2026 03:57 UTC

@colemancda
colemancda merged commit 6d3db71 into master Jul 21, 2026
39 checks passed
@colemancda
colemancda deleted the feature/indication-confirmation branch July 21, 2026 04:04
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.

1 participant