Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ ReliaBLE is a Swift package that provides a reliable, modern, yet easy to use in
1. Reliability of Communication:

- FR-1.1: Implement error detection and correction mechanisms for each BLE transaction.
- FR-1.2: Ensure automatic reconnection attempts with exponential backoff for failed connections.
- FR-1.2: Ensure automatic reconnection attempts with exponential backoff for failed connections. On reconnection, services and characteristics must be re-discovered rather than reused from the prior connection (see FR-10.6).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-discover on reconnect is correct; incomplete for in-flight work.

Within GATT scope this FR-1.2 addition is right. Please also note (one clause is enough) that re-discovery is part of returning to ready, and that any future command-layer reconnect-and-rerun (FR-4/5; post-merge) depends on this ready transition — so we don't later treat "connected again" as sufficient to resume I/O.

No need to specify the full pause/retry model here.

- FR-1.3: Provide status updates on connection stability and data transmission integrity.
- FR-1.3.1: Provide status updates on connection stability (e.g. connected, disconnected, reconnecting).
- FR-1.3.2: Provide status updates on data transmission integrity.
Expand All @@ -49,7 +49,7 @@ ReliaBLE is a Swift package that provides a reliable, modern, yet easy to use in

4. Command Style Protocol for Interacting with Peripherals:

- FR-4.1: Define a flexible command protocol where the content and functionality of commands are provided by the integrating app.
- FR-4.1: Define a flexible command protocol where the content and functionality of commands are provided by the integrating app. Commands target services and characteristics made available through discovery (see FR-10), referenced by UUID (CBUUID).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good cross-ref — make the gate explicit.

"Commands target services/characteristics made available through discovery" still allows interpreting discovery as a passive catalog. Please strengthen to something like:

Commands may only execute against a peripheral that is discovery-ready for the targeted service/characteristic UUIDs (FR-10.3). Targeting an undiscovered or not-ready characteristic is an error (or must await readiness — pick one; prefer await-with-timeout for auto-discover, hard error if discovery already failed).

This is the minimal FR-4 tie-in needed so the later command-queue work inherits the gate instead of re-litigating it.


- FR-4.2: Support various command types:
- FR-4.2.1: Notify-only (peripherals notify with updates).
Expand Down Expand Up @@ -122,10 +122,54 @@ ReliaBLE is a Swift package that provides a reliable, modern, yet easy to use in
- Connection/disconnection events
- Command successes or failures
- ✅ Scanning start/stop
- Service/characteristic discovery events (discovery start/completion/failure, GATT table changes, subscription state changes)
- Security events (e.g., encryption initiation or failure)
- Data chunking operations


10. Service and Characteristic Discovery and Management:

The layer between establishing a connection (FR-3, FR-5) and reading or writing data
(FR-4, FR-7). After a peripheral connects, its GATT services and characteristics must be
discovered before any interaction, and that discovered view must be kept correct across
firmware changes and reconnections. This section covers discovery and characteristic setup
only; the movement of application data over characteristics is covered by the command
protocol (FR-4) and chunking (FR-7).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readiness as reliability invariant (missing).

This intro correctly positions discovery between connect and data movement, but it still describes discovery as a catalog/setup phase rather than a gate.

Please add an explicit invariant here (or as FR-10.3.x), roughly:

A peripheral is not eligible for command execution or characteristic I/O until discovery has reached a terminal ready state for the app-declared UUID set. connected (FR-1.3.1 / connection streams) is not sufficient.

This is the ObjC "discovery gates the command queue" property that the architecture comparison flagged as the first reliability primitive to land with GATT. Without it, FR-4/5 implementors will race writes against incomplete discovery.


- FR-10.1: Service Discovery:
- FR-10.1.1: Provide an API to discover services on a connected peripheral, requiring the integrating app to declare the specific service UUIDs of interest. Do not expose unfiltered "discover all services" as the default path — Apple explicitly discourages it because enumerating an entire remote GATT table negatively affects battery life and time-to-ready.
- FR-10.1.2: Support both automatic discovery on connection (using a caller-declared set of desired services/characteristics) and explicit, on-demand discovery invoked by the integrating app.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API shape for "caller-declared" services is underspecified.

FR-10.1.1–10.1.2 require a declared UUID set for auto-on-connect discovery, but not where that declaration lives. ReliaBLE's connection model is explicit connect(to:) (not demand-driven), so this needs a deliberate choice, e.g.:

  • per-connect options (desiredServices / desiredCharacteristics), and/or
  • a sticky per-peripheral registration on the manager, and/or
  • config defaults

