Repository navigation
plan 0019: PIN-gated remote transfer from the main menu - #168
Open
TheAngryRaven wants to merge 7 commits into
Open
TheAngryRaven wants to merge 7 commits into
TheAngryRaven wants to merge 7 commits into
Conversation
Design record for starting a Bluetooth transfer from the app while the logger idles on its main menu, gated by a challenge-response on the existing bluetooth_pin, with the camera always taking priority and an on-device PIN view for soldered-SD units. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTPVvtL3ZVDj7UBcAthaQk
The PIN handshake has to agree byte-for-byte with WebCrypto in the viewer and the hmac/sha2 crates in LapWing, so the primitive is pinned to FIPS 180-4 and RFC 4231 vectors, and the answer to a golden vector shared with both apps. The nonce, lockout, command gating, protected-key and camera priority rules live in remote_auth so the glue only owns the radio. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTPVvtL3ZVDj7UBcAthaQk
The logger now advertises the transfer service while it idles on the main menu, locked until the app answers a one-time challenge with an HMAC of the PIN. The PIN no longer leaks through SLIST/SGET, a local start hands it to the app with PINGET, and new PINs come from the hardware RNG. The camera always wins the radio: standby stops (and a remote session ends) before a paired camera's wake debounce. Transfer gains a PIN page that reveals or replaces the PIN behind a 3 s hold, since soldered SD cards took away the old recovery path. The answer is not bound to bluetooth_name as the design artifact had it: the nonce already makes it unique, and a name truncated on air would make correct implementations disagree. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTPVvtL3ZVDj7UBcAthaQk
Coverage — host-testable units📂 Overall coverage
📄 File coverage
|
Open
8 tasks done
The remote-transfer change rewrote settings.ino with LF endings, which turned a ~60-line change into a ~1160-line diff. BETA carries this file as CRLF; put it back so the PR shows only the real edits. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTPVvtL3ZVDj7UBcAthaQk
Review fixes for PR #168 (logger side): - A standby peer's disconnect could be routed to the camera when the camera claimed the radio in the same loop iteration, leaving bleConnected and a stale handle behind. Our own link is now matched by handle before owner, bleStandbyStop() waits (bounded, WDT-fed) for the disconnect, and standby starts from a clean link state. - bleStandbyStop() releases the owner before switching to open mode, so a write that preempts the teardown is ignored; a link being dropped is marked and gets no further commands. - A remote session ends with AUTH:ENGINE when the engine passes 500 rpm (camera paired or not) and with AUTH:IDLE after 10 minutes without a request, so it can no longer park the logger with no logging, auto-race or idle shutdown. Decision in remote_auth. - An unreadable or invalid stored PIN answers AUTH:BUSY without spending the nonce or counting a failure; PINGET refuses to hand out a PIN that could never authenticate. - BLE_SETUP() drops any surviving link and clears every pending flag before opening a local (open) session, and the transfer page says "PIN sent to app!" once PINGET has been served. - The paired-camera engine claim is latched until rpm < 300, so a pull-start no longer thrashes the standby advert. - After an AUTH:TIMEOUT squat drop the advert stays down 5 s. - Tests: the above, plus command gating with leading whitespace and embedded NULs, and the PIN page's entry press on the Back row. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTPVvtL3ZVDj7UBcAthaQk
Review fixes for PR #168 (logger side): - ensureDefaultSettings() regenerates a stored PIN that the handshake could never accept (wrong length, non-digits, or a JSON number that getSetting() can't read), instead of leaving remote transfer locked out for good. - Transfer -> PIN reads the PIN wider than four digits and shows "PIN read failed" / "PIN invalid: New PIN" rather than a blank page or a value cut down to four digits. The page also lands on Show rather than inheriting the Transfer menu's row index. - The PIN no longer reaches the debug serial: createDefaultSettings() drops it from its log line and setSettingInner() hides the value of protected keys. - SRESET keeps a valid bluetooth_pin. A reset sent from a remote session used to roll a PIN the app could never learn; replacing the PIN stays on the device's PIN page. - settings.h: remote_transfer costs 23 bytes, not 26. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTPVvtL3ZVDj7UBcAthaQk
- Plan 0019: AUTH:ENGINE / AUTH:IDLE drop notices, AUTH:BUSY for an unreadable PIN, the squat back-off, synchronous standby handover, SRESET keeping the PIN, and the accepted local-start PINGET gap in the threat model. - CLAUDE.md / ARCHITECTURE.md: BLE is no longer lazy on stock builds (the menu standby starts it), plus the new rules and constants. - CHANGELOG: the PIN does cross the air on one path, PINGET on a local start; the remote-session end, PIN-sent notice, PIN regeneration and SRESET behaviour. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTPVvtL3ZVDj7UBcAthaQk
This branch has not been deployed
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.
Summary
Before: to start a Bluetooth transfer, someone had to stand at the logger and pick Transfer → Bluetooth. The
bluetooth_pinexisted, but nothing checked it, andSLIST/SGETsent it to anyone who was connected.After: while the logger idles on its main menu, it advertises the transfer service in a locked mode. The app answers a one-time 16-byte challenge with
HMAC-SHA256(PIN, "BEAUTH1|" + nonce), and a correct answer turns the link into the normal transfer page, labelled Remote Transfer. A local start stays open, as it was before. A local start is now also the only way to read the PIN over BLE, usingPINGET.How it works:
AUTH?getsAUTH:NONCE:<hex32>back, with the nonce taken from the hardware RNG.AUTH:<hex32>gets one ofAUTH:OK,AUTH:FAIL:<n>,AUTH:LOCKED:<s>,AUTH:NO_NONCEorAUTH:BUSY.AUTH:OK, any other command getsAUTH:REQUIRED, exceptBATT.AUTH:TIMEOUTand is dropped.CAMERA_LOOP(), so the camera never finds the radio taken.AUTH:CAMERAand then reboots.CAMERA_FORCE_RELEASE().SLISTleaves outbluetooth_pin.SGET:bluetooth_pinreturnsSERR:PROTECTED.SSET:bluetooth_pinaccepts exactly 4 digits.remote_transfer, defaulton.The rules are in host-tested pure units:
sha256(FIPS 180-4 / RFC 4231 vectors)remote_auth, with a golden answer vector shared with the viewer and LapWingpin_pageDesign record:
docs/plans/0019-remote-transfer-pin.md. It includes one change from the reviewed design: the answer is not bound tobluetooth_name. That name can be truncated on air, which would make correct implementations disagree, and the nonce already makes each answer unique.Security limit: the link is still unencrypted Just Works. The PIN keeps other people in the pits out, but someone who records a handshake can brute-force it offline. The plan states this.
Type of change
This is additive to the BLE protocol. Old apps keep working with a local start, but they no longer see the PIN in
SLIST.How it was verified
ctest --test-dir tests/build)clang-tidyclean, run on the three new units.inochanges.Checklist
CHANGELOG.mdupdated under[Unreleased](if user-visible)ARCHITECTURE.md/CLAUDE.mdupdated (if a module or interface changed)tests/Related issues
Viewer and LapWing handshake PRs follow.
🤖 Generated with Claude Code
https://claude.ai/code/session_01BTPVvtL3ZVDj7UBcAthaQk
Generated by Claude Code