Skip to content
Merged
Changes from all commits
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
59 changes: 57 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, as part of returning to a discovery-*ready* state (FR-10.6, FR-10.3); a re-established link alone is not sufficient to resume characteristic I/O. Any future command-layer reconnect-and-rerun (FR-4/FR-5) depends on this ready transition rather than treating "connected again" as enough.
- 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 by UUID (CBUUID), and may only execute against a peripheral that is discovery-*ready* for the targeted UUIDs (FR-10.3): targeting an undiscovered or not-ready characteristic must await readiness (bounded by a timeout) while discovery is in progress, or fail fast when discovery has already failed. This gate is inherited from FR-10 so the command queue does not re-litigate 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,65 @@ 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, readiness transitions, 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.


**Discovery is a reliability gate, not merely a catalog.** A peripheral being connected
(FR-1.3.1) is not sufficient for interaction: it is not eligible for command execution or
characteristic I/O until discovery has reached a terminal *ready* state for the app-declared
UUID set. This "connected ≠ ready" invariant (defined in FR-10.3) is the discovery-layer
foundation that the command queue (FR-4, FR-5) and reconnect-and-rerun behavior are expected
to build on; those command-layer mechanics are out of scope for this section and referenced
only where the gate must be honored.

- 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 and explicit, on-demand discovery. The integrating app must declare the desired service/characteristic UUID set that discovery targets; this declaration is supplied at or around connection (e.g., via `connect` options and/or a sticky per-peripheral registration on the manager) and/or as configuration defaults. The exact API surface is finalized in implementation, but the declaration is mandatory: if automatic discovery is requested with no declared UUID set, that is a hard error (fail-closed), not a silent leave-not-ready — consistent with the "no unfiltered default" rule (FR-10.1.1).
- FR-10.1.3: Expose discovered services to the integrating app as Sendable value snapshots keyed by peripheral identifier (per the identity model in FR-10.6.2) 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 drive the peripheral to the *ready* state (FR-10.3) only once all requested discoveries have completed.
- 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: Expose discovery/readiness state through a feed distinct from the connection-state stream (FR-1.3.1 / `ConnectionState`), so that connection-recovery observability (a library strength) and GATT readiness are not conflated. Readiness must be observable and awaitable (e.g., a discovery/readiness state stream and/or an awaitable ready signal), reporting progress, completion, and errors. `connected` must never be presented to the app as `ready`.
- 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). On unexpected disconnect, the app-visible discovery snapshot must be invalidated (cleared, or marked stale until the next ready) with explicit, documented timing, matching the existing peripheral-snapshot refresh philosophy.
- FR-10.3.5: Partial-discovery policy is fail-closed. If a declared (required) service or characteristic UUID is absent — narrow filter, wrong firmware, or a stale cache — discovery is a failure: the peripheral does not become ready and the missing UUID(s) are reported. Map non-nil errors from each discovery callback into the library's error type, distinguishing common causes where feasible (e.g., missing-UUID versus disconnection during discovery).
- FR-10.3.6: Readiness is an enforced gate, not merely an observable signal. Characteristic I/O and command execution (FR-4, and any future read/write/notify API) against a peripheral must wait until it is ready for the targeted UUIDs: await readiness (bounded by the FR-10.3.3 timeout) while discovery is pending or in progress, and fail fast if discovery has already failed. The full command-queue behavior that consumes this gate is specified in FR-4/FR-5 (out of scope here); this requirement only fixes the gate contract those layers must honor.

- 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. Where a peripheral's readiness depends on active subscriptions, re-subscribe (FR-10.4.5) must complete before the readiness gate (FR-10.3.6) is released for notification-dependent work.
- 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: Notifications are unacknowledged at the GATT layer and may be lost or reordered above the link layer; application-layer framing/sequencing of multi-packet notification streams is out of scope for this section and handled per FR-7 (chunking and reassembly) and the command protocol (FR-4).
- FR-10.4.5: The library tracks requested subscription intent per characteristic. After reconnection, state restoration (FR-10.6.3), or `didModifyServices` re-discovery (FR-10.5.1), previously requested subscriptions must be re-armed — or explicitly reported as inactive for the app to re-request. Re-discovery alone does not restore notify/indicate intent, and notify-only integrations (FR-4.2.1) break without this.

- FR-10.5: GATT Cache Invalidation and Re-discovery:
- FR-10.5.1: On a peripheral GATT-table change (the `didModifyServices` callback), detection alone is insufficient. The library must immediately mark the peripheral *not ready* (re-gating work per FR-10.3.6), park or fail any in-flight discovery-dependent I/O, re-run discovery for the affected (or all declared) services, return to *ready* only after that completes, re-arm subscription intent (FR-10.4.5), and surface the event on the discovery/readiness stream.
- 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, State Restoration, 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. Re-discovery is part of returning to the *ready* state (FR-10.3.6): a re-established link is not sufficient to resume characteristic I/O until discovery completes (see FR-1.2).
- 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 the library's peripheral identity model (`Peripheral.id`, which resolves name → localName → `cbIdentifier`), not by held CoreBluetooth objects and not by `cbIdentifier` alone — so discovery keys stay consistent with the rest of the library. (Stable identity from manufacturer data, FR-8.5, is still open; this keying must adopt whatever FR-8.5 settles on.) This is consistent with the library's existing invalidation/refresh behavior on Bluetooth power cycling.
- FR-10.6.3: On CoreBluetooth central state restoration (`willRestoreState`), re-associate the restored peripherals, discard any prior discovery catalog, re-run discovery for peripherals the app still intends to use, and re-apply subscription intent (FR-10.4.5). A restored link is not ready: the same readiness gate (FR-10.3.6) as a fresh connection applies.


### Non-Functional Requirements

1. Modern Swift 6 Architecture:
Expand Down
Loading