Files
clipsync/docs/protocol-v1.md
T

134 lines
3.4 KiB
Markdown
Raw Normal View History

2026-08-27 15:38:55 +02:00
# clipsync Protocol v1 (MVP)
This document defines an implementation-friendly first protocol for clipboard sync.
## Transport
- Discovery: UDP broadcast on port `45821`
- Session: TCP on port `45821`
- Payload encoding: UTF-8 JSON frames
- Frame format on TCP: 4-byte big-endian length prefix + JSON bytes
## Versioning
Each message includes:
- `version`: protocol version string, initially `"1"`
- `type`: message type
- `request_id`: unique string per request/response pair where relevant
- `device_id`: sender stable ID
- `timestamp_ms`: unix epoch milliseconds
## Discovery message (UDP)
`DISCOVERY_ANNOUNCE`
```json
{
"version": "1",
"type": "DISCOVERY_ANNOUNCE",
"device_id": "desktop-uuid",
"device_name": "workstation",
"platform": "linux|windows",
"tcp_port": 45821,
"capabilities": ["clipboard:text"],
"timestamp_ms": 0
}
```
Android listens for these packets and shows peers.
## Pairing flow (TCP)
### 1) HELLO
Both peers exchange:
- protocol version
- device info
- ephemeral nonce
### 2) PAIR_REQUEST
Unpaired side sends pairing request including:
- public key
- pairing method (`"pin"` or `"qr"`)
### 3) PAIR_CHALLENGE
Responder sends short code metadata (or QR payload hash) and challenge nonce.
### 4) PAIR_CONFIRM
Requester returns challenge response.
### 5) PAIR_ACCEPT
Responder confirms trust and stores remote public key.
After `PAIR_ACCEPT`, both devices persist:
- `peer_device_id`
- `peer_public_key`
- `paired_at`
## Session messages
### `CLIP_UPDATE`
```json
{
"version": "1",
"type": "CLIP_UPDATE",
"request_id": "req-123",
"device_id": "android-uuid",
"timestamp_ms": 0,
"origin_device_id": "android-uuid",
"content_type": "text/plain",
"encoding": "utf-8",
"content_hash": "sha256:...",
"content": "clipboard text",
"sequence": 42
}
```
Rules:
- Only sync `text/plain` in MVP.
- Ignore messages with known `content_hash` from recent history.
- Ignore message if `origin_device_id` equals local device id (echo prevention).
### `ACK`
Acknowledges accepted `request_id`.
### `ERROR`
Contains error code and message (`UNPAIRED`, `UNSUPPORTED_VERSION`, `INVALID_MESSAGE`, `RATE_LIMITED`).
### `PING` / `PONG`
Keepalive every 15 seconds. Drop and reconnect after timeout.
## Security baseline
- Reject non-paired peers for clipboard writes.
- Encrypt session channel (TLS or Noise).
- Enforce max payload size (e.g., 1 MiB for MVP text).
- Apply rate limits per peer.
## Current daemon behavior (implemented)
- Server sends `HELLO` immediately on TCP connect.
- First client message must be `HELLO` (else `ERROR: INVALID_MESSAGE`).
- After `HELLO`, server accepts either:
- `PAIR_REQUEST` and responds with `PAIR_ACCEPT` (while persisting peer trust), or
- `CLIP_UPDATE` for already-paired peers.
- `CLIP_UPDATE` from unknown peers is rejected with `ERROR: UNPAIRED`.
- While connected, server polls local clipboard and emits `CLIP_UPDATE` to the connected paired peer when local text changes.
- Incoming paired `CLIP_UPDATE` (`content_type = text/plain`) is applied to local OS clipboard.
- Inbound `CLIP_UPDATE` messages are deduplicated using `content_hash` (with a fallback hash for missing values).
- Inbound `CLIP_UPDATE` messages are rate-limited per peer; over-limit updates return `ERROR: RATE_LIMITED`.
- Keepalive is implemented: server sends periodic `PING`, responds to inbound `PING` with `PONG`, and drops idle sessions after timeout.