Files
sepot/proto/sepot.proto
T
Julius Joska 69687d0acf feat(relay): ephemeral delivery, call wake-ups and TURN credentials
Protocol and relay groundwork for typing indicators and 1:1 calls.

- ContentHint.EPHEMERAL: delivered only to an online recipient, never
  queued or pushed; the sender gets Receipt.STATUS_DROPPED otherwise.
- ContentHint.CALL: a call offer is queued like a normal message, but an
  offline recipient is woken immediately — a PushKit VoIP push on iOS
  (priority 10, 45 s expiry) or a short-lived FCM data message on
  Android. The relay learns that a device is being called, not by whom.
- RegisterPush.voip_token stores the iOS VoIP token next to the regular
  APNs token (migration 0004).
- TurnRequest/TurnCredentials: logged-in devices receive time-limited
  coturn credentials (use-auth-secret REST scheme, HMAC-SHA1), so the
  TURN server is never an open relay. Configured with SEPOT_TURN_URLS and
  SEPOT_TURN_SECRET or SEPOT_TURN_SECRET_FILE; without them calls fall
  back to direct connections and STUN.
2026-10-03 22:43:53 +02:00

178 lines
6.0 KiB
Protocol Buffer

// Šepot wire protokol v1 — jediný zdroj pravdy pro Rust (prost) i Dart.
//
// Sémantika:
// server vidí JEN routing (dst_address, msg_uuid, content_hint); payload je
// opaque sealed-sender ciphertext, odesílatel i obsah jsou uvnitř.
// Žádné pole nesmí nést PII ani IP.
syntax = "proto3";
package sepot.v1;
// ── Obálka zprávy (klient→server i server→klient při DELIVER) ─────────
enum ContentHint {
CONTENT_HINT_DEFAULT = 0; // běžná zpráva
CONTENT_HINT_RESYNC = 1; // řízení retry po session poruše
CONTENT_HINT_SUPPLEMENTARY = 2; // doprovodná (receipt apod.)
// Jen živé doručení (např. „píše…“): offline příjemci se neukládá do
// fronty ani nebudí pushem — zastaralá by po vyzvednutí jen mátla.
CONTENT_HINT_EPHEMERAL = 3;
// Nabídka hovoru: ukládá se jako běžná zpráva, ale offline příjemce se
// budí VoIP pushem (iOS) nebo FCM s krátkou platností — zvonit má hned.
// Relay se tím dozví, že zařízení někdo volá; kdo, dál neví.
CONTENT_HINT_CALL = 4;
}
message Envelope {
bytes dst_address = 1; // 32 B rotovatelný routing token příjemce
bytes msg_uuid = 2; // 16 B, generuje odesílatel; dedup/fanout
ContentHint content_hint = 3;
bytes payload = 4; // OPAQUE sealed-sender ciphertext (≤256 KiB)
}
// ── Potvrzení a stavy ──────────────────────────────────────────────────
message Ack {
bytes msg_uuid = 1; // klient potvrzuje vyzvednutí DELIVER
}
message Receipt {
enum Status {
STATUS_QUEUED = 0;
STATUS_DELIVERED_ONLINE = 1;
STATUS_STORED_OFFLINE = 2;
STATUS_DROPPED = 3; // EPHEMERAL a příjemce nebyl online
}
bytes msg_uuid = 1;
Status status = 2;
}
// ── Autentizace zařízení (podpis výzvy, žádné heslo/PII) ────────
message Auth {
bytes device_uuid = 1; // 16 B
bytes nonce_client = 2; // 16 B
}
message Challenge {
bytes nonce_server = 1; // 32 B
}
message AuthProof {
// XEdDSA podpis device klíčem nad: nonce_client || nonce_server || "sepot-auth-v1"
bytes signature = 1; // 64 B
}
// ── Provozní rámce ─────────────────────────────────────────────────────
message PrekeysLow {
uint32 remaining = 1; // hint: zbývající one-time prekeys
}
message Error {
uint32 code = 1;
string message = 2;
}
message Ping {}
message Pong {}
// Potvrzení úspěšné WS autentizace — teprve po něm smí klient hlásit
// „připojeno" a posílat obálky (fronta se vylévá hned po něm).
message AuthOk {}
// ── Push registrace ───────────────────────────────────────────────
// Posílá se JEN přes autentizovaný WS (endpoint se nikdy neposílá
// neautentizovaně a jde stejnou cestou jako provoz — klidně přes Tor).
// Relay endpoint NIKDY neloguje. Budíček je vždy prázdný „ťuk" — žádný
// obsah ani odesílatel neopustí sealed-sender kanál.
message RegisterPush {
enum Platform {
PLATFORM_NONE = 0; // vypnout push (anonymní režim) — smaže token
PLATFORM_FCM = 1; // opt-in fast mode (vystaví token Googlu)
PLATFORM_APNS = 2; // opt-in fast mode (Apple)
PLATFORM_UNIFIEDPUSH = 3; // default — endpoint distributora (ntfy)
PLATFORM_WEBPUSH = 4; // RFC 8030 endpoint
}
Platform platform = 1;
// UnifiedPush/WebPush endpoint URL (utf-8 bytes), nebo FCM/APNs token.
// Prázdné, když platform=NONE.
bytes endpoint = 2;
// iOS: PushKit VoIP token pro příchozí hovory (vedle běžného APNs tokenu
// v `endpoint`). Prázdné = zařízení VoIP push nemá.
bytes voip_token = 3;
}
// Dočasné údaje k TURN serveru pro hovory (REST schéma coturnu). Vydává je
// relay jen přihlášenému zařízení, aby TURN nebyl otevřený pro kohokoli.
message TurnRequest {}
message TurnCredentials {
repeated string urls = 1; // turn:/turns: adresy
string username = 2; // "<unix expirace>:<náhodné id>"
string credential = 3; // base64(HMAC-SHA1(secret, username))
uint32 ttl_seconds = 4;
}
// ── Prekey typy (REST /v1/register, /v1/keys — sdílené s bundle fetch) ─
message SignedPreKey {
uint32 key_id = 1;
bytes public_key = 2; // 32 B Curve25519
bytes signature = 3; // 64 B XEdDSA podpis identity klíčem
}
message KyberPreKey {
uint32 key_id = 1;
bytes public_key = 2; // Kyber-1024, ~1569 B
bytes signature = 3;
bool is_last_resort = 4; // last-resort se nespotřebovává
}
message OneTimePreKey {
uint32 key_id = 1;
bytes public_key = 2; // 32 B Curve25519
}
message PreKeyBundle {
uint32 registration_id = 1;
uint32 device_id = 2;
bytes identity_key = 3; // 33 B (type byte + Curve25519)
SignedPreKey signed_prekey = 4;
KyberPreKey kyber_prekey = 5;
OneTimePreKey one_time_prekey = 6; // chybí, když OTP došly (povolený fallback)
}
// Bundle pro všechna zařízení identity (multi-device fanout).
message PreKeyBundles {
repeated PreKeyBundle devices = 1;
}
// ── Vrcholové rámce na WS (subprotocol "sepot.v1") ────────────────────
message ClientFrame {
oneof frame {
Auth auth = 1;
AuthProof auth_proof = 2;
Envelope envelope = 3;
Ack ack = 4;
Pong pong = 5;
RegisterPush register_push = 6;
TurnRequest turn_request = 7;
}
}
message ServerFrame {
oneof frame {
Challenge challenge = 1;
Receipt receipt = 2;
Envelope deliver = 3;
PrekeysLow prekeys_low = 4;
Ping ping = 5;
Error error = 6;
AuthOk auth_ok = 7;
TurnCredentials turn_credentials = 8;
}
}