# How the Host CLI Communicates with the Guest Daemon in vphone-cli

> Discover how the vphone-cli host communicates with the guest daemon via vsock using a JSON protocol on port 1337, including handshake and auto-updates.

- Repository: [Lakr/vphone-cli](https://github.com/Lakr233/vphone-cli)
- Tags: internals
- Published: 2026-09-06

---

**The host CLI (`vphone-cli`) communicates with the guest `vphoned` daemon over a virtual socket (vsock) using a length-prefixed JSON protocol on port 1337, with automatic handshake, request-response tracking, and binary auto-update capabilities.**

The `vphone-cli` project provides a command-line interface for managing iOS virtual machines on macOS through Apple's Virtualization framework. The host-side communication architecture centers on a clean separation between the VM lifecycle management and the control channel that talks to the guest daemon running inside the iOS VM.

## Virtual Socket (vsock) Setup and Connection

The communication channel starts with VM configuration in [`VPhoneVirtualMachine.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneVirtualMachine.swift). When building the `VZVirtualMachineConfiguration`, the code adds a `VZVirtioSocketDeviceConfiguration` to the socket devices array:

```swift
// VPhoneVirtualMachine.swift around line 246
let socketDevice = VZVirtioSocketDeviceConfiguration()
configuration.socketDevices = [socketDevice]

```

Once the VM is running, [`VPhoneAppDelegate.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneAppDelegate.swift) retrieves the actual `VZVirtioSocketDevice` from the VM's `socketDevices` and passes it to `VPhoneControl`. The `connect(device:)` method initiates the vsock connection:

```swift
func connect(device: VZVirtioSocketDevice) {
    // Stores weak reference: private weak var device: VZVirtioSocketDevice?
    device.connect(toPort: vsockPort) // vsockPort = 1337
}

```

The connection result is stored as a `VZVirtioSocketConnection` in `private var connection: VZVirtioSocketConnection?`.

## Handshake and Protocol Negotiation

After the TCP-like socket connects, `VPhoneControl` performs a mandatory handshake. The host sends a JSON "hello" message containing:

- `"v"` — protocol version
- `"t"` — message type (defined in lines 10-12 of [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift))
- Optional SHA-256 hash of the signed `vphoned` binary

The message format uses a 4-byte big-endian length prefix followed by UTF-8 JSON:

```

[uint32 length][UTF-8 JSON payload]

```

The guest responds with its name, capabilities, IP address, iOS version, and a `need_update` flag. This response enables **capability negotiation** — the host checks the capabilities list (e.g., `"touch"`, `"accessibility_tree"`) to determine which features to enable, such as `useGuestTouchInjection`.

If `need_update` is true, the host initiates an auto-update before proceeding with normal operations.

## Length-Prefixed JSON Protocol

All subsequent communication follows the same framing discipline implemented in `writeMessage(fd:dict:)` and `readMessage(fd:)`:

- **Writing**: Serialize JSON, prepend 4-byte big-endian length, write to socket
- **Reading**: Read 4 bytes as length, then read exactly that many bytes as JSON payload

This design supports both fire-and-forget commands and full request-response flows.

## Request-Response Flow with Async/Await

For commands requiring replies, `VPhoneControl` implements a complete async request-response system:

1. **Request generation**: `sendRequest(_:)` generates a unique `nextRequestId`, stores a callback in `pendingRequests`, arms a timeout timer, and writes the length-prefixed JSON
2. **Response matching**: `startReadLoop(fd:attemptToken:)` runs on a background thread, parsing incoming messages and matching them to pending request IDs
3. **Callback delivery**: Matched responses invoke their stored closures on the main queue
4. **Binary payloads**: Responses may include inline binary data (file contents, clipboard images) read via `readFully(fd:buf:count:)` after the JSON header

```swift
// Example: Ping with automatic request-response handling
Task {
    try await control.sendPing()  // Generates request ID, awaits response
}

```

## Auto-Update Mechanism

When the guest reports `need_update`, the host streams the signed `vphoned` binary through the same socket channel:

1. Host sends special `update` JSON header via `pushUpdate(fd:)`
2. Host writes raw binary bytes of the new `vphoned` executable
3. Guest acknowledges update completion
4. Host resumes `startReadLoop` for normal operation

This ensures the guest daemon stays synchronized with the host CLI version without manual intervention.

## Reconnection and Error Handling

On connection failure, `scheduleReconnect` delays and retries automatically. All pending requests in `pendingRequests` are failed with `ControlError.notConnected` to prevent memory leaks and hanging async operations.

## Practical Usage Examples

```swift
import VPhoneCore

// Initialize control for a VM variant
let control = VPhoneControl(variant: .regular)

// Connect to running VM's vsock device
if let socket = vm.virtualMachine.socketDevices.first as? VZVirtioSocketDevice {
    control.connect(device: socket)  // Triggers handshake + read loop
}

// Asynchronous commands with automatic response handling
Task {
    // Simple ping to verify connectivity
    try await control.sendPing()
    
    // List guest filesystem
    let entries = try await control.listFiles(path: "/var/mobile/Documents")
    
    // Install IPA with automatic vphoned update if needed
    let result = try await control.installIPA(
        localURL: URL(fileURLWithPath: "/path/to/app.ipa")
    )
}

// Fire-and-forget touch injection (no response expected)
control.sendTouch(phase: 0, x: 0.5, y: 0.3)  // touch down
control.sendTouch(phase: 1, x: 0.6, y: 0.4)  // touch move
control.sendTouch(phase: 3, x: 0.6, y: 0.4)  // touch up

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) | Core client: socket handling, handshake, `sendRequest(_:)`, `startReadLoop(fd:attemptToken:)`, auto-update |
| [`VPhoneVirtualMachine.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneVirtualMachine.swift) | VM configuration, `VZVirtioSocketDeviceConfiguration` setup around line 246 |
| [`VPhoneAppDelegate.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneAppDelegate.swift) | Bridges running VM to `VPhoneControl.connect(device:)` |
| [`VPhoneCameraServer.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneCameraServer.swift) | Secondary component using same vsock channel for camera streaming |
| [`research/VPhoneVirtualMachineRefactored.swift`](https://github.com/Lakr233/vphone-cli/blob/main/research/VPhoneVirtualMachineRefactored.swift) | Design documentation for vsock architecture |

## Summary

- **Transport**: Virtual socket (vsock) via `VZVirtioSocketDevice` on port 1337
- **Framing**: 4-byte big-endian length prefix + UTF-8 JSON payload
- **Handshake**: Version exchange, capability negotiation, SHA-256 hash verification
- **Pattern**: Async request-response with unique IDs, timeouts, and background read loop
- **Features**: Automatic `vphoned` binary updates, binary payload streaming, graceful reconnection
- **Entry point**: `VPhoneControl.connect(device:)` in [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift)

## Frequently Asked Questions

### What port does vphone-cli use for vsock communication?

The host CLI uses **port 1337** defined as `vsockPort` in [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift). This is passed to `device.connect(toPort: 1337)` when establishing the `VZVirtioSocketConnection`.

### How does vphone-cli handle binary data transfer over the vsock?

Binary data follows the JSON header in the same connection. After parsing the JSON response, the code calls `readFully(fd:buf:count:)` to read the exact byte count specified in the message. This is used for file transfers, clipboard images, and the `vphoned` auto-update mechanism.

### What happens if the guest `vphoned` version mismatches the host?

During handshake, the guest sends a `need_update` flag if its binary hash differs from the host's expected value. The host then enters `pushUpdate(fd:)`, streams the signed binary over the socket, waits for acknowledgment, and resumes normal operation without restarting the VM.

### Can multiple commands run simultaneously over the same vsock?

Yes. The `sendRequest(_:)` method generates unique request IDs and stores callbacks in `pendingRequests`. The background read loop matches incoming responses to pending requests by ID, allowing concurrent async operations without blocking the socket.