> ## Documentation Index
> Fetch the complete documentation index at: https://shareclick.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Wire protocol

> ShareClick's two-channel wire format: postcard-encoded input packets over UDP and length-prefixed bulk messages over TCP.

All types live in `crates/protocol/src/lib.rs`. Encoding is
[postcard](https://docs.rs/postcard) — a compact, `serde`-based binary format
chosen because it's small and fast (important on the input hot path).

<Note>
  **Versioning:** `PROTOCOL_VERSION` is currently `1`. Bump it on any breaking
  wire change. The handshake exchanges versions so peers can refuse mismatches.
</Note>

## Two channels

| Channel | Transport | Carries                                     | Type                       |
| ------- | --------- | ------------------------------------------- | -------------------------- |
| Input   | UDP       | mouse/keys, control signals, latency probes | `InputPacket` → `InputMsg` |
| Bulk    | TCP       | clipboard, files, handshake                 | `BulkMsg`                  |

## Input channel

### `InputPacket`

```rust theme={null}
struct InputPacket { seq: u32, msg: InputMsg }
```

`seq` is a per-sender monotonic counter. The receiver drops any `Events` packet
whose seq is ≤ the highest seen — duplicate/straggler rejection **without**
blocking, the reason we use UDP. Control/ping messages bypass this check.

### `InputMsg`

```rust theme={null}
enum InputMsg {
    Events(Vec<InputEvent>),           // a coalesced tick of input
    Enter { edge: Edge, entry: f32 },  // server → client: you have control
    Leave,                             // either direction: control returns to server
    Ping { nonce: u64, echo_nanos: u64 },
    Pong { nonce: u64, echo_nanos: u64 },
}
```

### `InputEvent`

```rust theme={null}
enum InputEvent {
    MouseMove { dx: i32, dy: i32 },   // RELATIVE motion (see Decision #4)
    MouseButton { button: MouseButton, pressed: bool },
    Scroll { dx: f32, dy: f32 },
    Key { key: Key, pressed: bool },  // portable key, see below
}
```

Events are **coalesced per capture tick** into one `Events(Vec<..>)` so a high
mouse polling rate doesn't flood the network or cause "jumpiness" when polling
exceeds display refresh.

### Portable `Key`

macOS and Windows use different raw keycodes, so we never send a raw scancode.
`keymap.rs` translates the native key to a portable `Key` enum on capture and
back on injection (the same approach Synergy/Deskflow use). Unmappable keys
become `Key::Unknown(u32)` and are dropped on injection.

## Bulk channel

### Framing

Each frame is a `u32` big-endian length prefix + the (optionally encrypted)
postcard bytes. Max frame size is **64 MiB** (guards against a hostile peer
forcing a huge allocation).

### `BulkMsg`

```rust theme={null}
enum BulkMsg {
    Hello { version: u16, name: String, screen: (u32,u32) },  // reserved
    Welcome { version: u16, name: String },                   // reserved
    Clipboard(ClipboardData),
    FileBegin { id: u64, name: String, size: u64 },
    FileChunk { id: u64, offset: u64, data: Vec<u8> },
    FileEnd { id: u64 },
    Heartbeat,
}

enum ClipboardData {
    Text(String),
    Image { width: u32, height: u32, rgba: Vec<u8> },  // raw RGBA, no codec
}
```

### File transfer

`FileBegin` → many `FileChunk` (64 KiB each, written at `offset`) → `FileEnd`.
Offsets make it robust and resumable-in-principle. The receiver sanitizes the
filename (strips path components) to prevent path-traversal, and writes into
`./received/`.

## Encryption framing

See [Security](/concepts/security) for the crypto design. Framing specifics:

* **Bulk (TCP):** the record is `seal(counter, aad=[], plaintext)`. The counter
  is *implicit* — TCP is ordered, so both peers keep in-sync send/recv counters
  starting at 0 and never transmit them.
* **Input (UDP):** the wire is `seq(4 bytes, cleartext) || seal(seq, aad=seq,
  ciphertext)`. The sequence number doubles as the nonce counter and is bound in
  as associated data, so it can't be tampered with. Packets that fail
  authentication are silently dropped.

## Adding a new message

<Steps>
  <Step title="Add the variant">
    To `InputMsg` / `BulkMsg` / `InputEvent` as appropriate.
  </Step>

  <Step title="Handle both sides">
    Send and receive in `run.rs`, `transport.rs`, `bulk.rs`.
  </Step>

  <Step title="Bump the version if needed">
    If it changes the meaning of existing bytes, bump `PROTOCOL_VERSION` and note
    the change here.
  </Step>

  <Step title="Add a round-trip test">
    See the `protocol` tests for the pattern.
  </Step>
</Steps>
