Skip to content

Q3 iteration 0 — dispatch table sourced from Q2 pcap

Status: verified — 2026-04-18

All bytes below were extracted from /tmp/iosmux-q2-utun-capture.pcap (the Q2 live capture of pymobiledevice3 remote rsd-info) and decoded through pymobiledevice3 v9.9.1's own XpcWrapper.parse(). No guessing, no synthesis — these are real iPhone response bytes. Personal identifiers (UDID, serial, MAC, UUIDs, tunnel ULA) are redacted per the iosmux personal-data policy; ports, OS/model strings, entitlement names and service bundle IDs are kept verbatim because they characterize the protocol shape.

Summary

  • Pcap: 31 packets, DLT_NULL (BSD loopback), 14530 bytes of application-layer payload across both directions.
  • client→iPhone: 9 TCP-payload packets, 204 bytes total
  • iPhone→client: 3 TCP-payload packets, 14326 bytes total
  • HTTP/2 frames observed:
  • c2s: 8 frames (SETTINGS×2, WINDOW_UPDATE×1, HEADERS×2, DATA×3)
  • s2c: 10 frames (SETTINGS×2, WINDOW_UPDATE×1, HEADERS×2, DATA×4, RST_STREAM×1)
  • DATA frames c2s: 3 — all generic RemoteXPC handshake
  • DATA frames s2c: 4 — three mirror the c2s handshake, one is the substantive rsd-info response
  • Streams observed: 1 (ROOT_CHANNEL), 3 (REPLY_CHANNEL)
  • Cross-check with Q1: the c2s DATA bytes are byte-exact with the CDS capture already decoded in Q1-decode.md. pymobiledevice3's client and CoreDeviceService speak the identical RemoteXPC handshake envelope.

The critical finding

The iPhone's first substantive DATA frame (s2c frame #8, stream 1, 14124 bytes) carries MessageType = "Handshake" with the full Services directory inline. It is not just a mirror of the client's empty-dict handshake — it is both the mirror sequence (empty dict, sync 0x0201, INIT_HANDSHAKE on stream 3) and the first real payload that pymobiledevice3 consumes as the rsd-info reply. The MessagingProtocolVersion is 7, the device advertises 62 services, and the top-level dict has message_id=2 (the first two handshake frames are message_id=0). See pymobiledevice3.remote.remotexpc.RemoteXPCConnection._do_handshake — that method reads exactly this dict and never sends a separate rsd-info request frame. rsd-info is the handshake.

Implication for Q3 iter-1+: a spike listener that wants CDS to progress past "connection ready" must, after the three-frame XPC handshake mirror, emit one additional DATA frame on stream 1 carrying a MessageType=Handshake dictionary with at least Services, Properties, MessagingProtocolVersion and UUID. The exact ServiceVersion/port values do not need to match a real device — the shape does.

Per-frame table — iPhone → client (the reference for Q3 replies)

iPhone frame #4 — DATA, stream=1, length=44 bytes, off=0x0034

Raw payload hex:

92 0b b0 29 01 00 00 00 14 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 42 37 13 42 05 00 00 00
00 f0 00 00 04 00 00 00 00 00 00 00

Decoded (XpcWrapper.parse):

Container:
    magic = 0x29B00B92
    flags = Container:
        ALWAYS_SET = True
    message = Container:
        message_id = 0x0000000000000000
        payload = Container:
            magic = 0x42133742
            protocol_version = 0x00000005
            obj = Container:
                type = (enum) DICTIONARY 61440
                data = Container:
                    count = 0x00000000
                    entries = None

Semantic: ROOT_CHANNEL empty-dict reply — mirrors the client's c2s frame #3. pymobiledevice3's server-side in RemoteXPCConnection sends this as its first post-preface DATA frame; the iPhone mirrors it back to acknowledge the channel.

Pair to CDS client frame: byte-exact with Q1-decode.md DATA frame #0 (session-01-recv.bin offset 0x004c).

iPhone frame #5 — DATA, stream=1, length=24 bytes, off=0x0069

Raw payload hex:

92 0b b0 29 01 02 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

Decoded (XpcWrapper.parse):

Container:
    magic = 0x29B00B92
    flags = Container:
        ALWAYS_SET = True
    message = Container:
        message_id = 0x0000000000000000
        payload = None

Raw flags field 0x00000201ALWAYS_SET plus an unnamed 0x200 bit that pymobiledevice3's XpcFlags enum does not name. Exact same byte pattern as the c2s frame and as Q1's DATA frame #1.

