SyncMessage.kt

2.8 KB · raw

1package com.peachpasswords.sync23import kotlinx.serialization.SerialName4import kotlinx.serialization.Serializable5import kotlinx.serialization.json.Json67/**8 * Chirp Sync wire protocol — exact port of the browser `ChirpSyncMessage`9 * union (shared/sync-types.ts). Each frame is JSON with a `type` discriminator,10 * sent over the Noise-NK-encrypted session. Entries travel as opaque base6411 * ciphertext envelopes; the coordinator never sees plaintext.12 */13@Serializable14sealed class SyncMessage {15    @Serializable16    @SerialName("SYNC_HELLO")17    data class Hello(18        val vaultId: String,19        val deviceId: String,20        val version: Long,21        val deviceTime: Long,22    ) : SyncMessage()2324    @Serializable25    @SerialName("SYNC_MANIFEST")26    data class Manifest(27        val entries: Map<String, Long>,28        val tombstones: Map<String, Long>,29    ) : SyncMessage()3031    @Serializable32    @SerialName("SYNC_REQUEST")33    data class Request(val entryIds: List<String>) : SyncMessage()3435    @Serializable36    @SerialName("SYNC_ENTRIES")37    data class Entries(val entries: Map<String, String>) : SyncMessage()3839    @Serializable40    @SerialName("SYNC_TOMBSTONES")41    data class Tombstones(val tombstones: Map<String, Long>) : SyncMessage()4243    @Serializable44    @SerialName("SYNC_DEVICE_MANIFEST")45    data class DeviceManifest(val manifest: SignedDeviceManifest?) : SyncMessage()4647    @Serializable48    @SerialName("SYNC_DONE")49    data class Done(50        val entriesReceived: Int = 0,51        val entriesSent: Int = 0,52    ) : SyncMessage()5354    @Serializable55    @SerialName("SYNC_ERROR")56    data class Error(val error: String) : SyncMessage()57}5859@Serializable60data class ManifestDeviceEntry(61    val deviceId: String,62    val deviceName: String,63    val deviceType: String,64    val longTermPublicKeyHex: String,65    val signingPublicKeyHex: String,66    val wsPort: Int? = null,67    val pairedAt: Long,68)6970@Serializable71data class DeviceManifestBody(72    val manifestVersion: Int,73    val issuerDeviceId: String,74    val issuedAt: Long,75    val devices: List<ManifestDeviceEntry>,76)7778@Serializable79data class SignedDeviceManifest(80    val body: DeviceManifestBody,81    /** Hex-encoded 64-byte Ed25519 signature over the canonicalised body. */82    val signatureHex: String,83)8485/**86 * The wire codec. `classDiscriminator = "type"` reproduces the browser's flat87 * `{ "type": "SYNC_HELLO", ... }` shape exactly; `encodeDefaults` keeps the88 * optional SYNC_DONE counters on the wire.89 */90val SyncJson: Json = Json {91    classDiscriminator = "type"92    encodeDefaults = true93    ignoreUnknownKeys = true94}9596fun SyncMessage.encodeToBytes(): ByteArray =97    SyncJson.encodeToString(SyncMessage.serializer(), this).toByteArray(Charsets.UTF_8)9899fun decodeSyncMessage(bytes: ByteArray): SyncMessage =100    SyncJson.decodeFromString(SyncMessage.serializer(), String(bytes, Charsets.UTF_8))