# 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.