# vphone-cli Protocol: How the Host and Guest Communicate Over vsock

> Discover the vphone-control protocol, a custom length-prefixed JSON format used by vphone-cli for secure communication over vsock. Learn how host and guest exchange messages efficiently.

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

---

**vphone-cli uses a custom length-prefixed JSON protocol called "vphone-control protocol" over vsock sockets.** Each message is framed with a 4-byte big-endian length field followed by UTF-8 encoded JSON containing protocol version (`"v"`), message type (`"t"`), and an optional request ID (`"id"`).

The vphone-cli project provides command-line control of iOS virtual machines running on Apple Silicon Macs. All host-side orchestration relies on a lightweight, bidirectional communication channel with an in-guest daemon. This article explains the exact wire format, message structure, and implementation details found in the Lakr233/vphone-cli source code.

## vphone-control Protocol Overview

The protocol runs over **vsock** (virtio-socket), a paravirtualized socket interface that allows the host to communicate directly with guest VMs without network stack overhead. The guest daemon **vphoned** listens on vsock port 1337 and speaks the same framing format.

### Transport and Framing

Every message follows a strict binary framing scheme defined in [[`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift)](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift#L42-L45) (lines 42-45):

| Field | Size | Description |
|-------|------|-------------|
| Length | 4 bytes | uint32 big-endian, total JSON payload size |
| JSON | N bytes | UTF-8 encoded JSON object |

The host-side `VPhoneControl` class writes the length prefix before each JSON payload. The guest reads exactly `length` bytes, parses the JSON, and responds using identical framing.

### Required JSON Fields

Every JSON message contains at least:

- **`"v"`** — Protocol version (currently `1`)
- **`"t"`** — Message type string (e.g., `"hello"`, `"ping"`, `"file_list"`)
- **`"id"`** — Request identifier (optional for notifications, required for requests awaiting response)

## Protocol Handshake and Message Flow

### Initial Handshake

Connection establishment follows a simple capability exchange. The host sends:

```json
{ "v": 1, "t": "hello", "id": 0 }

```

The guest replies with its capabilities, VM name, assigned IP address, and other metadata. This handshake validates protocol compatibility before further operations proceed ([[`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) lines 75-84](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift#L75-L84)).

### Request-Response Correlation

For operations requiring acknowledgment, the host includes a unique `"id"` field. The guest echoes this ID in its response, enabling the host to match asynchronous replies to pending calls. The `VPhoneControl` implementation maintains an internal dictionary of continuations indexed by request ID ([[`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) lines 52-64](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift#L52-L64)).

### Binary Payload Handling

Certain message types transfer raw binary data after the JSON header:

- **`"file_data"`** — File upload/download content
- **`"clipboard_get"`** — Clipboard image data

For these messages, the JSON header includes a `size` field indicating subsequent binary bytes. The receiver reads exactly `size` bytes from the stream after parsing the JSON ([[`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) lines 95-108](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift#L95-L108)).

## Implementing vphone-control in Swift

The `VPhoneControl` class in [[`sources/vphone-cli/VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift)](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift) encapsulates all protocol details. Below are practical examples demonstrating the API surface.

### Creating a Control Client

```swift
// Initialize for regular VM variant (vs. debug/jailbreak variants)
let control = VPhoneControl(variant: .regular)

// Connect to the virtio-socket device provided by the VM configuration
try control.connect(device: virtioSocketDevice)

```

### Sending a Ping Request

```swift
Task {
    do {
        try await control.sendPing()
        print("Guest responded to ping")
    } catch {
        print("Ping failed: \(error)")
    }
}

```

The `sendPing()` method internally constructs `{"v":1,"t":"ping","id":<unique>}`, writes the length-prefixed frame, and awaits the matching response.

### File System Operations

```swift
// List directory contents
Task {
    do {
        let entries = try await control.listFiles(path: "/var/mobile")
        for entry in entries {
            let name = entry["name"] ?? "unknown"
            let type = entry["type"] ?? "unknown"
            print("\(name) (\(type))")
        }
    } catch {
        print("List failed: \(error)")
    }
}

```

### File Upload with Binary Payload

```swift
Task {
    let textData = Data("Hello from host".utf8)
    try await control.uploadFile(
        path: "/tmp/greeting.txt",
        data: textData
    )
}

```

The `uploadFile` implementation sends a JSON header with file metadata, then streams the raw bytes without additional framing.

### Touch Event Injection

```swift
// Touch down at screen center
control.sendTouch(phase: 0, x: 0.5, y: 0.5)

// Touch up
control.sendTouch(phase: 3, x: 0.5, y: 0.5)

```

Phase values follow iOS touch conventions: `0` for down, `1` for move, `3` for up. Coordinates are normalized 0.0-1.0 relative to screen dimensions.

### Clipboard Operations

```swift
Task {
    let clipboard = try await control.clipboardGet()
    
    if let text = clipboard.text {
        print("Text: \(text)")
    }
    
    if let imageData = clipboard.imageData {
        // Process PNG/JPEG bytes
    }
}

```

When image data is present, the JSON `size` field indicates additional bytes to read after the header.

## Protocol Implementation Files

| File | Purpose | Key Components |
|------|---------|---------------|
| [[`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift)](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift) | Core protocol client | Framing, handshake, request/response matching, binary payload handling |
| [[`VPhoneHostControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneHostControl.swift)](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneHostControl.swift) | VM lifecycle integration | Higher-level wrapper tying `VPhoneControl` to VM management |
| [`scripts/vphoned`](https://github.com/Lakr233/vphone-cli/blob/main/scripts/vphoned) | Guest daemon | Python implementation parsing the same length-prefixed JSON format |

The guest daemon mirrors the host's framing logic in Python, enabling bidirectional communication without external dependencies beyond standard library `socket` and `json` modules.

## Comparison with Alternative Approaches

The vphone-control protocol design reflects deliberate trade-offs:

- **vsock vs. TCP/IP** — vsock avoids network configuration, firewall rules, and routing tables. VMs need no assigned IP for control operations.
- **Length-prefixed vs. delimiter-separated** — Fixed 4-byte length fields simplify buffer management and eliminate escaping concerns inherent in newline-delimited protocols.
- **JSON vs. binary serialization** — Human-readable messages aid debugging; the overhead is negligible for control-plane traffic. Binary payloads bypass JSON for bulk data transfer.

## Summary

- **Transport**: vsocket (virtio-socket) on port 1337
- **Framing**: 32-bit big-endian length prefix + UTF-8 JSON
- **Core fields**: `"v"` (version), `"t"` (type), `"id"` (correlation ID)
- **Binary data**: Additional size field in JSON, raw bytes follow header
- **Handshake**: `"hello"` message exchanges capabilities on connect
- **Implementation**: [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) (host), `vphoned` Python script (guest)

## Frequently Asked Questions

### What happens if the protocol version mismatches?

The guest validates the `"v"` field in every message. Version mismatches trigger an error response with `"t":"error"` and descriptive message, causing the host-side Swift code to throw a `VPhoneError.protocolMismatch`. The connection typically terminates requiring reconnection.

### Can the protocol run over networks instead of vsock?

No. The current implementation hardcodes `VSOCK` address family usage in both [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) and the guest `vphoned` script. Network transparency would require substituting TCP sockets and adapting the connection establishment code.

### How large can binary payloads be?

The 32-bit length field theoretically permits 4 GB payloads. Practical limits depend on guest memory availability and vsocket buffer configurations. The reference implementation streams large files in manageable chunks rather than single massive messages.

### Is the protocol documented outside the source code?

No formal specification exists. The protocol definition is the implementation in [[`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift)](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift) and the guest [`vphoned`](https://github.com/Lakr233/vphone-cli/blob/main/scripts/vphoned) script. Both files include inline comments describing message types and expected fields.