Skip to content

suunto_nautic: skip foreign frames on a contended link instead of erroring - #2

Merged
latishab merged 1 commit into
mainfrom
fix/nautic-skip-foreign-frames
Sep 6, 2026
Merged

suunto_nautic: skip foreign frames on a contended link instead of erroring#2
latishab merged 1 commit into
mainfrom
fix/nautic-skip-foreign-frames

Conversation

@latishab

@latishab latishab commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Problem

The Nautic/Ocean BLE link is single-central, but another client can still hold a session on it. When a user leaves the official Suunto app running with a live logbook subscription, that subscription's Whiteboard 0x07 subscribe-result frames leak onto the shared link (on a different handle from our fetch).

Both fetch loops (suunto_nautic_device_paginated_fetch and suunto_nautic_short_fetch_frame) skipped non-DATA frames but capped skips at 8, then returned DC_STATUS_DATAFORMAT. A burst of that foreign traffic blows through the cap immediately, so the download aborts with a misleading data-format error and the user gets zero dives.

Confirmed mechanism (decompiled libmds.so): 0x07 routes through Whiteboard::handleClientResult with (type & 0xe) == 6, i.e. it is the Suunto app's subscription traffic, not a response to our request. Field-confirmed with multiple paying users on production; every case clears once the Suunto app is fully closed.

Change

  • Distinguish foreign-but-well-formed frames (0xA5 magic, but not our 0x05/handle) from genuinely malformed/truncated ones.
  • Skip foreign frames freely (bounded only by MAX_FOREIGN_SKIPS to guard against an unbounded loop); keep the tight MAX_MALFORMED_SKIPS = 8 cap for malformed frames → DATAFORMAT.
  • When our DATA never gets a turn on a saturated link, the dc_iostream_read times out first, so the failure now surfaces as a clean DC_STATUS_TIMEOUT instead of DATAFORMAT.
  • Factored the two frame checks into small suunto_nautic_frame_wellformed() / suunto_nautic_frame_is_our_data() helpers used by both loops.

What this does and does not do

  • It does not make a contended download succeed — the user still must close the app holding the subscription (that remains the real fix, and we ship an in-app hint for it).
  • It does turn a corrupt-data error into an honest "couldn't read the device" outcome, never truncates a good download on a brief foreign burst, and helps any consumer of the driver, not just apps with an app-side heuristic.

Downstream note

The terminal status for the pure-contention case changes DATAFORMATTIMEOUT. Any consumer keying a "close the Suunto app" hint off DATAFORMAT + no dives should broaden it to include TIMEOUT (tracked on our side as a Currents follow-up to DEE-45).

Testing

  • swift build clean; existing swift test suite green (25 tests, 1 skipped).
  • The fetch loops require a live device, so they're exercised via field testing rather than unit tests; the parser/tank-pressure suites are unaffected.

…oring

The BLE link is shared by one central at a time, but another client can still
hold a session: when the official Suunto app keeps a live logbook subscription,
its Whiteboard 0x07 subscribe-result frames leak onto the link on a different
handle. Both fetch loops capped skips at 8 and then returned DC_STATUS_DATAFORMAT,
so a burst of that foreign traffic aborted the download with a misleading
data-format error and the user got zero dives.

Distinguish foreign-but-well-formed frames (0xA5 magic, not our DATA/handle)
from genuinely malformed ones. Skip the former freely (bounded only against an
unbounded loop) and keep the tight cap for the latter. When our DATA never gets
a turn on a saturated link, the iostream read now times out first, so the
failure surfaces as a clean DC_STATUS_TIMEOUT rather than DATAFORMAT.

This does not make a contended download succeed -- the user still has to close
the app holding the subscription -- but it turns a corrupt-data error into an
honest "couldn't read the device" outcome and never truncates a good download on
a brief foreign burst.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016pyeZodmZ5cTDJysokDsCb
@latishab
latishab merged commit 3d7f6cb into main Sep 6, 2026
6 of 18 checks passed
@latishab
latishab deleted the fix/nautic-skip-foreign-frames branch September 6, 2026 10:34
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