Repository navigation
fix: close six reachable contradictions in implicit state machines - #697
Merged
Merged
Conversation
delete_session removed the group and its establishment stamp but left the Welcome that encrypt_for_user parks for the peer. get_pending_welcome then handed out an invitation to a group this device no longer has. The Welcome is cleared after the session goes, best-effort, so an Err still means the session is there.
on_chunk_ack inserted any index into acked, so a stray or out-of-range index could fill the set to chunks.len() and is_fully_acked would report a transfer complete with real chunks unacknowledged. The engine completes transfers from its own delivered set today, so this is latent, not live.
…ransport The manager's copy is the carrier that last carried a frame; the selector's is the DORS hysteresis anchor, whose setter restarts the switch cooldown. Merging them would let every direct neighbour send reset the cooldown.
…livered The wrapper had two copies of event delivery. The core-event path clones the callback out of its slot and catches a throw; emit_event, which carries NeighborDiscovered, NeighborLost, PeerRssi and the carrier status events, called the callback under the slot's read guard with no catch_unwind. A callback that called set_event_callback from inside it deadlocked, and a throwing one unwound into the wrapper and the polling queue lost the event. Both paths now go through deliver_event.
… no link ble_link_up added the address to linked and consumed its verification before looking up the BLE transport, which does not exist until initialize. The refusal left the address linked: fragments from it were accepted, and the next link-up read as a relink and never announced the peer. The transport is now resolved before either set changes.
… down start() told the core BLE was available at once and again a second later, whatever the manager's state. With Bluetooth off, unauthorized or unsupported the manager reports false, and the one-second backup then reported true over it, so the core routed sends to a radio that was off until its next state change. Both reports are now skipped while the manager is unavailable. The foreground edge likewise restated the Wi-Fi Direct layer up on every foreground, for a transport that was disabled, stopped or had failed to start. It now does so only while the peer-stream transport is running.
…thing was created start() called protocol?.start() and then startForegroundService() even with no protocol, so "Mesh Active" went up and a host was registered over no engine; a sticky restart then read that as KEEP_ALIVE. It now rejects with "Protocol not initialized", as the module's other methods do, before the event buffers or the service are touched. The JavaScript wrapper never reaches this; a direct caller of the native module could.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Fixes found by an audit of the SDK's implicit state machines: lifecycles encoded in flags,
Optionfields and map membership rather than one enum. Each fix closes a combination of fields that the code could reach and should not.Nine candidates went in. Six are fixed here, and three turned out to be intended behaviour once read in full; those are listed at the end so nobody re-reports them.
Fixed
start()reported Bluetooth available at once and again after 1 s, whatever the radio's state. With Bluetooth off, unauthorized or unsupported, the manager reportsfalse, and the backup then reportedtrueover it, so the engine routed sends to a dead radio until its next state change.BleManager.state == .unavailable. Coming to the foreground also restated the Wi-Fi Direct layer up for a transport that was disabled, stopped or had failed to start; it now does so only while the manager is.running.NeighborDiscovered,NeighborLost,PeerRssi, carrier status) called the callback under the read guard with nocatch_unwind: a callback callingset_event_callbackdeadlocked, and a throwing one skipped the polling queue.deliver_eventused by both paths.delete_sessionleft thePendingWelcomethatencrypt_for_userparks, somls_get_pending_welcomereturned an invitation to a deleted group.Errstill means the session exists.ble_link_upadded the address tolinkedand consumed its verification before looking up the BLE transport, which does not exist untilinitialize. The refused call left the address linked: its fragments were accepted, and the next link-up read as a relink and never announced the peer.start()ranprotocol?.start()thenstartForegroundService()even with no protocol, putting "Mesh Active" up over no engine; a sticky restart then read that asKEEP_ALIVE. The JS wrapper never does this; a direct native-module caller could.OutboundTransferState::on_chunk_ackcounted any index, sois_fully_ackedcould go true early. Latent: the engine completes transfers from its own delivered set.Investigated and left as is
update_keysis not retried.check_epoch_forksdocuments this as intended: the 5-minute stale sweep allows re-detection, and escalation is the application's.engineset afterstop().OfflineNode.startis a static constructor that creates a fresh worker and sends onestart, andstop()terminates the worker, so neither a restart nor a concurrent start reaches it through the page API.Verification
cargo clippy --workspace -- -D warnings,cargo fmt --all -- --checkandRUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-depsare clean.cargo test --workspace: 3,752 passed. The one local failure,no_shipped_file_names_a_relay, flags only files in a gitignored.claude/worktreescheckout that CI does not have.OfflineProtocolModule.swifttypechecks with React using the CI step's recipe; a probe calling a missing method on the module was caught, so the harness is not vacuous.start()itself, because the module class needs a React context.cargo test -p offline-protocol-web.