Divano

Un server di sincronizzazione tuoA sync server of your own

Divano sincronizza i dispositivi di casa attraverso una piccola API HTTP, descritta qui in OpenAPI 3.1. Il server conserva solo dati cifrati sui dispositivi: chiunque può ospitarne uno, e l'app ha bisogno solo del suo indirizzo.

Divano syncs the devices of a household through a small HTTP API, described here in OpenAPI 3.1. The server only keeps data encrypted on the devices: anyone can host one, and the app only needs its address.

Come funziona

  • Uno spazio è l'insieme dei dispositivi di una famiglia. Ha una chiave che conoscono solo i suoi dispositivi e che il server non riceve mai.
  • Il server conserva record {id, rev, deleted, blob} per spazio. id è un'impronta calcolata con la chiave e blob è cifrato con AES-256-GCM: il server sa quanti record ci sono, quanto sono grandi (arrotondati) e quando cambiano, non cosa contengono.
  • rev è un contatore per spazio assegnato dal server. Le scritture sono compare-and-set su rev: l'ordine del server fa fede e l'orologio dei dispositivi non conta.
  • Un nuovo dispositivo entra nello spazio con un abbinamento: mostra un codice QR, un dispositivo già presente lo inquadra, controlla l'impronta della chiave e gli passa la chiave dello spazio cifrata per lui. Il server la custodisce senza poterla aprire.

Un server malevolo può non rispondere o restituire dati vecchi, ma non può leggere né falsificare i record, né far entrare un suo dispositivo in uno spazio. Il formato dei record e la crittografia riguardano solo l'app: il server deve soltanto conservarli e restituirli in ordine.

How it works

  • A space is the set of devices of one household. It has a key that only its devices know and that the server never receives.
  • The server stores records {id, rev, deleted, blob} per space. id is a keyed hash and blob is encrypted with AES-256-GCM: the server learns how many records there are, how big they are (rounded) and when they change, not what they contain.
  • rev is a per-space counter assigned by the server. Writes are compare-and-set on rev: the server's order is the truth and no device clock matters.
  • A new device joins a space by pairing: it shows a QR code, a device already in the space scans it, checks the key fingerprint and hands over the space key encrypted for it. The server holds it without being able to open it.

A malicious server can refuse service or hand back stale data, but it cannot read or forge records, nor slip a device of its own into a space. The record format and the cryptography only concern the app: the server just stores records and hands them back in order.

Cosa deve fare un server

  • Rispondere su HTTPS. Se sta dietro un proxy, il timeout di lettura del proxy deve superare la durata dei long-poll (maxWaitSeconds).
  • Servire GET /discovery senza autenticazione: versioni del protocollo, regole di registrazione, limiti, funzioni e chi gestisce il server. Non sotto /.well-known, che alcuni hosting riservano a sé.
  • Salvare i record in una tabella records(space_id, id, rev, deleted, blob) con chiave (space_id, id) e un indice su (space_id, rev), più un contatore per spazio.
  • Eseguire ogni push in una transazione: controllare ogni baseRev, assegnare rev = ++contatore alle scritture applicate, restituire il record attuale per i conflitti.
  • Dimenticare i record cancellati dopo tombstoneRetentionDays (almeno 30) e ricordare il rev più alto eliminato: un pull con since più vecchio riceve 410 cursor_expired.
  • Long-poll: svegliare i pull in attesa quando il contatore dello spazio cambia. Chi non può tenere aperte le richieste annuncia maxWaitSeconds: 0 e toglie longpoll da features.
  • Far scadere gli abbinamenti dopo 10 minuti; ogni codice si usa una volta sola.
  • Conservare dei token solo l'impronta SHA-256 e non registrare mai i corpi delle richieste né i token.
  • Limitare per IP POST /v1/spaces e POST /v1/pairings.
  • Per un server privato, usare registration: secret: chi crea uno spazio deve conoscere la password del server.

What a server must do

  • Answer over HTTPS. Behind a proxy, the proxy's read timeout must be longer than the long-polls (maxWaitSeconds).
  • Serve GET /discovery without authentication: protocol versions, registration rules, limits, features and who runs the server. Not under /.well-known, which some hosts reserve for themselves.
  • Store records in a table records(space_id, id, rev, deleted, blob) keyed on (space_id, id) with an index on (space_id, rev), plus a per-space counter.
  • Run each push in one transaction: check every baseRev, assign rev = ++counter to the applied writes, return the current record for conflicts.
  • Forget deleted records after tombstoneRetentionDays (at least 30) and remember the highest purged rev: a pull with an older since gets 410 cursor_expired.
  • Long-poll: wake waiting pulls when the space's counter moves. A server that can't hold requests open advertises maxWaitSeconds: 0 and drops longpoll from features.
  • Expire pairings after 10 minutes; each code is single use.
  • Keep only a SHA-256 hash of tokens and never log request bodies or tokens.
  • Rate-limit POST /v1/spaces and POST /v1/pairings per IP.
  • For a private server, use registration: secret: whoever creates a space must know the server password.

Per esempio, il server ufficiale risponde così aFor example, the official server answers GET https://sync-divano.dambox.ch/discovery:

{
  "protocols": [1],
  "registration": "open",
  "features": ["longpoll"],
  "limits": {
    "maxRecords": 20000, "maxBytes": 33554432, "maxBlobBytes": 16384,
    "maxBatch": 500, "maxDevices": 10, "maxWaitSeconds": 30,
    "tombstoneRetentionDays": 90, "inactiveSpaceDays": 365
  },
  "operator": {
    "name": "Divano",
    "privacyUrl": "https://divano.dambox.ch/privacy/",
    "contact": "app+divano@dambox.ch"
  }
}

Collegare l'app

  1. In Impostazioni › Sincronizzazione, prima di iniziare, scrivi l'indirizzo del server nel campo Server. Senza schema, Divano aggiunge https://; http:// è accettato solo per indirizzi della rete di casa (localhost, .local, .lan, .home.arpa, IP privati).
  2. Con registration: secret compare Password del server: è il segreto che hai configurato.
  3. Usa lo stesso server su tutti i dispositivi: durante l'abbinamento l'app controlla che coincida.

Connecting the app

  1. In Settings › Sync, before you start, type the server address in the Server field. Without a scheme Divano adds https://; http:// is only accepted for home network addresses (localhost, .local, .lan, .home.arpa, private IPs).
  2. With registration: secret, Server password appears: it's the secret you configured.
  3. Use the same server on every device: pairing checks that they match.

Riferimento APIAPI reference

Generato da openapi.yaml, in inglese come la specifica. Corpi JSON, errori application/problem+json (RFC 9457), valori binari in base64url senza padding, orari in millisecondi Unix.

Generated from openapi.yaml. JSON bodies, application/problem+json errors (RFC 9457), binary values in base64url without padding, times in Unix milliseconds.