Also specify: if auto-discovery is enabled and no UUID set was declared, is that a hard error (preferred — matches "no unfiltered default") or a no-op leave-not-ready?

Please nail this at requirement level so implementation doesn't invent three competing APIs.

- FR-10.1.3: Expose discovered services to the integrating app as Sendable value snapshots keyed by peripheral identifier and service UUID (CBUUID), mirroring the existing peripheral-snapshot pattern. Never surface live CoreBluetooth objects.

- FR-10.2: Characteristic and Descriptor Discovery:
- FR-10.2.1: Provide an API to discover characteristics for a discovered service, allowing the integrating app to declare the characteristic UUIDs of interest. Enforce CoreBluetooth's ordering constraint: services are discovered before characteristics, and characteristics before descriptors.
- FR-10.2.2: Track per-service discovery completion, correctly handling the separate completion callback fired for each service, and report a peripheral as "ready" only once all requested discoveries have completed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Ready" needs a hard consumer contract, not just a status bit.

"Report a peripheral as ready only once all requested discoveries have completed" is good, but incomplete without:

  1. Partial discovery policy — if a declared service/characteristic UUID is absent (narrow filter, wrong firmware, stale cache), is that discovery failure (not ready + error) or partial ready with a missing-UUID report? ObjC-style reliability prefers fail-closed for required UUIDs.
  2. Gate on consumers — FR-4 commands / any future read-write-notify API must await/park until ready (and fail fast if discovery failed), not merely observe a stream.
  3. Cross-ref — add an explicit FR-4.1 (or FR-5.2) note that enqueue/execute is gated on FR-10 readiness.

Without (2)–(3), "ready" is informational only and won't prevent the early-I/O races this section exists to stop.

- FR-10.2.3: Optionally discover descriptors for a characteristic on demand. Descriptor discovery is not required to enable notifications (see FR-10.4.1).
- FR-10.2.4: Expose discovered characteristics as Sendable value snapshots including metadata: UUID (CBUUID), properties (read, write, write-without-response, notify, indicate), and, when discovered, descriptors.

- FR-10.3: Discovery Lifecycle and Readiness:
- FR-10.3.1: Model discovery as an explicit, per-peripheral state machine driven entirely by delegate callbacks. Never assume synchronous availability of services or characteristics after initiating discovery.
- FR-10.3.2: Provide callbacks/streams (mirroring the asynchronous event pattern of FR-2.3) reporting discovery progress, completion, and errors.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discovery lifecycle vs existing ConnectionState streams.

ReliaBLE already ships connection lifecycle streams (connected / disconnected / reconnecting, etc.). FR-10.3.1–10.3.2 introduce another per-peripheral state machine + streams, but don't define the relationship.

Please specify one of:

  • A) Discovery states are a separate feed (e.g. discoveryStateChanges) and docs must stress connected ≠ ready, or
  • B) Connection lifecycle is extended with discovery phases (connecting → connected → discovering → ready), or
  • C) Hybrid: keep connection stream pure; readiness is a derived gate used internally + exposed as a simple isReady/awaitable.

Architecture eval preference: keep connection recovery observability (a ReliaBLE strength) distinct, and treat readiness as an explicit gate/stream so reconnect ladders don't get conflated with GATT catalog state. Either way, pick it in the PRD so implementers don't fork models.

- FR-10.3.3: Enforce a configurable discovery timeout so that a stalled or interrupted discovery surfaces as an error rather than an indefinite wait.
- FR-10.3.4: Handle interrupted or partial discovery: if a peripheral disconnects mid-discovery, treat it as a hard reset of the discovery state — discard partial results and any pending discovery bookkeeping — and re-run discovery on the next connection (see FR-10.6.1).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good hard-reset on mid-discovery disconnect — keep this.

One addition: on unexpected disconnect, also define what happens to any app-visible discovery snapshot (clear immediately vs mark stale until next ready). FR-10.6.2 says discard internal CB refs; the app-facing catalog invalidation timing should be equally explicit to match the existing peripheral-snapshot refresh philosophy.

- FR-10.3.5: Handle non-nil errors returned in each discovery callback, mapping CoreBluetooth errors into the library's error type. Distinguish common failure causes where feasible (e.g., characteristic-not-found from an overly narrow filter or a stale cache, versus disconnection during discovery).

