Q3 iter-3 — dispatch table v2 rules out ordering, CDS still RST_STREAM's s3¶
Status: verified — completed 2026-04-19
iter-3 aligned the event-driven dispatcher's wire emit order
with the real iPhone's s2c order from the Q2 pcap. All 10 replay
frames dispatched (send = 14326 bytes). Client recv stayed at
217 bytes (same as iter-2). CDS still RST_STREAM'd stream 3 with
STREAM_CLOSED. Outcome: the "dispatch ordering" hypothesis
from iter-02/findings.md is empirically disproven — the
rejection happens at a deeper application-semantics layer.
Full writeup: findings.md.
iter-4 will test whether dropping #9 RST_STREAM(s1) changes
CDS's behaviour. If not, iter-5 will test whether unzeroing
the identifiers in the 14 KB Handshake dict matters.
What iter-3 does¶
- Listener
iosmux-spike-listener.pycommitc842b56replaces iter-2's bundled triggers with: client DATA(s1)per-occurrence counter (1st →#4, 2nd →#5, 3rd+ dropped)client DATA(s3)composite trigger fires#7,#8,#9- Deploy to havoc, trigger CDS via
killall CoreDeviceService+devicectl list devices, capture session artifacts. - Decode and compare with iter-02 recv byte-for-byte.
Files¶
findings.md— full decode, dispatcher wire-order reconciliation with the pcap, iter-4 hypothesis ranking.results/session-01.log— per-frame listener log showing all 10 dispatches fired.results/session-01-recv.bin— 217 bytes from CDS (preface + 9 frames ending in RST_STREAM(s3)).results/session-01-send.bin— 14326 bytes sent to CDS (full corpus).results/iosmux-listener.log,results/tunneld-spike.log— lifecycle logs, redacted.
What changed vs. iter-2¶
| Aspect | iter-2 | iter-3 |
|---|---|---|
DATA(s1) handling |
one-shot flag (loses #5) | counter (1st→#4, 2nd→#5) |
#8/#9 trigger |
client DATA(s1) (too early) |
client DATA(s3) (matches pcap) |
| Dispatches fired | 9/10 (#5 lost) | 10/10 |
| Send bytes | 14326 (but #5 was replaced by #8, so content differs) | 14326 (full corpus) |
| Recv bytes | 217 | 217 (unchanged) |
| Wire frame order vs pcap | mismatch at #8 timing |
matches pcap (only #5/#6 inter-stream swap, semantically equivalent) |
| CDS teardown | RST_STREAM(s3, STREAM_CLOSED) | RST_STREAM(s3, STREAM_CLOSED) |
Dispatch rules (iter-3, as deployed)¶
client SETTINGS(s0, non-ACK) → emit replay #0, #1, #2
client HEADERS(s1) → emit replay #3
client DATA(s1), count == 1 → emit replay #4 (empty-dict mirror)
client DATA(s1), count == 2 → emit replay #5 (sync 0x0201 mirror)
client DATA(s1), count >= 3 → silently drop
client HEADERS(s3) → emit replay #6
client DATA(s3) → emit replay #7, #8, #9
client SETTINGS-ACK → noop (acknowledged our SETTINGS)
client WINDOW_UPDATE → noop
Key negative result¶
iter-3 did what it set out to do — reproduce the pcap's emit order under real CDS — and therefore it rules out:
- HTTP/2 framing issues (CDS's h2c parser accepts every frame)
- Stream-ownership violations (no PROTOCOL_ERROR GOAWAY)
#8early-arrival concerns (fired in correct order)#5missing (fired correctly)
The RST_STREAM(s3, STREAM_CLOSED) must therefore be caused by something below the frame schedule: semantic content, device identity verification, or per-frame timing. iter-4+ narrows that down one hypothesis at a time.