# Herdr Wire Protocol Version Negotiation: How the Handshake Works

> Learn how Herdr wire protocol handles version negotiation with its explicit binary handshake. Discover the client Hello message and server Welcome response.

- Repository: [Can Celik/herdr](https://github.com/ogulcancelik/herdr)
- Tags: internals
- Published: 2026-05-31

---

**Herdr uses an explicit binary handshake where the client sends a protocol version in a `Hello` message, and the server validates it against its own `PROTOCOL_VERSION`, rejecting mismatches with a descriptive error in the `Welcome` response.**

The Herdr terminal multiplexer (`ogulcancelik/herdr`) implements a strict, integer-based versioning scheme in its binary wire protocol to prevent wire format incompatibilities between clients and the server. Understanding this negotiation flow is essential for developers building third-party clients or debugging connection failures in multi-version deployments.

## The Version Handshake Flow

Herdr’s version negotiation follows a deterministic three-step handshake that occurs immediately after the Unix socket connection is established.

### Client Initiates with Hello

The client must send a `ClientMessage::Hello` as its first message. This struct includes a `version: u32` field representing the protocol version the client implements, alongside terminal dimensions and encoding preferences.

According to the source code in [`src/protocol/wire.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/protocol/wire.rs) (lines 67–85), the `Hello` message structure is defined as:

```rust
let hello = ClientMessage::Hello {
    version: PROTOCOL_VERSION,          // client's view of the protocol
    cols: 80,
    rows: 24,
    cell_width_px: 8,
    cell_height_px: 16,
    requested_encoding: RenderEncoding::SemanticFrame,
    keybindings: ClientKeybindings::Server,
    launch_mode: ClientLaunchMode::App,
};
protocol::write_message(&mut stream, &hello)?;

```

### Server Reads and Validates

The server reads this initial message using generic framing functions in the client transport module. In [`src/server/client_transport.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/server/client_transport.rs) (lines 72–84), the server extracts the `version` field and passes it to `protocol::check_client_version`.

The validation logic resides in [`src/protocol/wire.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/protocol/wire.rs) (lines 24–32 and 624–642). Unlike protocols that support backward compatibility, Herdr enforces an **exact match policy**:

- **Version 0** (pre-persistence clients) is always rejected
- **Exact match** with the server’s `PROTOCOL_VERSION` returns `VersionCheck::Compatible`
- **Client version newer** than the server returns an incompatible error
- **Client version older** than the server returns an incompatible error

### The Welcome Response

Based on the validation result, the server constructs a `ServerMessage::Welcome` response:

- **Compatible**: The server proceeds with initialization and sends a `Welcome` message with `error: None` ([`src/server/client_transport.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/server/client_transport.rs), lines 35–41).
- **Incompatible**: The server immediately sends a `Welcome` containing the server’s `PROTOCOL_VERSION` but sets `error: Some(reason)`, then closes the connection ([`src/server/client_transport.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/server/client_transport.rs), lines 85–93).

The server-side handling appears as follows:

```rust
match protocol::check_client_version(version) {
    protocol::VersionCheck::Compatible => {
        // continue with normal initialization
    }
    protocol::VersionCheck::Incompatible(reason) => {
        // Send a rejecting Welcome and close the connection
        let welcome = ServerMessage::Welcome {
            version: PROTOCOL_VERSION,
            encoding: RenderEncoding::SemanticFrame,
            error: Some(reason),
        };
        let _ = protocol::write_message(&mut stream, &welcome);
        return Ok(());
    }
}

```

## Version Checking Implementation

The `check_client_version` function in [`src/protocol/wire.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/protocol/wire.rs) implements the negotiation policy with explicit error messages:

```rust
pub fn check_client_version(client_version: u32) -> VersionCheck {
    if client_version == 0 {
        return VersionCheck::Incompatible(
            "pre-persistence client (version 0) is not supported".to_owned(),
        );
    }

    if client_version == PROTOCOL_VERSION {
        VersionCheck::Compatible
    } else if client_version < PROTOCOL_VERSION {
        VersionCheck::Incompatible(format!(
            "client version {client_version} is older than server version {PROTOCOL_VERSION}; \
             please upgrade your herdr client"
        ))
    } else {
        VersionCheck::Incompatible(format!(
            "client version {client_version} is newer than server version {PROTOCOL_VERSION}; \
             please upgrade the herdr server"
        ))
    }
}

```

This design ensures **no silent fallbacks** occur. By rejecting all mismatches, Herdr avoids subtle bugs that could arise from differing wire formats between protocol versions.

## Key Source Files

- **[`src/protocol/wire.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/protocol/wire.rs)**: Defines `PROTOCOL_VERSION`, the `ClientMessage::Hello` structure, the `VersionCheck` enum, and the `check_client_version` function.
- **[`src/server/client_transport.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/server/client_transport.rs)**: Handles the initial `Hello` read, invokes version validation, and manages the `Welcome` response lifecycle.

## Summary

- Herdr’s wire protocol requires clients to begin with a `Hello` message containing a `version` field.
- The server validates this against its internal `PROTOCOL_VERSION` using `check_client_version()` in [`src/protocol/wire.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/protocol/wire.rs).
- Only exact version matches are accepted; version 0, older, and newer clients are explicitly rejected.
- Incompatible connections receive a `Welcome` message with an error description before the server closes the socket.
- This strict negotiation prevents wire format mismatches by failing fast during the handshake phase.

## Frequently Asked Questions

### What happens if the client version is newer than the server?

The server rejects the connection with a `VersionCheck::Incompatible` error containing the message *"client version X is newer than server version Y; please upgrade the herdr server"*. The client receives this message in the `error` field of the `Welcome` response and should terminate with a clear upgrade instruction to the user.

### Why does Herdr reject version 0 clients specifically?

Version 0 represents pre-persistence legacy clients that predate Herdr’s current storage model. According to [`src/protocol/wire.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/protocol/wire.rs), these clients are unconditionally rejected with the message *"pre-persistence client (version 0) is not supported"* to prevent data corruption or undefined behavior with the current server architecture.

### Where is the protocol version constant defined in the source code?

The `PROTOCOL_VERSION` constant is defined in [`src/protocol/wire.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/protocol/wire.rs) alongside the `check_client_version` function (lines 24–32 and 624–642). This file serves as the single source of truth for the wire format version used by both the client and server binaries.

### How can a client detect that version negotiation failed?

The client reads the `ServerMessage::Welcome` response after sending its `Hello`. If the `error` field is `Some(reason)`, the handshake failed and the connection will close. The client should display the error string to the user, which indicates whether the mismatch is due to an outdated client, outdated server, or unsupported legacy version.