Key reassembly buffers by characteristic, validate sequence, drop truncated data#88
Open
kmatzen wants to merge 1 commit into
Open
Key reassembly buffers by characteristic, validate sequence, drop truncated data#88kmatzen wants to merge 1 commit into
kmatzen wants to merge 1 commit into
Conversation
…ncated data Three defects in BLEPacketReconstructor, all of which corrupt camera state silently rather than failing visibly. Continuations were routed to whichever buffer for that peripheral was touched most recently, because nothing in the packet correlates it to a message. That is not a tuning problem: buffers were keyed by (peripheral, queryID), which invents concurrency the protocol does not have while ignoring the concurrency it does. Query responses (0x0077) and settings responses (0x0075) are separate notify characteristics that stream independently, and both fed one buffer set, so a continuation could be appended to an unrelated message. Buffers are now keyed by (peripheral, characteristic), which matches the spec's per-characteristic accumulation and makes routing deterministic. The 4-bit continuation sequence counter documented in BLE_PROTOCOL.md was never parsed, so a dropped or duplicated packet produced a byte-shifted payload that still satisfied the length check and decoded as plausible-but-wrong TLV. It is now validated, and a gap discards the message in progress. checkTimeouts force-completed truncated buffers and dropped the peripheral half of the buffer key on the way out, so BLEManager applied one camera's partial data to every connected camera — which then fed hasSettingsMismatch and could trigger a settings rewrite of the whole group. Truncated buffers are now discarded, so nothing is delivered on timeout and the broadcast loop is gone. Also: clearBuffers(for:) matched peripherals by bare prefix, so clearing "p1" would also clear "p10"; a start packet arriving mid-message now discards the superseded one loudly instead of silently overwriting it; and a negative declared length is rejected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AqfipPBp3eErDgZboooqxP
kmatzen
force-pushed
the
fix/packet-reassembly
branch
from
July 19, 2026 04:45
372aafd to
2f8cee9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of four stacked PRs. Base is
fix/correctness-and-test-gaps(#87), notmain.Three defects in
BLEPacketReconstructor. All of them corrupt camera state silently rather than failing visibly, which is why none showed up as a bug report.Continuations routed by recency
Continuations went to whichever buffer for that peripheral was touched most recently, because nothing in the packet correlates it to a message.
The root cause is the buffer key.
(peripheral, queryID)invents concurrency the protocol does not have, while ignoring the concurrency it does: query responses (0x0077) and settings responses (0x0075) are separate notify characteristics that stream independently, and both fed one buffer set. Buffers are now keyed by(peripheral, characteristic), matching the spec's per-characteristic accumulation, which makes routing deterministic.Reproduced against the pre-fix code with the real ordering — start query
0x13, start settings0x12, then0x13's continuation arrives:Sequence counter never validated
The 4-bit counter documented in
BLE_PROTOCOL.md:29was parsed by nobody. A dropped or duplicated continuation produced a byte-shifted payload that still satisfied the length check and decoded as plausible-but-wrong TLV — wrong resolution, FPS, battery. It is now validated, and a gap discards the message in progress.Worth being explicit: sequence validation alone would not have fixed the routing bug. Two messages interleaved at the same fragment position carry the same counter, so the check cannot tell them apart. Re-keying does that work; the counter catches drops and duplicates.
Truncated buffers broadcast to every camera
checkTimeoutsreturned(data, queryID)and discarded the peripheral half of the key, soBLEManagerapplied one camera's partial buffer to every connected camera — which then fedhasSettingsMismatchand could trigger a settings rewrite of the whole group. A dropout on one camera could rewrite the fleet.Truncated buffers are now discarded. Nothing is delivered on timeout, so the broadcast loop is gone entirely.
Smaller fixes
clearBuffers(for:)matched peripherals by bare prefix, so clearingp1also clearedp10.Testing
128 tests pass, including five new ones covering sequence gaps, duplicates, cross-characteristic isolation, superseded messages, and timeout discard. Mutation-verified — reverting the key to peripheral-only fails the isolation test; disabling sequence validation fails the gap and duplicate tests.
Review notes
The design was checked with a TLA+ model (
specs/PacketReassembly.tla, added in #90) before the code was written. Behaviour on real hardware is not verified — in particular, the counter starts at 0 for the first continuation, which matches this repo's existing tests and the Open GoPro convention, but is worth a sanity check against a camera.🤖 Generated with Claude Code
https://claude.ai/code/session_01AqfipPBp3eErDgZboooqxP