# How the Guest Daemon vphoned Communicates with the Host Over VSock Using a Length-Prefixed JSON Protocol

> Discover how vphoned communicates with the host over VSock using a length-prefixed JSON protocol. Learn about packet structure and message exchange.

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

---

**The guest daemon `vphoned` communicates with the host by opening a VSock listener on port 1337 and exchanging length-prefixed JSON messages over AF_VSOCK, where each packet consists of a 4-byte big-endian length header followed by UTF-8 encoded JSON payloads containing protocol version, message type, and command-specific data.**

The `vphoned` guest daemon runs inside the iOS virtual machine as an Objective-C launch daemon, enabling bidirectional control between the host and guest in the Lakr233/vphone-cli project. It implements a robust length-prefixed JSON protocol over AF_VSOCK to handle commands ranging from HID input to file transfers. Understanding this communication architecture reveals how the host-side [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) establishes reliable command and control channels with the virtualized iOS environment.

## VSock Connection Architecture

The communication stack relies on the **AF_VSOCK** address family, which provides socket communication between virtual machines and their hosts without network-level overhead.

### Guest-Side Listener Configuration

Inside the iOS VM, `vphoned` (implemented in `scripts/vphoned/vphoned.m`) initializes a VSock listener bound to **port 1337** (`VPHONED_PORT`). The daemon uses standard BSD socket APIs with `AF_VSOCK` to accept incoming connections from the hypervisor. When a host connects, `vphoned` spawns a client handler thread running `handle_client` to manage the persistent connection.

### Host-Side Socket Device

