documentation
This commit is contained in:
+86
@@ -0,0 +1,86 @@
|
||||
# clipsync
|
||||
|
||||
LAN clipboard sync between Android and Linux/Windows devices. A device is a
|
||||
participant in a sync mesh; paired devices exchange clipboard changes over an
|
||||
authenticated session.
|
||||
|
||||
## Identity & Trust
|
||||
|
||||
**Device ID**
|
||||
A stable, install-time unique identifier for a device. Immutable for the life
|
||||
of the install and used as the echo/loop key for clipboard updates.
|
||||
_Avoid_: device name, peer id (transient)
|
||||
|
||||
**Identity key**
|
||||
A device's long-term key pair. The public half is exchanged at pairing and
|
||||
pinned in the trust record; it authenticates every session.
|
||||
_Avoid_: public key (ambiguous — could mean ephemeral)
|
||||
|
||||
**Pairing**
|
||||
The one-time process that establishes mutual trust between two devices: an
|
||||
authenticated key-exchange handshake plus verification of a short
|
||||
authentication string derived from the handshake. Produces a pinned trust
|
||||
record on both sides.
|
||||
_Avoid_: linking, connecting
|
||||
|
||||
**SAS (Short Authentication String)**
|
||||
A human-comparable code both devices derive from the handshake transcript. The
|
||||
user confirms both sides show the same string to defeat a man-in-the-middle.
|
||||
_Avoid_: PIN, pairing code, OTP
|
||||
|
||||
**Trust record**
|
||||
The persisted per-peer entry proving a completed pairing: peer device ID,
|
||||
pinned identity public key, paired-at time, SAS-verified flag, display name,
|
||||
last-seen time.
|
||||
_Avoid_: credential, token
|
||||
|
||||
**Peer**
|
||||
A device for which a trust record exists (i.e., already paired).
|
||||
_Avoid_: client, node, endpoint
|
||||
|
||||
**Session**
|
||||
A single authenticated channel between two paired peers over one TCP
|
||||
connection. Re-established on every (re)connect using the pinned identity keys.
|
||||
|
||||
## Sync Propagation
|
||||
|
||||
**Origin device**
|
||||
The device that first produced a clipboard change. Immutable as the update is
|
||||
fanned out; used for echo prevention.
|
||||
_Avoid_: sender (ambiguous — forwarders also send)
|
||||
|
||||
**Sequence**
|
||||
A persistent, monotonic per-device counter tagging each clipboard update.
|
||||
Together with origin device ID it forms the loop-prevention key.
|
||||
_Avoid_: nonce, index
|
||||
|
||||
**Fan-out**
|
||||
The propagation rule: a device sends each local clipboard change to every
|
||||
currently-connected trusted peer.
|
||||
|
||||
**Loop prevention**
|
||||
Dropping a clipboard update when its origin device ID equals the local device
|
||||
ID, or when its (origin device ID, sequence) pair is not newer than the
|
||||
last-seen pair for that peer.
|
||||
|
||||
**Ordering**
|
||||
The deterministic total order `(timestamp_ms, origin device ID)` used to decide
|
||||
which clipboard update wins when changes conflict; bounded by a clock-skew
|
||||
guard.
|
||||
|
||||
## Protocol & Discovery
|
||||
|
||||
**CLIP_UPDATE**
|
||||
A message carrying one clipboard change: origin device ID, content, content
|
||||
type, content hash, sequence, timestamp. Text only in MVP; content type is
|
||||
extensible.
|
||||
|
||||
**Discovery**
|
||||
How unpaired devices find each other: one side announces over UDP and the
|
||||
other listens, with manual IP:port entry as a fallback when announcement is
|
||||
blocked.
|
||||
|
||||
**Clipboard backend**
|
||||
The component that reads and writes the operating-system clipboard on the
|
||||
desktop side; reaches the clipboard through the platform's sanctioned API
|
||||
rather than raw display-server access.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Transport security uses Noise, not TLS
|
||||
|
||||
We secure the session with the Noise protocol (an `XX` handshake) using each
|
||||
device's long-term identity key, rather than mutual TLS with per-device certs.
|
||||
Noise avoids a CA/cert machinery on a peer-to-peer LAN, maps cleanly onto the
|
||||
SAS pairing flow, and is light enough for the Odin daemon and Android
|
||||
(Conscrypt). The pinned trust record and the session key are the *same* key,
|
||||
simplifying the model.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Sync topology is fan-out to all trusted peers
|
||||
|
||||
A clipboard change is sent to every currently-connected paired peer, not just
|
||||
one. The trust store already models a list of peers, and restricting to 1:1
|
||||
later would be a painful product reversal. Fan-out makes the system
|
||||
many-to-many by construction; correctness then rests entirely on loop
|
||||
prevention (see ADR-0003).
|
||||
@@ -0,0 +1,9 @@
|
||||
# Loop prevention keys on (origin device ID, sequence), not content hash
|
||||
|
||||
The original protocol draft used `content_hash` recent-history dedup as the
|
||||
loop breaker. That is wrong for many-to-many: two different devices pasting
|
||||
the same text are falsely dropped as echoes, and the hash is optional so the
|
||||
gate is bypassable. The loop breaker is now the immutable origin device ID plus
|
||||
a per-peer monotonic sequence: ignore updates whose `(origin, sequence)` is not
|
||||
newer than the last-seen pair, and drop any update whose origin is the local
|
||||
device. `content_hash` is retained only for integrity/diagnostics.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Pinned-key mismatch forces a full re-pair
|
||||
|
||||
If a peer reconnects with a static key different from the pinned one, the
|
||||
session is rejected and the peer is treated as unpaired; a fresh handshake plus
|
||||
SAS verification is required before the pinned key is updated. Silently
|
||||
accepting a changed key would void the entire purpose of SAS verification (a
|
||||
LAN MITM who knows the peer's device ID could swap the key). Reinstalling an
|
||||
app therefore means a new identity and a new pairing.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Android reads the background clipboard via AccessibilityService
|
||||
|
||||
Android 10+ blocks background apps from reading the primary clip, which breaks
|
||||
the "send local copy to desktop" half of bidirectional sync. We use an
|
||||
AccessibilityService to read the clipboard in the background; if the user denies
|
||||
it, the app degrades gracefully to receive-only plus manual send (share/quick
|
||||
tile). Applying a received clip via `setPrimaryClip` needs no extra permission
|
||||
and always works.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Desktop clipboard uses the xdg-desktop-portal Clipboard API
|
||||
|
||||
The desktop daemon reaches the clipboard through `xdg-desktop-portal` rather
|
||||
than native X11 calls. Wayland — the default session on modern Fedora/Ubuntu —
|
||||
exposes no direct clipboard access, so an X11-only daemon is broken for most
|
||||
Linux users. Consequence: on Wayland the daemon cannot passively poll; an active
|
||||
portal session started by a user gesture is required, so "enable clipboard
|
||||
sync" is an explicit action (trivial no-op grant on X11). Windows uses the
|
||||
Win32 clipboard API.
|
||||
Reference in New Issue
Block a user