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

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/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:

{ "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 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 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 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) encapsulates all protocol details. Below are practical examples demonstrating the API surface.

Creating a Control Client

// 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

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

// 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

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

// 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

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/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/sources/vphone-cli/VPhoneHostControl.swift) VM lifecycle integration Higher-level wrapper tying VPhoneControl to VM management
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 (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 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/sources/vphone-cli/VPhoneControl.swift) and the guest vphoned script. Both files include inline comments describing message types and expected fields.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →