Skip to content

Q3 iter-2 — event-driven dispatch clears HTTP/2, REPLY_CHANNEL closes on dispatch-order bug

Status: verified — completed 2026-04-18

iter-2 replaced iter-1's upfront replay with an event-driven dispatcher that emits server frames in reaction to matching client frames on the same stream. Verdict: HTTP/2 framing layer accepted (no more PROTOCOL_ERROR GOAWAY), full XPC handshake exchanged on both channels, but CDS closed REPLY_CHANNEL with RST_STREAM(stream=3, error_code=5 STREAM_CLOSED) because our dispatch table fires the big Handshake (replay #8) on client DATA(s1) instead of on client DATA(s3). Full writeup: findings.md.

iter-3 (dispatch table v2: relocate #8/#9 onto client DATA(s3) trigger, split client DATA(s1) into a counter) is the next implementation pass.

What iter-2 does

  1. Listener iosmux-spike-listener.py commits 74e8d61 replace the upfront replay loop with a dispatch_replay() method invoked from handle_frame() for each client frame.
  2. Dispatch table keyed on (frame_type, stream_id, flags) with trigger-once dispatched_* flags to avoid double-emit.
  3. Deploy to havoc /tmp/iosmux-spike-listener.py, start with IOSMUX_SPIKE_REPLAY=1, trigger CDS via killall CoreDeviceService + devicectl list devices.
  4. Pull session artifacts, decode, compare with iter-0 pcap dispatch reference.

Files

What changed vs. iter-1

Aspect iter-1 iter-2
Replay strategy Upfront, all 10 frames before reading Event-driven, in reaction to client frames
CDS rejection type HTTP/2 PROTOCOL_ERROR GOAWAY RemoteXPC STREAM_CLOSED on stream 3
CDS stopped at After its first DATA(s1) After full handshake on both s1 and s3
Recv bytes 180 (preface + 5 frames) 217 (preface + 9 frames)
Debug text "request HEADERS: invalid stream_id" None (RST_STREAM has no diagnostic)
Failure layer HTTP/2 stream ownership RemoteXPC channel ordering

Dispatch rules (iter-2, as deployed)

client SETTINGS(s0, non-ACK)   → emit replay #0, #1, #2
client HEADERS(s1)             → emit replay #3, #4, #5
client DATA(s1), first only    → emit replay #8, #9   ← BUG (fires too early)
client HEADERS(s3)             → emit replay #6
client DATA(s3)                → emit replay #7

The bug is the client DATA(s1) line: #8 is the substantive 14 KB Handshake response and should wait until CDS has finished opening REPLY_CHANNEL (i.e. until after client DATA(s3)). See findings.md for the iter-3 proposal.

Reuse of iter-1 corpus

The 10-frame IPHONE_REPLAY_FRAMES tuple in ../iter-01/iphone_replay_bytes.py is unchanged. iter-2 re-imports the same module and emits exactly the same 14326 bytes — only the dispatch timing changed. This keeps the iter-0/iter-1 redaction provenance intact and lets iter-2 isolate a pure ordering-level variable.