- FR-10.4: Characteristic Subscription Management (Notify/Indicate):
- FR-10.4.1: Provide an API to enable and disable notifications or indications on a characteristic using the platform's subscribe mechanism. Never write the Client Characteristic Configuration Descriptor (CCCD, 0x2902) directly — CoreBluetooth manages the CCCD write internally and forbids writing it directly.
- FR-10.4.2: Confirm the subscription state-change callback before treating a subscription as active, and sequence subscription-enable before issuing any command that causes the peripheral to emit data, so that initial notifications are not lost. Delivery of the notified values is covered by FR-4.2.1 (notify-only) and FR-2.3.2 (data-received streams).
- FR-10.4.3: Expose subscription state and its changes to the integrating app. Do not rely solely on the platform's `isNotifying` flag, which can lag the actual state.
- FR-10.4.4: Account for the fact that notifications are unacknowledged at the GATT layer and may be lost or reordered above the link layer. Application-layer framing/sequencing for multi-packet notification streams is handled per FR-7 (chunking and reassembly).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FR-10.4 scope + reconnect re-subscribe gap.

Two issues:

  1. Scope boundary — PR summary says discovery/setup only; enable/disable notify is setup-adjacent and fine, but FR-10.4.4's deep dive into lossy notifications + FR-7 handoff is command/chunking territory. Consider trimming 10.4.4 to a one-liner forward-ref so this section stays discovery/setup-focused.

  2. Missing requirement (in-scope): After reconnect / restore / didModifyServices re-discovery, previously requested subscriptions must be re-armed (or explicitly reported as inactive for the app to re-request). Rediscovery alone does not restore notify/indicate intent. Notify-only apps (FR-4.2.1) break without this.

Also sequence re-subscribe before releasing the readiness gate for commands that depend on notifications (ties to FR-10.4.2).


- FR-10.5: GATT Cache Invalidation and Re-discovery:
- FR-10.5.1: Detect and surface peripheral GATT-table changes reported by the platform (the "did modify services" callback) and automatically re-run discovery for the affected services.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didModifyServices must re-gate work, not only re-discover.

ObjC re-suspended the peripheral queue on GATT table change, then rediscovered, then resumed. FR-10.5.1 only requires detecting the callback and re-running discovery.

Please require:

  • mark peripheral not ready immediately on didModifyServices
  • fail or park any in-flight discovery-dependent I/O
  • re-run discovery for affected (or all declared) services
  • return to ready only after completion
  • surface the event on the discovery/lifecycle stream

Otherwise commands can execute against a half-invalidated catalog mid-DFU/service-change — exactly the class of bug the readiness gate exists to prevent.

- FR-10.5.2: Treat reconnection following a peripheral firmware update or DFU as requiring full re-discovery; do not trust a previously cached view of services and characteristics.
- FR-10.5.3: Document the iOS OS-level GATT caching limitation: there is no public API to clear the cache. Reliable cross-connection refresh requires the peripheral to implement the Service Changed characteristic (0x2A05) within the Generic Attribute Service (0x1801) and to bond; otherwise the only fallback is user-driven (toggle Bluetooth, or "Forget This Device"). This is a peripheral-firmware responsibility that the library cannot fully control, and the limitation must be communicated to integrating apps.

- FR-10.6: Reconnection and Stale-Reference Handling:
- FR-10.6.1: Re-discover services and characteristics on every new connection. Never reuse a service or characteristic reference obtained from a prior connection (see FR-1.2 for reconnection).
- FR-10.6.2: Internally discard all live CoreBluetooth service/characteristic references on disconnect and reset the discovery state. The app-facing catalog must be keyed by UUID (CBUUID), not by held CoreBluetooth objects. This is consistent with the library's existing invalidation/refresh behavior on Bluetooth power cycling.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State restoration path is missing.

FR-10.6 covers "every new connection" and disconnect invalidation, but ReliaBLE already has CB state restoration (restoreIdentifier, willRestoreState). Documentation.docc/Topics/Background.md already notes that when GATT lands, restoration must re-wire peripheral GATT state.

Please add something like FR-10.6.3:

  • On central restore, re-associate restored CBPeripherals, discard any prior catalog, re-run discovery for peripherals the app still intends to use, and re-apply subscription intent (see FR-10.4 comment).
  • Restored link ≠ ready; same readiness gate as a fresh connect.

Also: "keyed by peripheral identifier" should explicitly mean ReliaBLE's Peripheral.id model (and note FR-8.5 still incomplete) so implementers don't key only on cbIdentifier inconsistently with the rest of the library.



### Non-Functional Requirements

1. Modern Swift 6 Architecture:
Expand Down
Loading