Skip to content

Pair Button + CFNetwork Crash Analysis

Date: 2026-04-12 (CFNetwork part RESOLVED, Pair button part still relevant) Status: CFNetwork crash fixed. Pair button mechanism described below is the path forward but needs to be implemented via the right protocol layer.

Part 1: CFNetwork Crash — RESOLVED

Original problem

CDS crashed with SIGSEGV in HTTPConnectionCache::timeoutIdleConnections() when service connection actions ran. NULL+0x68 deref inside CFNetwork.

Root cause

interpose_xpc_remote_connection_create_with_remote_service was returning NULL for any service not in our hardcoded list of 10 services. CFNetwork registered NULL in HTTPConnectionCache and crashed during the idle-cleanup timer.

Fix applied

Parse all 74 services from the RSD Handshake Services dict into g_services[] at init time. The interpose then has a real port for any service Xcode asks for, and never returns NULL. See iosmux_xpc_proxy.m Handshake event handler.

This part of the document is kept for historical context only — the bug is gone.

Part 2: Pair Button Mechanism — STILL RELEVANT

Problem

Xcode shows "Pair" button even when DeviceInfo.pairingState is set to .paired. Clicking it triggers an XPC action flow that we currently can't satisfy.

Mechanism (verified by DVTCoreDeviceCore disassembly)

Xcode UI displays the device based on a computed deviceWindowCategory property in DVTDeviceKit, which depends on three KVO properties of DVTCoreDevice_Impl:

deviceWindowCategory =
    if hasConnection then connected_path (category 3 or 6 → no Pair button)
    else                  disconnected_path (category 0 or 4 → shows Pair button)

hasConnection is computed:

hasConnection = (_shadowUseAssertion != nil &&
                 _shadowUseAssertion.state == .fulfilled)

_shadowUseAssertion is a CoreDevice.DeviceUsageAssertion? field on DVTCoreDevice_Impl. It is set when Xcode's local CoreDevice.RemoteDevice.acquireDeviceUsageAssertion(withReason:options:...) returns success — which dispatches an AcquireDeviceUsageAssertionActionDeclaration through Mercury to CDS and waits for the reply. (Wire action ID: com.apple.coredevice.action.acquireusageassertion. The Swift type name includes "Device" — earlier notes said "AcquireB…" because Swift mangling uses B as a single-letter substitution for the previously seen identifier Device, which decodes to "AcquireDeviceUsageAssertion".)

Without a successful response to that action, _shadowUseAssertion stays nil → hasConnection is false → device window category is "disconnected" → Xcode shows the Pair button.

Implication

To make the Pair button disappear, we need CDS to respond successfully to the acquireusageassertion action. The exact response format is the CoreDevice.AcquireDeviceUsageAssertionActionDeclaration.Output associated type — and per the empirical disasm in notes/iosmux-mercury-action-design-research.md (Q2), no concrete .Output struct exists in CoreDevice; the Codable Output is Void (empty). The serialized return container that DVT cares about is CoreDevice.UsageAssertionInformation (UUID identifier + owningProcess + processName + creationReason + hostName + preparednessDescription), but it's returned by the listUsageAssertions query, not by Acquire itself; on Acquire success Xcode just constructs a local DeviceUsageAssertion instance from the inputs it sent.

Two gates, not one

hasConnection (computed from _shadowUseAssertion.fulfilled) and pairingState (a DeviceInfo struct field) are independent gates that look like one. The disasm above shows hasConnection controls whether the Pair button is rendered at all; once a device row exists in Xcode's Devices window, pairingState drives the row's label and icon.

Older project notes (e.g. archive/2026-03-session8-five-questions.md) that say "Pair button gates on pairingState" are describing label-state observation without separating it from button rendering. The DVTCoreDeviceCore disasm in this document is the empirical anchor for button rendering; reconcile any older mention of "pair-state gates the button" against it.

ADR-0009 §Consequences names a successful synthetic AcquireDeviceUsageAssertion reply (Mercury Codable Output) as the mechanism that flips _shadowUseAssertion.fulfilled and thereby hides the Pair button without invoking iOS Pair-Setup against the real iPhone.

Empirical confirmation 2026-04-28 (session 14 iter 17)

Pair-button click GAMBIT iteration loop (commits b808eae49c8f1e) reached the milestone state where Apple's Codable decoder accepts the entire PairActionDeclaration.Output = DeviceConnectionChangeResult reply with pairingState: Paired and Pairing attempt completed with error nil. The Pair button did NOT disappear; instead Xcode UI transitioned to a new spinner state ("Xcode has already started pairing… Follow the instructions on iPhone (iosmux) to complete pairing."). DVTFoundation's log at the same instant: unknown: skipping implicit preparation for device being ignored with reason: deviceIsUnpaired. So even after PairAction returns success with pairingState: Paired, Xcode UI still considers the device unpaired for connection-preparation purposes. This confirms empirically that _shadowUseAssertion.fulfilled (this section's "Pair button gating") is the third independent gate, AND that there is at minimum a fourth gate — the "implicit preparation" decision — that PairAction success does not by itself satisfy. Full schema + iteration trail in gambit-pair-action-schema.md. Open question for the next-gate research is Q-D66-14 in d66-research-questions.md.

Action interception complication

Xcode↔CDS uses Mercury Codable XPCDictionary with mangledTypeName envelope, NOT the flat CoreDevice.featureIdentifier dict format that pymobiledevice3 uses against the iPhone. Filtering by a top-level string key is impossible on this connection. See action-interception-full-picture.md for the synthesis and the new strategy options.

Part 3: Missing DeviceInfo Fields — UNVERIFIED HYPOTHESIS

We currently set: name, productType, hardwareModel, marketingName, state, pairingState, visibilityClass, areDeveloperDiskImageServicesAvailable, preparednessState.

The following fields may also affect Xcode's device categorization. The list comes from disassembling DVTCoreDeviceCore.DVTCoreDevice_Impl setters and matching them to CoreDeviceProtocols.DeviceInfo fields. None of these have been runtime-verified to actually change Xcode behavior.

Field Plausible value Why it might matter
transportType .localNetwork Auto-pair + connection logic
platform .iOS UI classification
deviceType .iPhone UI icon/category
reality .physical vs virtual/simulated
osVersion from Handshake UI display
osBuild from Handshake Compatibility checks
udid from Handshake Identity cross-check
authenticationType .manualPairing Pairing flow type
developerModeStatus .enabled Skip developer mode prompt
bootState .booted Device is running
isMobileDeviceOnly true iOS device flag

All of OSVersion / BuildVersion / UniqueDeviceID / ProductType / SerialNumber / DeviceClass / HardwarePlatform / ChipID / BoardId / UniqueChipID are available in the RSD Handshake Properties dict (46 keys total).

When to set these

Defer until after action interception is working. If acquireusageassertion returning success is enough to make the Pair button disappear and CDS to consider the device usable, the additional DeviceInfo fields may not be needed. If it isn't enough, set them one at a time and observe.