Phase D.6.2 H1c findings — pair trigger falsified; trigger-type is not the missing variable¶
Status: verified — 2026-04-23
Tested hypothesis H1c from iter-07-uuid-patched/findings.md:
"maybe devicectl manage pair (which initiates actual
pair-protocol flow) will push CDS into post-handshake
RemoteXPC traffic where devicectl device info did not."
Result: four sessions captured, every one hit the same
40-line handshake baseline with zero post-handshake frames.
devicectl returned com.apple.Mercury.error 1000 "connection
was interrupted" — the identical symptom as D.6.0-B / iter-6.
Three trigger classes now tested (device info, manage pair,
unpair+pair) all produce the same silent-hold behaviour.
TL;DR¶
H1c is falsified. The missing variable is not what command we
invoke on the client side. CDS's post-handshake silence is
structural, not trigger-dependent. We need to look at what we
are not doing that a real iPhone would do after emitting #8 big
Handshake.
New hypothesis-ranking for D.6.3+:
- H1b (was also ranked here, now elevated) — passive port
instrumentation. Does CDS try to back-connect to any of the
62 service ports we advertise in the Handshake
Servicesdict? If yes, we're missing a server process for it. If no, CDS is waiting purely for another RemoteXPC frame from us. - H2-reply (new) — CDS may expect a specific follow-up
frame from us on stream 1 or stream 3 (e.g. a
msg_id=3XpcPayload carrying a Handshake-reply or acknowledgement dict). The iter-1 replay corpus ends at#8/#9because that's all the iPhone emitted in Q2's one-shotrsd-inforound-trip. Pair flow may require further server-side frames that Q2 never captured. - H1a (active) — dispatcher-side heartbeat / PING. Cheapest to test via dispatcher modification but least likely to be the variable.
The four sessions¶
| session | trigger point | handshake complete | EOF | delta | notes |
|---|---|---|---|---|---|
| 1 | killall CDS |
23:43:58 | 23:44:07 | 9 s | CDS sends FIN after the handshake reply; reconnects immediately |
| 2 | (retry after 1) | 23:44:11 | 23:44:27 | 16 s | Same pattern, slightly longer |
| 3 | manage pair --device $INTERNAL_UUID |
23:44:28 | 23:46:27 | 119 s | Long-hold, then EOF caused by the subsequent unpair devicectl call |
| 4 | unpair+pair retry |
23:46:27 | 23:46:30 | 3 s | Instant rejection — unpair already confused CDS |
Every session = byte-identical 40-line baseline: preface → server
SETTINGS+WINDOW_UPDATE → client SETTINGS → our #3 HEADERS(s1) →
client DATA(s1) → our #4 DATA(s1, empty dict) → client
HEADERS(s3) → our #6 HEADERS(s3) → client DATA(s1, sync) →
our #5 → client DATA(s3, INIT_HANDSHAKE) → our #7+#8 big
Handshake (with patched UUID). Never anything past #8.
Comparison across recent iterations¶
| iter | trigger | UUID | session fate |
|---|---|---|---|
| iter-4 (Python) | devicectl list devices |
zero | quick pkill (~13 s) — timeout unobserved |
| iter-6 | device info |
zero | CDS EOF at 23-36 s (3× consecutive) |
| iter-7 (D.5 smoke) | list devices |
zero | quick pkill (~5 s) — timeout unobserved |
| iter-7 (D.6.1-B) | device info |
random v4 | no EOF in 130 s; we pkill |
| iter-8 (D.6.2 H1c) | manage pair |
random v4 | EOFs at 9 s / 16 s / 119 s / 3 s |
Something in the pair flow causes CDS to stop and retry faster
than the device info flow does with the same UUID patch. Two
interpretations:
- Pair flow has a shorter application-level deadline than info — CDS's lockdown-pairing state machine expects a response within ~10-20 s and gives up faster.
- Pair flow cascades through multiple CDS internal states, each issuing its own fresh connection when our Handshake reply is silent after it — each retry = one new session in our log.
Both fit the data. Neither changes the finding: no new application-layer frames from CDS in any session.
devicectl error surfacing (same root cause)¶
ERROR: An error occurred while communicating with a remote process.
(com.apple.dt.CoreDeviceError error 3 (0x03))
The connection was interrupted. (com.apple.Mercury.error error 1000 (0x3E8))
name = com.apple.CoreDevice.CoreDeviceService
Mercury is the XPC messaging framework underneath. "The connection was interrupted" from Mercury means our session reached CDS, but the RemoteXPC request CDS wanted to dispatch through it never got an acknowledgement. devicectl → Mercury → CDS → our backend → silence → Mercury timeout → caller sees error 1000.
This is the same error surface as iter-6 and D.6.0-B. devicectl never gets past the handshake, regardless of what flag combo we pass.
Side-observation: devicectl's internal UUID vs tunneld UDID¶
devicectl manage pair --device 00008110-0004596E22A0401E (the
raw iPhone UDID from tunneld) returned "specified device was
not found". Only devicectl manage pair --device
E8A190DD-64F5-44A4-8D57-28E99E316D60 worked — a separate UUID
discovered via devicectl list devices. This is devicectl's
internal device identifier, maintained in some local database
separate from tunneld's UDID advertisement.
Implication for future D.6.x: when designing triggers, remember
that devicectl speaks in its own internal identifiers, not in
the iPhone UDID. This was not a factor in the experiment's
outcome but may matter for future commands.
Implications for D.6.3¶
The cleanest next experiment: widen the pcap filter. iter-8's
tcpdump was tcp port 34719 — we only captured traffic to/from
our backend. If CDS tried to back-connect to any of the 62
service ports in our advertised Services dict, we missed it.
H1b + pcap fix: next run uses tcpdump -i lo0 with no port
filter (or not port 49151 to exclude tunneld noise), so any TCP
SYN to any local port is visible.
Parallel alternative: a reference capture of real iPhone pair
flow via tcpdump on utun4 (same methodology as Q2). This
would give authoritative bytes for what the iPhone sends on the
pair path — but requires temporarily switching tunneld out of
SPIKE mode, running a real pair, switching back. Higher
scientific value; higher operational risk (pair state dance).
D.6.3 recommendation: start with the wider pcap filter (no risk, tells us if CDS is trying to back-connect). If pcap shows back- connect attempts → H1b confirmed, list of ports = next handler targets. If pcap shows no back-connects → H2-reply elevated, and the reference pcap becomes necessary.
Artifacts¶
Under
iter-08-pair-trigger/:
verbose-session.log— 12,279 B, 158 lines across 4 sessions. Patched UUIDs:aa498892-...(session 1) and three more random ones (one per session). All in v4 format.iosmux-d8.pcap— 75,020 B loopback capture, filtertcp port 34719. Clean scan for real UDID (0 matches).- No redactions applied (log + pcap both free of identifiers).
Safe to commit as-is.
Status of the H1c chain¶
- Step A (trigger test): delivered, H1c falsified.
- Step B (decode + document): this document.
iter-8 closes under H1c. D.6.3 widens the tcpdump lens to separate H1b from H2-reply.