Skip to content

Close the review's protocol-parity and input-correctness findings - #28

Merged
whoisaldo merged 1 commit into
mainfrom
fix/protocol-parity-and-input
Aug 27, 2026
Merged

Close the review's protocol-parity and input-correctness findings#28
whoisaldo merged 1 commit into
mainfrom
fix/protocol-parity-and-input

Conversation

@whoisaldo

Copy link
Copy Markdown
Owner

The last batch from the /code-review pass.

The Rust reassembler had drifted from its own specification

proto/src/reassembly.rs is a hand-written mirror of FrameAssembler.swift and says so in its header — "if behavior here diverges, the Swift side is the specification". It had diverged three ways, and since the E2E fake receiver uses the Rust copy, the tests were validating a receiver nobody ships:

  • Count mismatch wiped the frame. A fragment claiming a different count rebuilt the pending frame from scratch; Swift keeps the first-seen count and ignores the odd fragment. One late duplicate could discard nine tenths of a buffered keyframe.
  • Replays overwrote good bytes and counted as another receipt. Swift is first-write-wins.
  • frags_lost was inverted — it counted the fragments that did arrive. A frame missing one of ten reported nine lost, so the E2E fed the ABR controller ~9× the real loss and passed, while a real iPad barely moved it.

All three now match Swift, each with a test. The test that locked in the rebuild behavior is replaced by one proving progress survives.

Control messages were authorized by source IP alone

The parsed header — which carries the session id — was discarded (Ok((_, message))). An IP is shared by every process on the device and everything behind the same NAT, and is trivially spoofed on the local link. So a stray INPUT_EVENT clicked the mouse, a stray RECEIVER_REPORT steered the bitrate, and a late BYE from a superseded session tore down the one that replaced it. Every message except HELLO2 now has to carry the id the handshake minted. (The Swift client already gated this way in the other direction.)

The client's BYE never actually went out

sendBye enqueued three copies on the control queue; the caller cancels the socket on the next line, so even the zero-delay copy was still waiting its turn. The host saw no goodbye, streamed at a dead peer for the full 3 s liveness timeout, held the virtual display up just as long, and answered an immediate reconnect with "busy". The first copy is now synchronous.

Two input fixes

A drag ending on a letterbox bar released at the middle of the desktop — dropping the dragged window there — because an out-of-video release has no coordinate and fell back to screen centre. It now releases at the last point inside the video.

The edge deduper remembered exactly one id, but the client sends each press/release twice on purpose and Wi-Fi reorders freely: began(N), ended(N+1), began(N) walked the single slot and let the replayed press through as a second click. It now remembers the last eight.

Gates

107 Rust tests (6 new), 68 iOS tests (1 new), zero clippy warnings, simulator E2E green at 180 frames / 60 fps.

The Rust reassembler is a hand-written mirror of the Swift one and names
that file as its specification, but it had drifted in three ways that all
made the host-side tests validate a receiver nobody runs.

A fragment whose count disagreed with the live frame's rebuilt that
frame, discarding everything already buffered for it; Swift keeps the
first-seen count and ignores the odd fragment, so one late duplicate
could throw away nine tenths of a keyframe. A replayed fragment
overwrote good bytes and counted as another receipt. And frags_lost
counted the fragments that DID arrive rather than the ones that did not,
so a frame missing one of ten reported nine lost — the end-to-end test
fed the ABR controller roughly nine times the real loss and passed,
while a real iPad barely moved it. All three now match Swift, with tests
for each; the test that locked in the rebuild behavior is replaced by one
that proves progress survives.

Control messages were authorized by source IP alone: the parsed header,
which carries the session id, was discarded. An IP is shared by every
process on the device and by everything behind the same NAT, and is
trivially spoofed on the local link, so a stray INPUT_EVENT clicked the
mouse, a stray RECEIVER_REPORT steered the bitrate, and a late BYE from a
superseded session tore down the one that replaced it. Every message
except HELLO2 now has to carry the id the handshake minted. The client
already did this in the other direction.

The client's BYE never went out. sendBye enqueued three copies on the
control queue and the caller cancelled the socket on the next line, so
even the zero-delay copy was still waiting its turn. The host saw
nothing, streamed at a dead peer for the full liveness timeout, held the
virtual display up for the same three seconds, and answered an immediate
reconnect with "busy". The first copy is now sent synchronously.

A drag that ended on a letterbox bar released at the middle of the
desktop, dropping whatever was being dragged there, because an
out-of-video release has no coordinate and fell back to screen centre.
It now releases at the last point that was inside the video.

Finally, the input deduper remembered exactly one edge id, but the client
sends each press and release twice on purpose and Wi-Fi reorders freely:
began(N), ended(N+1), began(N) walked the single slot and let the
replayed press through as a second click. It remembers the last eight.
@whoisaldo
whoisaldo merged commit 0eb1d27 into main Aug 27, 2026
4 checks passed
@whoisaldo
whoisaldo deleted the fix/protocol-parity-and-input branch August 27, 2026 17:36
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