On the host side, [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) (located in [`sources/vphone-cli/VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneControl.swift)) creates a `VZVirtioSocketDevice` attachment and connects to the guest's port 1337. This establishes the bidirectional byte stream that carries the length-prefixed JSON protocol between the macOS host process and the virtualized iOS guest.

## Length-Prefixed JSON Protocol Format

Both endpoints use an identical framing strategy referred to as the "vphone-control" protocol, ensuring message boundary integrity over the byte-stream VSock connection.

### Message Framing Structure

Every message follows a strict **length-prefixed binary format**:

1. **Header**: 4 bytes representing the payload length as a **uint32 in big-endian** (network) order
2. **Payload**: UTF-8 encoded JSON data exactly matching the header's length specification

The C implementation in [`scripts/vphoned/vphoned_protocol.h`](https://github.com/Lakr233/vphone-cli/blob/main/scripts/vphoned/vphoned_protocol.h) provides `vp_write_message`, which serializes an `NSDictionary` to JSON and prepends the length header:

```objc
// vphoned_protocol.h – writeMessage implementation
BOOL vp_write_message(int fd, NSDictionary *dict);
// Serializes dict to JSON, writes 4-byte big-endian length, then the UTF-8 bytes

```

The Swift counterpart in [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) mirrors this exactly:

```swift
// VPhoneControl.swift – writeMessage
private func writeMessage(fd: Int32, dict: [String: Any]) -> Bool {
    let json = try? JSONSerialization.data(withJSONObject: dict)
    var header = UInt32(json!.count).bigEndian
    // Write header (4 bytes) followed by JSON payload
}

```

### JSON Payload Schema

Every JSON message contains mandatory fields:

- **`"v"`**: Protocol version (currently `1`)
- **`"t"`**: Message type string (e.g., `"hello"`, `"hid"`, `"file_get"`, `"update"`)
- **`"id"`** (optional): Hexadecimal request identifier echoed in responses for correlation

Additional fields depend on the specific command type.

## Handshake and Capability Negotiation

Upon connection establishment, the host initiates a handshake by sending a `"hello"` message. This message may include a `bin_hash` field containing the SHA-256 hash of the host's `vphoned` binary to enable automatic guest updates.

The guest processes this in `vphoned.m` within the `handle_client` function (lines 86-103):

```objc
// vphoned.m – handshake processing
NSDictionary *hello = vp_read_message(fd);
// Verify protocol version "v"
NSMutableDictionary *helloResp = @{
    @"v": @PROTOCOL_VERSION,
    @"t": @"hello",
    @"name": @"vphoned",
    @"caps": caps  // Array of capability strings
}.mutableCopy;
// Report IPv4 address and check if update needed
if (![hostHash isEqualToString:localHash]) {
    helloResp[@"need_update"] = @YES;
}
vp_write_message(fd, helloResp);

```

If the binary hashes differ, the guest sets `"need_update": true`, triggering the host to push a new binary via the update flow.

## Command Exchange and Dispatch

Following the handshake, the connection enters a command loop where the host sends operation requests and the guest returns structured responses.

### Host Request Construction

In [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift), host commands are constructed as dictionaries with protocol metadata and command parameters. For example, sending an HID key press:

```swift
// VPhoneControl.swift – HID command construction
func sendHIDPress(page: UInt32, usage: UInt32) {
    nextRequestId += 1
    let msg: [String: Any] = [
        "v": Self.protocolVersion,
        "t": "hid",
        "id": String(nextRequestId, radix: 16),
        "page": page,
        "usage": usage
    ]
    guard let fd = connection?.fileDescriptor,
          writeMessage(fd: fd, dict: msg) else { return }
}

```

### Guest Command Dispatch

The guest's `handle_client` function in `vphoned.m` loops on `vp_read_message(fd)`, dispatching based on the `"t"` field:

```objc
// vphoned.m – command dispatch (lines 88-122)
NSString *type = msg[@"t"];
NSString *reqId = msg[@"id"];

if ([type isEqualToString:@"hid"]) {
    uint32_t page = [msg[@"page"] unsignedIntValue];
    uint32_t usage = [msg[@"usage"] unsignedIntValue];
    if (msg[@"down"] != nil) {
        vp_hid_key(page, usage, [msg[@"down"] boolValue]);
    } else {
        vp_hid_press(page, usage);
    }
    return vp_make_response(@"ok", reqId);
}
else if ([type isEqualToString:@"touch"]) {
    // Process touch coordinates
    return vp_make_response(@"ok", reqId);
}

```

The helper `vp_make_response` constructs standardized response dictionaries containing the echoed `"id"` and a status string.

## Binary Data Transfer Flow

For operations requiring raw binary data (file transfers, clipboard images, or binary updates), the protocol extends the JSON header with a `"size"` field indicating the subsequent byte count.

### Host-Side Binary Push

When pushing an update, the host writes the JSON header followed immediately by raw bytes:

```swift
// VPhoneControl.swift – binary update transmission
private func pushUpdate(fd: Int32) {
    guard let data = guestBinaryData else { return }
    nextRequestId += 1
    let header: [String: Any] = [
        "v": Self.protocolVersion,
        "t": "update",
        "id": String(nextRequestId, radix: 16),
        "size": data.count
    ]
    writeMessage(fd: fd, dict: header)
    // Stream raw binary after the JSON header
    data.withUnsafeBytes { buf in
        Self.writeFully(fd: fd, buf: buf.baseAddress!, count: data.count)
    }
}

```

### Guest-Side Binary Reception

The guest reads the size from the JSON header, then uses `vp_read_fully` to consume the exact byte count:

```objc
// vphoned.m – receive_update (lines 42-73)
static BOOL receive_update(int fd, NSUInteger size) {
    // Create temporary file for atomic replacement
    while (remaining > 0) {
        size_t chunk = remaining < sizeof(buf) ? remaining : sizeof(buf);
        if (!vp_read_fully(fd, buf, chunk)) {
            // Handle read error
            return NO;
        }
        write(tmp_fd, buf, chunk);
        remaining -= chunk;
    }
    // chmod +x, rename to CACHE_PATH, and restart daemon
}

```

## Error Handling and Request Timeouts

Both endpoints implement robust error reporting using an `"err"` message type containing a descriptive `"msg"` field. The host tracks pending requests in a dictionary keyed by the hex `"id"`, using `armRequestTimeout` to fail requests that exceed the timeout threshold without receiving a response.

On the guest side, `vp_read_message` and `vp_write_message` return Boolean status codes that trigger connection cleanup and thread termination when socket errors occur, ensuring the daemon remains available for new host connections.

## Summary

- **Transport**: The guest daemon opens an AF_VSOCK listener on port 1337, while the host connects via `VZVirtioSocketDevice` to establish a raw byte stream.
- **Framing**: All messages use 4-byte big-endian length prefixes followed by UTF-8 JSON payloads, implemented in [`vphoned_protocol.h`](https://github.com/Lakr233/vphone-cli/blob/main/vphoned_protocol.h) and [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift).
- **Structure**: Every JSON message includes `"v"` (version), `"t"` (type), and optional `"id"` (correlation ID) fields.
- **Handshake**: The `hello` exchange negotiates capabilities and triggers automatic binary updates via SHA-256 hash comparison.
- **Binary transfers**: Commands include a `"size"` field after which raw bytes stream directly over the socket without additional framing.
- **Reliability**: Request timeouts, error message types, and full-read helper functions (`vp_read_fully`, `writeFully`) ensure data integrity across the virtualized boundary.

## Frequently Asked Questions

### What port does vphoned use for VSock communication?

The guest daemon listens on **port 1337** (`VPHONED_PORT`) over the AF_VSOCK address family. The host-side [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift) connects to this specific port to establish the control channel.

### How does the length-prefixed framing prevent message corruption?

Each message begins with a 4-byte big-endian integer specifying the exact byte count of the following JSON payload. This allows the receiver to allocate precise buffers and use blocking reads (`vp_read_fully` or `readFully`) to consume exactly the intended data, preventing buffer overflows or partial message parsing errors common in stream-based protocols.

### What message types does the vphoned protocol support?

The protocol supports types including `"hello"` (handshake), `"hid"` (keyboard/mouse input), `"touch"` (touchscreen events), `"file_get"` and `"file_put"` (file transfer), `"update"` (binary replacement), and `"err"` (error reporting). Each type expects specific additional fields in the JSON dictionary.

### How does the host handle guest binary updates automatically?

During the handshake, the host sends a `bin_hash` field containing its local SHA-256 hash. The guest compares this against its running binary's hash. If they differ, the guest sets `"need_update": true` in the response, prompting the host to transmit a new binary using the `"update"` message type with a `"size"` field, streaming the raw executable bytes immediately after the JSON header.