Skip to content

Phase D.6.1-B findings — hypothesis H1 (placeholder UUID) CONFIRMED STRONGLY

Status: verified — 2026-04-19

Breakthrough. D.6.0-B observed CDS tearing down the TCP connection at 23-36 s after our #8 big Handshake with the 16-byte zero placeholder UUID. D.6.1-B swapped that UUID for a fresh RFC 4122 v4 UUID per session (via the D.6.1-A IOSMUX_HANDSHAKE_UUID=random env var, commit ffe7c29) and CDS held the connection for the full 130-second observation window with no EOF, only TCP keepalives every ~15 s. H1 is CONFIRMED: the zero UUID was the session- progression gate, and patching it unblocks CDS's handshake acceptance.

TL;DR

The iter-6 30-second timeout is gone. D.6.1-B session 1 held TCP ESTABLISHED for 130 s (orchestrator's kill window), with CDS sending TCP keep-alive ACKs every ~15 s as the only client→server traffic. Backend PID 5839's FIN was the only session terminator — CDS did not disconnect on its own.

But: CDS did not send any new application-layer traffic either. After our #8 big Handshake, CDS goes truly silent at the HTTP/2 layer. It neither disconnects (good, H1 gate passed) nor initiates a service request (not-so-good, we still don't know what it expects next).

This is a new third state beyond iter-4 "killed before timeout" and iter-6 "killed by CDS":

iter UUID in #8 session fate
iter-1 → iter-3 zero (redacted) CDS disconnects via PROTOCOL_ERROR or RST_STREAM
iter-4 → D.5 zero (redacted) CDS TCP ESTABLISHED, pkill within 13 s, timeout unobserved
D.6.0-B zero (redacted) CDS closes TCP with peer EOF at 23-36 s (30 s window observed 3x)
D.6.1-B random v4 per session CDS holds TCP ESTABLISHED, 130 s observed, no EOF, no new traffic

The sessions

Setup

  • Backend: cmd/iosmux-backend/, commit ffe7c29, cross-compiled darwin/amd64 (5,808,976 B Mach-O x86_64 PIE).
  • Env: IOSMUX_HANDSHAKE_UUID=random IOSMUX_BACKEND_VERBOSE=1.
  • SPIKE tunneld: PID 5731 (freshly re-armed after the iter-6 tunnel died); advertising tunnel-port:34719 for the iPhone's UDID.

Session timeline

Single CDS connection observed:

event timestamp elapsed since handshake
Backend start 02:49:50
accept session=1 02:50:05.382 t₀
Full iter-4 dispatch emitted (#0-#8) 02:50:05.382 +0 ms
UUID patched for this session 02:50:05.382 8529522e-248a-4641-a72a-8557fc17aa6d (v4)
TCP keep-alive observed 02:50:20 +15 s
TCP keep-alive observed 02:50:35 +30 s
TCP keep-alive observed 02:50:50 +45 s
... (keep-alives continue every 15 s) ... ...
TCP keep-alive observed 02:52:06 +121 s
Backend kill (pkill) 02:52:16 +131 s
Local FIN sent 02:52:16 +131 s

D.6.0-B's 3 sessions died at +36 s, +32 s, +23 s. D.6.1-B's session 1 hit +131 s with zero CDS-side disconnect. The pcap confirms TCP state was ESTABLISHED on both ends at the moment we sent our FIN.

Per-session UUID

One UUID per session because D.6.1-B captured only one session. In future D.6.x iterations each new CDS connection will get a fresh v4 — the patching happens at every #8 emit, not once per backend lifetime.

session=1 verbose send UUID (patched) => 8529522e-248a-4641-a72a-8557fc17aa6d

Version nibble 4 at position 12 (4641) and variant bits 10xx at position 16 (a72a) confirm proper RFC 4122 v4 layout.

What this reinterprets (second-order clarifications)

iter-04/findings.md and iter-05-go-backend/findings.md were updated after D.6.0-B with a clarification callout: "CDS silently evaluates the handshake, decides it is unusable, disconnects." That clarification is true for the zeroed fixture but not inherent to our handshake structure. With a session-bound UUID, the same dispatcher produces an accepted handshake and a long-lived connection.

The original iter-4 dispatch-table work and the D.5 Go backend are therefore not semantically broken — they were blocked by a single 16-byte field redacted at source. The byte-level framing, stream topology, XPC envelopes, and service dict structure were all correct; only the UUID identity fingerprint was missing.

D.5's "byte-exact replaces the Python listener" claim still holds: Python and Go both emit the same 14,313 B server-side stream. The difference between iter-4 (Python 30 s timeout) and D.6.1-B (Go, 130 s no timeout) is purely the UUID patching, which the Python listener does not do.

What D.6.1-B did NOT resolve

CDS accepts the handshake, but doesn't initiate anything next. The original D.6.0 goal — observing CDS's first post-handshake RemoteXPC service request — is still unmet. The only post-handshake wire traffic during 130 s was TCP keep-alives (stack-level, not HTTP/2).

Candidate explanations for the new silence (ranked by likelihood, NOT verified):

H1a: CDS expects server-initiated traffic (not client-initiated)

In real iPhone flows, the iPhone proactively pushes heartbeat or service-advertisement updates. Our dispatcher emits nothing past #8 ever. CDS may be genuinely idle, waiting for us to say something.

Test: extend the dispatcher to emit a PING on stream 0 every N seconds after #8, observe whether that triggers CDS to respond.

H1b: CDS waits for a specific user-side action

devicectl device info still returned error 1000 "device not found" even with accepted handshake. The error path in CDS may be triggered by missing Services that our Handshake advertises but that are not reachable (ports CDS tries to connect to but no one listens on).

Test: instrument the dispatcher to log any new TCP connection attempt to the ports in our advertised Services dict. If CDS tries com.apple.lockdown.remote.trusted:55451, we'd see a second loopback connection.

H1c: CDS's state machine is deterministic and simply has nothing to say

Some services may require devicectl pair as a different starting action. Pair is the only action that initiates service traffic from CDS rather than reading a cached state.

Test: try devicectl manage pair with the UUID-patched backend running.

Recommended D.6.2: test H1c first (cheapest — no code changes, just a different trigger). If pair trigger produces new traffic, we have the first actual post-handshake request to decode. If pair also gives silence, try H1b (passive port- listen instrumentation). H1a requires dispatcher changes, save for last.

Artifacts

Under iter-07-uuid-patched/:

  • verbose-session.log — 3,144 B, 40 lines. Single session with the patched UUID line clearly visible before #8.
  • iosmux-d7.pcap — 19,806 B loopback capture. Scanned for real iPhone UDID (upper, lower, binary) — zero hits. Safe to commit as-is.

No redaction needed on the log — the backend-generated v4 UUID is ephemeral session state we invented, not PII. Pcap contains only our handshake bytes + TCP keep-alive frames.

How to reproduce

On havoc (assumes apparatus armed with SPIKE tunneld):

# Cross-compile + deploy with UUID patching ON
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build \
    -o /tmp/iosmux-backend-darwin ./cmd/iosmux-backend/
scp /tmp/iosmux-backend-darwin havoc:/tmp/iosmux-backend
ssh havoc 'chmod +x /tmp/iosmux-backend'

ssh havoc 'nohup env IOSMUX_HANDSHAKE_UUID=random IOSMUX_BACKEND_VERBOSE=1 \
    /tmp/iosmux-backend -listen "[::1]:34719" \
    > /tmp/iosmux-backend.log 2>&1 &'

# Trigger + WAIT past the iter-6 30 s timeout
ssh havoc-root 'killall CoreDeviceService'
ssh havoc "devicectl device info details --device <UDID>"
sleep 90

# Observe: log should NOT contain 'peer closed (EOF)'
ssh havoc 'grep -c "peer closed (EOF)" /tmp/iosmux-backend.log'
# Expected: 0

Status of the D.6.1 chain

  • Step A (UUID patching code): delivered in commit ffe7c29.
  • Step B (deploy + re-arm + observe): delivered, H1 confirmed strongly. This document.
  • Step C (next hypothesis test): D.6.2 with H1c (pair trigger) first.

D.6.1 closes. The handshake-rejection mystery is resolved: it was the zero UUID. D.6.2 picks up the "what does CDS expect us to send next" question with the handshake-gate unblocked.