Attribute reassembled sync replies to the link's bound peer - #1705
Open
heyaim wants to merge 1 commit into
Open
Conversation
A sync reply that arrives in one BLE frame is validated against the peer bound to the link it came in on (BLEIngressLinkRegistry, then RequestSyncManager's check that we asked that peer). A reply too large for one frame took a different path: each fragment was judged against the bound peer and passed, but the dispatch handed BLEFragmentHandler only the fragment's claimed sender, and the handler validated the reassembled packet against its inner sender, the original author in both cases. With no open request to the author, the reply was dropped. Requests go only to direct neighbors, on the 30-second maintenance tick, one second after a neighbor connects, and on stalled-fragment resyncs. The loss was every multi-frame reply older than the two-minute skew window served for an author two or more hops away, among them long public and group messages, file transfers and board posts. The dispatch now carries the peer bound to the link a frame arrived on through to BLEFragmentHandler, which judges a flagged reassembled packet against that bound peer, or against the packet's own sender when the link is unbound or the packet was re-injected: the rule the registry applies to a single flagged frame. The fragments' claimed sender is never the anchor. Unflagged packets, self-fragment suppression and re-injection are unchanged; re-injection still hands the packet on as the fragments' claimed sender's, the author for a planner-built train, and refreshes that peer's last-seen time. Tests: the handler with planner-built fragments and a real RequestSyncManager, a 256-run comparison against the direct path, and two SimulatedMesh runs through the production ingress path, one delivering, one dropping. Full iOS simulator suite 2237/2237, SwiftPM suite green, swiftlint --strict clean.
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.
Gossip sync exists to carry the public history a peer missed. A message too long for one BLE write, at most 512 bytes, from an author two or more hops away did not arrive that way once it was more than two minutes old. A reply that fits in one write from the same author over the same link arrives.
A sync reply that arrives in one BLE frame is validated against the peer bound to the link it came in on:
BLEIngressLinkRegistryattributes a flagged frame to the bound peer, andRequestSyncManagerchecks that we asked that peer for sync. That is the ruledocs/REQUEST_SYNC_MANAGER.mdstates: a flagged reply is exempt from the timestamp check only when it answers a pending request sent to that specific peer. A reply too large for one frame took a different path: each fragment was judged against the bound peer and passed, but the dispatch handedBLEFragmentHandleronly the fragment's claimed sender, and the handler validated the reassembled packet against its inner sender, the original author in both cases. With no open request to the author, the reply was dropped. A flagged packet never reaches the skew check. The reply's age made no difference.Requests go only to direct neighbors: on the 30-second maintenance tick, one second after a neighbor connects, and on stalled-fragment resyncs, each against a 30-second window. Two-device testing therefore hides the drop: the author is always a neighbor. The fragment round narrows it further: re-served author fragments reassemble into a packet judged by its own timestamp, which still delivers trains under two minutes old. The requester never asks an author two or more hops away. Every multi-frame reply older than two minutes from such an author was lost, among them long public and group messages, file transfers and board posts.
Changes
attributeAndHandlePacketcarries it throughhandleReceivedPackettohandleFragment, andBLEFragmentHandler.handletakes it as an optionalboundTo:parameter after the claimed sender: nil for an unbound link or a re-injected packet.BLEIngressLinkRegistryapplies to a single flagged frame. The fragments' claimed sender is never the anchor: the signature and the direct path both refer to the packet's own sender, and anyone can name a peer we asked. An unflagged packet is still judged against its author._test_registerSyncRequest(to:)records the pending request aREQUEST_SYNCwould record, which lets a test present a solicited reply without the timer-driven send.Tests
BLEFragmentHandlerTests: planner-built fragments of a stored broadcast message marked as a sync response, with the assembly buffer,BLEIngressPacketGuardand aRequestSyncManager. The accepted reply passes the ingress guard and reaches the handler with the samefromandboundTothe dispatch passes, with a request to the serving peer only. The two rejection cases feed the handler directly: the same reply with no request registered is dropped, and an unflagged train from the same author is still judged on the author.reassemblyFollowsTheDirectPathsAcceptanceRuletreats the direct path as the specification: across 256 runs covering 192 distinct combinations of flag placement, request state, age, author, link binding (bound or none) and the sender the fragments claim (the author or another peer), reassembly accepts a packet only when the same packet would pass as a single frame on the same link, and a fully admitted train is accepted exactly when that frame would be.SimulatedMeshTests: the production ingress path end to end, in a line of three peers where the author is two hops from the requester, known from a relayed announce and never asked. Her signed reply arrives as fragments on the serving peer's bound link, with a request held to that peer, and is delivered. An equivalent train on a never-announced link, with fragments naming the serving peer, is judged by its own sender and dropped. The tests split the reply with the planner, since the simulated mesh has no physical links.Validation
swift test --parallel --skip PerformanceBaselineTests(the CI app job's test selection, run without coverage): 2024 Swift Testing tests, exit 0.-parallel-testing-enabled NO, iPhone 17 Pro simulator: 2237/2237, 0 failed, 0 skipped.swiftlint lint --strict: no violations.What this does not change