Pair to CDS client frame: byte-exact with Q1-decode.md DATA frame #1 (offset 0x008a).

iPhone frame #7 — DATA, stream=3, length=24 bytes, off=0x0093

Raw payload hex:

92 0b b0 29 01 00 40 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

Decoded (XpcWrapper.parse):

Container:
    magic = 0x29B00B92
    flags = Container:
        ALWAYS_SET = True
        INIT_HANDSHAKE = True
    message = Container:
        message_id = 0x0000000000000000
        payload = None

Flag value 0x00400001 = ALWAYS_SET | INIT_HANDSHAKE. Sent after the iPhone opens REPLY_CHANNEL (HEADERS frame #6, stream 3).

Pair to CDS client frame: byte-exact with Q1-decode.md DATA frame #2 (offset 0x00ab).

iPhone frame #8 — DATA, stream=1, length=14124 bytes, off=0x00b4 — the rsd-info Handshake

First 64 bytes of raw payload:

92 0b b0 29 01 01 00 00 14 37 00 00 00 00 00 00
02 00 00 00 00 00 00 00 42 37 13 42 05 00 00 00
00 f0 00 00 04 37 00 00 05 00 00 00 4d 65 73 73
61 67 65 54 79 70 65 00 00 90 00 00 0a 00 00 00

XpcWrapper header fields (from those 64 bytes):

  • magic 92 0b b0 290x290bb092
  • flags 01 01 00 000x00000101 = ALWAYS_SET | DATA_PRESENT
  • size 14 37 00 00 00 00 00 000x3714 = 14100 (XPC payload size = DATA length 14124 minus the 24-byte wrapper header)
  • message_id 02 00 00 00 00 00 00 002
  • XpcPayload magic 42 37 13 42, protocol_version 5
  • root object: type 0x0000f000 = DICTIONARY, payload size 0x3704 = 14084, 5 top-level entries
  • first entry key "MessageType" (len 5), first value type 0x00009000 = STRING, len 10, data starts at 48 61 6e 64 73 68 61 6b 65 (Handshake\0)

Too large to hex-dump in full inside a markdown frame; the parser script at /tmp/iosmux-q3-parse-pcap.py prints the complete hex block.

Decoded top-level shape (XpcWrapper.parse) — 5 entries:

key type value (redacted where needed)
MessageType STRING "Handshake"
MessagingProtocolVersion UINT64 7
Services DICTIONARY 62 entries — full table below
Properties DICTIONARY 46 entries — table below
UUID UUID [redacted-16-bytes]

Properties — 46 entries. Device-identifying values (UniqueDeviceID, SerialNumber, EthernetMacAddress, UniqueChipID, BootSessionUUID) are redacted; OS/model/fuse strings are kept verbatim because they characterize the protocol shape.

key type value
ThinningProductType STRING "iPhone14,6"
HumanReadableProductVersionString STRING "26.4.1"
HasSEP BOOL true
HWModel STRING "D49AP"
EthernetMacAddress STRING "[redacted-mac]"
EffectiveProductionStatusSEP BOOL true
ChipID UINT64 33040
CPUArchitecture STRING "arm64e"
AppleInternal BOOL false
HWModelDescriptionForUserVisibility STRING "D49AP"
RegionInfo STRING "LL/A"
IsUIBuild BOOL true
SupplementalBuildVersion STRING "23E254"
SigningFuse BOOL true
EffectiveSecurityModeAp BOOL true
DeviceSupportsLockdown BOOL true
RestoreLongVersion STRING "23.5.254.0.0,0"
SerialNumber STRING "[redacted-serial]"
ProductType STRING "iPhone14,6"
ProductName STRING "iPhone OS"
MobileDeviceMinimumVersion STRING "1827.100.14"
Image4CryptoHashMethod STRING "sha2-384"
SensitivePropertiesVisible BOOL true
OSVersion STRING "26.4.1"
BuildVersion STRING "23E254"
ProductTypeDescForUserVisibility STRING "iPhone14,6"
UniqueDeviceID STRING "[UDID]"
UniqueChipID UINT64 [redacted-ecid]
StoreDemoMode BOOL false
EffectiveSecurityModeSEP BOOL true
EffectiveProductionStatusAp BOOL true
DeviceColor STRING "1"
BoardId UINT64 16
BootSessionUUID UUID [redacted-uuid]
SecurityDomain UINT64 1
RegionCode STRING "LL"
ModelNumber STRING "MMX83"
DeviceEnclosureColor STRING "1"
DeviceClass STRING "iPhone"
CertificateSecurityMode BOOL true
CertificateProductionStatus BOOL true
RemoteXPCVersionFlags UINT64 72057594037927942 (0x0100000000000006)
OSInstallEnvironment BOOL false
IsVirtualDevice BOOL false
Image4Supported BOOL true
HardwarePlatform STRING "t8110"

Services — 62 entries. Each service value is a DICTIONARY with three keys: Port (STRING, tunneld-assigned), Entitlement (STRING) and Properties (DICTIONARY with UsesRemoteXPC: BOOL and optionally ServiceVersion: INT64). Port numbers are tunneld's ephemeral TCP ports on the iPhone side of the tunnel — they change across reboots and are not personal identifiers.

idx service name Port UsesRemoteXPC SvcVer Entitlement
0 com.apple.springboardservices.shim.remote 55495 false com.apple.mobile.lockdown.remote.trusted
1 com.apple.RestoreRemoteServices.restoreserviced 55487 true 2 com.apple.private.RestoreRemoteServices.restoreservice.remote
2 com.apple.osanalytics.logTransfer 55485 true com.apple.ReportCrash.antenna-access
3 com.apple.crashreportmover.shim.remote 55469 false com.apple.mobile.lockdown.remote.trusted
4 com.apple.mobile.lockdown.remote.untrusted 55464 false 1 com.apple.mobile.lockdown.remote.untrusted
5 com.apple.accessibility.axAuditDaemon.remoteAXService 55455 true 1 AppleInternal
6 com.apple.mobile.notification_proxy.shim.remote 55452 false com.apple.mobile.lockdown.remote.trusted
7 com.apple.mobile.house_arrest.shim.remote 55450 false com.apple.mobile.lockdown.remote.trusted
8 com.apple.mobile.mobile_image_mounter.shim.remote 55445 false com.apple.mobile.lockdown.remote.trusted
9 com.apple.GPUTools.MobileService.shim.remote 55444 false com.apple.mobile.lockdown.remote.trusted
10 com.apple.backgroundassets.lockdownservice.shim.remote 55434 false com.apple.mobile.lockdown.remote.trusted
11 com.apple.mobile.insecure_notification_proxy.remote 55492 true 1 com.apple.mobile.insecure_notification_proxy.remote
12 com.apple.sysdiagnose.remote.trusted 55490 true com.apple.private.sysdiagnose.remote.trusted
13 com.apple.webinspector.shim.remote 55482 false com.apple.mobile.lockdown.remote.trusted
14 com.apple.afc.shim.remote 55471 false com.apple.mobile.lockdown.remote.trusted
15 com.apple.pcapd.shim.remote 55470 false com.apple.mobile.lockdown.remote.trusted
16 com.apple.internal.devicecompute.CoreDeviceProxy.shim.remote 55461 false com.apple.mobile.lockdown.remote.trusted
17 com.apple.amfi.lockdown.shim.remote 55435 false com.apple.mobile.lockdown.remote.trusted
18 com.apple.mobile.lockdown.remote.trusted 55493 false 1 com.apple.mobile.lockdown.remote.trusted
19 com.apple.PurpleReverseProxy.Conn.shim.remote 55481 false com.apple.mobile.lockdown.remote.trusted
20 com.apple.mobilebackup2.shim.remote 55474 false com.apple.mobile.lockdown.remote.trusted
21 com.apple.modelmanager.remote 55466 true AppleInternal
22 com.apple.mobile.notification_proxy.remote 55465 true 1 com.apple.mobile.notification_proxy.remote
23 com.apple.carkit.remote-iap.service 55458 true AppleInternal
24 com.apple.crashreportcopymobile.shim.remote 55446 false com.apple.mobile.lockdown.remote.trusted
25 com.apple.commcenter.mobile-helper-cbupdateservice.shim.remote 55441 false com.apple.mobile.lockdown.remote.trusted
26 com.apple.iosdiagnostics.relay.shim.remote 55438 false com.apple.mobile.lockdown.remote.trusted
27 com.apple.carkit.service.shim.remote 55486 false com.apple.mobile.lockdown.remote.trusted
28 com.apple.mobile.assertion_agent.shim.remote 55479 false com.apple.mobile.lockdown.remote.trusted
29 com.apple.atc.shim.remote 55476 false com.apple.mobile.lockdown.remote.trusted
30 com.apple.mobile.installation_proxy.shim.remote 55462 false com.apple.mobile.lockdown.remote.trusted
31 com.apple.security.cryptexd.remote 55457 true 3 com.apple.private.security.cryptexd.remote
32 com.apple.instruments.dtservicehub 55454 com.apple.private.dt.instruments.dtservicehub.client
33 com.apple.mobile.heartbeat.shim.remote 55453 false com.apple.mobile.lockdown.remote.trusted
34 com.apple.mobilesync.shim.remote 55448 false com.apple.mobile.lockdown.remote.trusted
35 com.apple.mobile.MCInstall.shim.remote 55442 false com.apple.mobile.lockdown.remote.trusted
36 com.apple.mobile.file_relay.shim.remote 55437 false com.apple.mobile.lockdown.remote.trusted
37 com.apple.fusion.remote.service 55491 true 1 com.apple.fusion.remote.service
38 com.apple.streaming_zip_conduit.shim.remote 55473 false com.apple.mobile.lockdown.remote.trusted
39 com.apple.atc2.shim.remote 55468 false com.apple.mobile.lockdown.remote.trusted
40 com.apple.idamd.shim.remote 55467 false com.apple.mobile.lockdown.remote.trusted
41 com.apple.syslog_relay.shim.remote 55440 false com.apple.mobile.lockdown.remote.trusted
42 com.apple.companion_proxy.shim.remote 55436 false com.apple.mobile.lockdown.remote.trusted
43 com.apple.mobile.storage_mounter_proxy.bridge 55494 true 1 com.apple.private.mobile_storage.remote.allowedSPI
44 com.apple.corecaptured.remoteservice 55488 true com.apple.corecaptured.remoteservice-access
45 com.apple.dt.ViewHierarchyAgent.remote 55484 true com.apple.private.dt.ViewHierarchyAgent.client
46 com.apple.preboardservice.shim.remote 55483 false com.apple.mobile.lockdown.remote.trusted
47 com.apple.dt.remotepairingdeviced.lockdown.shim.remote 55480 false com.apple.mobile.lockdown.remote.trusted
48 com.apple.misagent.shim.remote 55472 false com.apple.mobile.lockdown.remote.trusted
49 com.apple.bluetooth.BTPacketLogger.shim.remote 55447 false com.apple.mobile.lockdown.remote.trusted
50 com.apple.os_trace_relay.shim.remote 55443 false com.apple.mobile.lockdown.remote.trusted
51 com.apple.PurpleReverseProxy.Ctrl.shim.remote 55439 false com.apple.mobile.lockdown.remote.trusted
52 com.apple.internal.dt.coredevice.untrusted.tunnelservice 55489 true 2 com.apple.dt.coredevice.tunnelservice.client
53 com.apple.mobile.insecure_notification_proxy.shim.remote 55478 false com.apple.mobile.lockdown.remote.untrusted
54 com.apple.accessibility.axAuditDaemon.remoteserver.shim.remote 55477 false com.apple.mobile.lockdown.remote.trusted
55 com.apple.preboardservice_v2.shim.remote 55475 false com.apple.mobile.lockdown.remote.trusted
56 com.apple.remote.installcoordination_proxy 55463 true 1 com.apple.private.InstallCoordinationRemote
57 com.apple.internal.devicecompute.CoreDeviceProxy 55460 false 1 AppleInternal
58 com.apple.dt.remoteFetchSymbols 55459 true 1 com.apple.private.dt.remoteFetchSymbols.client
59 com.apple.sysdiagnose.remote 55456 true com.apple.private.sysdiagnose.remote
60 com.apple.mobileactivationd.shim.remote 55451 false com.apple.mobile.lockdown.remote.trusted
61 com.apple.mobile.diagnostics_relay.shim.remote 55449 false com.apple.mobile.lockdown.remote.trusted

Pair to CDS client frame: no direct CDS equivalent exists in the Q1 capture because CDS stalled before reaching this stage — this is the frame the spike listener needs to synthesize to unblock Q1's stall.

iPhone frame #9 — RST_STREAM, stream=1, length=4 bytes, off=0x37e9

Raw payload hex:

00 00 00 00

error_code = 0x00000005 (STREAM_CLOSED). The iPhone closes stream 1 immediately after delivering the Handshake DATA frame on that same stream. The RST_STREAM with STREAM_CLOSED (not NO_ERROR, as an earlier revision of this doc mistakenly stated) reads in RFC 7540 §7 as "a frame was received on a stream in the half-closed (remote) or closed state" — consistent with the server finishing its one-shot reply and signalling "I am done with this stream, don't send anything else on it". pymobiledevice3 consumes the Handshake dict and exits — rsd-info is a one-shot round-trip, not a long-lived channel. Stream 3 (REPLY_CHANNEL) is left open.

Per-frame table — client → iPhone (for reference, not dispatch)

The client's c2s DATA frames are byte-exact with Q1's session-01-recv.bin. Only the HTTP/2 offsets differ (the c2s preface here is at offset 0; in Q1 the preface was also at offset 0).

c2s frame #3 — DATA, stream=1, length=44 bytes, off=0x0043

92 0b b0 29 01 00 00 00 14 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 42 37 13 42 05 00 00 00
00 f0 00 00 04 00 00 00 00 00 00 00

Decoded: empty XPC dictionary, ALWAYS_SET only — byte-exact match with Q1 DATA frame #0 and with pymobiledevice3.remote.remotexpc.RemoteXPCConnection._do_handshake's await self.send_request({}).

c2s frame #4 — DATA, stream=1, length=24 bytes, off=0x0078

92 0b b0 29 01 02 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

Header-only wrapper with flags 0x00000201 (ALWAYS_SET + unnamed 0x200). Byte-exact with Q1 DATA frame #1.

c2s frame #6 — DATA, stream=3, length=24 bytes, off=0x00a2

92 0b b0 29 01 00 40 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

Flags 0x00400001 = ALWAYS_SET | INIT_HANDSHAKE. Sent after opening REPLY_CHANNEL via HEADERS on stream 3. Byte-exact with Q1 DATA frame #2.

Parse refusals

None. All 7 DATA frames (3 c2s + 4 s2c) parsed cleanly through pymobiledevice3.remote.xpc_message.XpcWrapper.parse. No exceptions, no magic mismatches.

Implications for Q3 iterations 1+

  1. The spike listener's reply sequence is completely specified by this capture. After CDS sends its three DATA frames (empty dict on stream 1, sync 0x0201 on stream 1, INIT_HANDSHAKE on stream 3), the listener must emit:

    • HTTP/2 SETTINGS/WINDOW_UPDATE/SETTINGS-ACK/HEADERS on stream 1 (already done by Q1's spike)
    • DATA on stream 1 = empty-dict XpcWrapper (44 bytes, message_id=0)
    • DATA on stream 1 = sync XpcWrapper (24 bytes, flags 0x0201)
    • HEADERS on stream 3 opening REPLY_CHANNEL
    • DATA on stream 3 = INIT_HANDSHAKE XpcWrapper (24 bytes, flags 0x00400001)
    • DATA on stream 1 = the Handshake dictionary (MessageType/MessagingProtocolVersion/Services/Properties/UUID, message_id=2, flags 0x00000101)
    • RST_STREAM on stream 1, error_code 5 (STREAM_CLOSED)
  2. message_id progression is 0, 0, 0, 2 on stream 1. The sync frame (0x0201) and the Handshake DATA share message_id=0 and message_id=2 respectively. message_id=1 is never used in this capture — unclear whether CDS expects it on a different path (e.g. for an INIT_HANDSHAKE ack) or whether the iPhone simply skips it. Known gap. Not a blocker for Q3 (mirroring the observed values is enough), but worth flagging.

  3. Services values (ports 554xx) can be any non-conflicting ephemeral ports. The listener does not need to match the real device — CDS validates the dict shape, not the port values. Entitlement strings and bundle IDs, however, are likely referenced by CDS code paths and should be preserved verbatim.

  4. Properties.RemoteXPCVersionFlags = 0x0100000000000006 and MessagingProtocolVersion = 7 are the version contract. A spike listener that wants to keep CDS progressing must advertise the same (or at least CDS-compatible) values. Lower values may cause CDS to reject the device as too-old.

  5. No pair-specific traffic is observed. rsd-info is device discovery, not pairing. Q3's subsequent iterations must capture a pymobiledevice3 pair or equivalent command to see what bytes CDS will send after the Handshake dict. That is Q3 iter-2+ scope.

  6. The 0x200 flag bit is still unnamed. Same gap already recorded in Q1-decode.md. Not a blocker.

How to reproduce

source /home/op/venvs/iosmux-research/bin/activate
pip install scapy   # if not already present in the venv
python3 /tmp/iosmux-q3-parse-pcap.py

The script reads /tmp/iosmux-q2-utun-capture.pcap directly, strips the 4-byte BSD loopback (DLT_NULL, AF_INET6) prefix, reassembles both TCP directions by sequence number, walks HTTP/2 frames, and prints the same table as above. The script is kept on /tmp and is not committed (research one-shot tool). The pcap itself stays at /tmp/iosmux-q2-utun-capture.pcap and is not committed (contains the unredacted UDID/serial/MAC).