Skip to content

[P2] Oura State/DebugText malformed UTF-8 diverges across Swift and Kotlin #92

Closed
ryanbr/noop
#1478

Description

@bhelm

Finding

The public Oura decoders disagree on malformed text bytes:

  • decodeState/1: Swift uses strict UTF-8 decoding, retaining the state code but returning text=nil; Kotlin uses String(bytes, UTF_8), substitutes U+FFFD, and returns replacement text.
  • decodeDebugText/1: Swift returns nil for malformed UTF-8; Kotlin substitutes U+FFFD and emits a debug-text event.

Representative exact outputs:

  • State payload 08 63 68 67 2e 20 64 65 74 65 63 74 65 64 ff (code 8 + chg. detected + FF): Swift OuraState(code:8,text:nil) and no charger transition; Kotlin OuraState(code=8,text="chg. detected\uFFFD"), which matches the charger-start predicate.
  • Debug payload 41 ff 42: Swift nil / no event; Kotlin "A\uFFFDB" / DebugTextEvent.
  • Other representative malformed sequences follow the same split: c3 28 => Swift nil, Kotlin "\uFFFD("; truncated e2 82 => Swift nil, Kotlin "\uFFFD"; f0 28 8c 28 => Swift nil, Kotlin "\uFFFD(\uFFFD(".

Reachability and severity

These payloads are reachable from parsed raw BLE records: framing validates record shape/length but does not validate text encoding before OuraDriver dispatch. State events feed the live charger/wear tracker, so Kotlin can transiently change the charging badge where Swift does not. DebugText is emitted by the protocol driver but app ingest drops it; it remains visible to diagnostic consumers/CLI. No durable metric or scoring row is affected.

The raw protocol contract distinguishes valid from malformed data: State carries an optional trailing UTF-8 string; DebugText is specified as ASCII. Therefore conforming ring frames are unaffected. This is a P2 robustness/parity defect for corrupt or misaligned frames, not a valid-wire semantic disagreement.

Existing coverage and duplicate search

Current mirrored tests cover valid ASCII, short State, and valid/short binary decoder shapes, but no malformed State/DebugText bytes. Exhaustive title/body search in both bhelm/noop and ryanbr/noop found no existing malformed-UTF issue or PR. Related upstream PRs #88, #94, and ryanbr#143 are closed/unmerged debug logging/framing work; merged ryanbr#710 (FeatureStatus) and ryanbr#1109 (ASCII diagnostics docs) do not address this behavior.

Acceptance

  • Add mirrored public decoder tests with invalid UTF-8 sequences and valid ASCII/UTF-8 controls.
  • Choose and document one strict cross-platform malformed-input contract without changing valid text behavior.
  • Make Swift and Kotlin State/DebugText outputs and driver emission agree.
  • Verify charger/wear behavior cannot be triggered by replacement-decoded malformed bytes.
  • Preserve public/product callsite behavior for conforming frames.
  • Close only after the fix and regression tests are accepted upstream in ryanbr/noop; a fork-only fix is not acceptance.

No production fix is part of the RED-test handoff.

Activity

  1. bhelm commented on Aug 21, 2026

    @bhelm
    OwnerAuthor

    Resolved upstream by ryanbr/noop#1478, merge commit eb4fe181, which is contained in current upstream main. The merged fix includes regression coverage and passed upstream CI. Closing as completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions