SyncLimits.kt

1.6 KB · raw

1package com.peachpasswords.sync23import java.io.IOException45/**6 * Cloudflare Workers caps one WebSocket message at 32 MiB. Keep a generous7 * margin for the Noise authentication tag and platform framing overhead.8 */9const val MAX_SYNC_FRAME_BYTES: Int = 24 * 1024 * 10241011class SyncFrameTooLargeException(12    message: String = "sync frame too large (WebSocket close 1009)",13) : IOException(message)1415fun requireSyncFrameSize(size: Int) {16    if (size > MAX_SYNC_FRAME_BYTES) {17        throw SyncFrameTooLargeException("sync frame exceeds $MAX_SYNC_FRAME_BYTES bytes")18    }19}2021/**22 * Timestamp bounds, ported from peach-browser `src/shared/sync-limits.ts`.23 * Both sides must agree, so change them together.24 */25object SyncLimits {26    /**27     * HV-1: maximum tolerated forward clock skew on any timestamp received from28     * a sync peer. A peer whose clock is slightly ahead still merges cleanly,29     * but a deliberately far-future `modifiedAt`/`deletedAt` can no longer30     * permanently pin a malicious value against honest local edits.31     */32    const val MAX_CLOCK_SKEW_MS = 5 * 60 * 1000L3334    /**35     * Clamp a peer-supplied timestamp to `now + MAX_CLOCK_SKEW_MS`. Applied to36     * every remote `modifiedAt` and `deletedAt` BEFORE it is compared against37     * any local timestamp.38     *39     * See the HOLE 2 coverage audit in the browser's `sync-limits.ts` for the40     * full list of peer-supplied timestamps and what bounds each one.41     */42    fun clampSyncTimestamp(ts: Long, now: Long = System.currentTimeMillis()): Long =43        minOf(ts, now + MAX_CLOCK_SKEW_MS)44}