Skip to content

suunto_nautic: stream /Data on the handle its GET returned, and release handles - #8

Merged
latishab merged 1 commit into
mainfrom
fix/nautic-stream-on-acked-handle
Oct 4, 2026
Merged

latishab merged 1 commit into
mainfrom
fix/nautic-stream-on-acked-handle

Conversation

@latishab

@latishab latishab commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Problem

Reported on deepsealabs/libdc-swift#29 (urbamax, Suunto Nautic). After downloading dive 1787752091 by id, a by-id download of 1788616918 on the same link came back with 1787752091's profile bytes. Only the listed-size check (#58) stopped it being saved as the wrong dive.

Root cause

A GET of /Logbook/byId/<id>/<resource> binds that id to one of the resource's handle slots: 00 24 <res>, or 10 24 <res> while the 00 slot is still bound. The slot stays bound until a 0x0B release. You can see this in the Suunto app capture (it alternates 00 24 12 / 10 24 12 and always releases the previous handle) and in urbamax's own extractor log (the second dive's /Data resolved to 10240e).

The driver subscribed (0x10) and unsubscribed (0x11) on a hard-coded 00 24 0E and released (0x0B) a hard-coded 00 24 12, with message ids derived from ACK bytes. The /Data slot was never released, so the first dive downloaded on a link kept 00 24 0E. A later dive's GET got 10 24 0E, but the driver still subscribed to 00 24 0E and streamed the first dive.

Fix

  • Subscribe and unsubscribe on the handle from the /Data GET's ACK, using the normal sequence counter for message ids.
  • Release every per-id handle once its transfer is done: /Data after the stream stops (or is refused), /Summary after the last page, and a short fetch after its frame. Static (F0) and unknown (FF FF FF) handles are skipped. The release is best-effort, and its 0x03 reply is matched by message id.
  • On a refused stream (423), the iostream timeout now goes back to 5 s too.

The first download on a fresh link is unchanged on the wire: the ACK handle is 00 24 0E there, same as the old hard-coded value.

Tests

The libdc-swift fake watch now binds per-id slots and only frees them on 0x0B. That reproduces the bug against the old driver: by-id A then B returns A's bytes, and foreach of 2+ dives gets the wrong profile for later dives. Both pass with this change. Covered in the libdc-swift PR that bumps this submodule.

🤖 Generated with Claude Code

…se handles

A GET of /Logbook/byId/<id>/<resource> binds the id to one of the
resource's handle slots (00 24 <res>, or 10 24 <res> while the 00 slot
is still bound) until a 0x0B release frees it. The driver subscribed to
a hard-coded 00 24 0E and only ever released a hard-coded 00 24 12, so
the /Data slot stayed bound to the first dive downloaded on a link: a
later dive's GET got the 10 slot, and the subscription streamed the
first dive's profile under the later dive's id. The listed-size check
caught it as an incomplete download.

Subscribe and unsubscribe on the handle from the GET's ACK with normal
message ids, and release every per-id handle once its transfer is done
(/Data after the stream stops, /Summary after the last page, a raw
short fetch after its frame), as the Suunto app does. Static (F0) and
unknown (FF) handles are left alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@latishab
latishab merged commit 9cbc430 into main Oct 4, 2026
18 checks passed
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