RendezvousTest.kt

2.9 KB · raw

1package com.peachpasswords.sync23import org.junit.Assert.assertEquals4import org.junit.Assert.assertThrows5import org.junit.Test6import java.security.SecureRandom78class RendezvousTest {9    private val handshakeHash = Rendezvous.secretFromHex(10        "0fbab8cd1782bf4b17d69beb69f5b80b0bf24d93da4924959c76ee701c32e655",11    )1213    @Test fun `matches browser rendezvous KATs`() {14        val secret = Rendezvous.deriveSecret(handshakeHash)15        assertEquals(16            "0803691c8ac7330883c0987e33cea4a171992366ce4b06b88691e84ca689773d",17            Rendezvous.bytesToHex(secret),18        )19        assertEquals(20            "9UY3j5AHnwYSApvymtY965su9n60rQOv8QZ2PZzHuKI",21            Rendezvous.deriveId(secret, 500_000),22        )23    }2425    @Test fun `uses required waiting and dialing window order`() {26        val secret = Rendezvous.deriveSecret(handshakeHash)27        val now = 500_000L * 3600 + 359928        assertEquals(29            listOf(30                Rendezvous.deriveId(secret, 500_000),31                Rendezvous.deriveId(secret, 499_999),32            ),33            Rendezvous.waitingIds(secret, now),34        )35        assertEquals(36            listOf(37                Rendezvous.deriveId(secret, 500_000),38                Rendezvous.deriveId(secret, 499_999),39                Rendezvous.deriveId(secret, 500_001),40            ),41            Rendezvous.dialingIds(secret, now),42        )43    }4445    @Test fun `first-pair token is 128-bit base64url`() {46        val deterministic = object : SecureRandom() {47            override fun nextBytes(bytes: ByteArray) {48                bytes.indices.forEach { bytes[it] = it.toByte() }49            }50        }51        assertEquals("AAECAwQFBgcICQoLDA0ODw", Rendezvous.newPairingToken(deterministic))52    }5354    @Test fun `relay URL requires credential-free wss`() {55        assertEquals(56            "wss://relay.example.test/base",57            Rendezvous.normalizeRelayUrl("wss://relay.example.test/base/"),58        )59        assertThrows(IllegalArgumentException::class.java) {60            Rendezvous.normalizeRelayUrl("ws://relay.example.test")61        }62        assertThrows(IllegalArgumentException::class.java) {63            Rendezvous.normalizeRelayUrl("wss://user:[email protected]")64        }65    }6667    @Test fun `completed pairing persists secret and the listener relay`() {68        val device = PairedDevice(69            deviceId = "browser-1",70            deviceName = "Firefox",71            deviceType = "extension",72            longTermPublicKeyHex = "ab".repeat(32),73            pairedAt = 1L,74        )75        val bound = Rendezvous.bindPairState(76            device,77            handshakeHash,78            "wss://relay.example.test/",79        )80        assertEquals(81            "0803691c8ac7330883c0987e33cea4a171992366ce4b06b88691e84ca689773d",82            bound.rendezvousSecretHex,83        )84        assertEquals("wss://relay.example.test", bound.relayUrl)85    }86}