1package com.peachpasswords.sync23import org.junit.Assert.assertEquals4import org.junit.Assert.assertNull5import org.junit.Assert.assertSame6import org.junit.Assert.assertThrows7import org.junit.Test8import java.security.SecureRandom910class RendezvousTest {11 private val handshakeHash = Rendezvous.secretFromHex(12 "0fbab8cd1782bf4b17d69beb69f5b80b0bf24d93da4924959c76ee701c32e655",13 )1415 @Test fun `matches browser rendezvous KATs`() {16 val secret = Rendezvous.deriveSecret(handshakeHash)17 assertEquals(18 "0803691c8ac7330883c0987e33cea4a171992366ce4b06b88691e84ca689773d",19 Rendezvous.bytesToHex(secret),20 )21 assertEquals(22 "9UY3j5AHnwYSApvymtY965su9n60rQOv8QZ2PZzHuKI",23 Rendezvous.deriveId(secret, 500_000),24 )25 }2627 @Test fun `uses required waiting and dialing window order`() {28 val secret = Rendezvous.deriveSecret(handshakeHash)29 val now = 500_000L * 3600 + 359930 assertEquals(31 listOf(32 Rendezvous.deriveId(secret, 500_000),33 Rendezvous.deriveId(secret, 499_999),34 ),35 Rendezvous.waitingIds(secret, now),36 )37 assertEquals(38 listOf(39 Rendezvous.deriveId(secret, 500_000),40 Rendezvous.deriveId(secret, 499_999),41 Rendezvous.deriveId(secret, 500_001),42 ),43 Rendezvous.dialingIds(secret, now),44 )45 }4647 @Test fun `opaque capabilities resolve current and previous windows only`() {48 val secretHex = "42".repeat(32)49 val peer = PairedDevice(50 deviceId = "browser-1",51 longTermPublicKeyHex = "ab".repeat(32),52 pairedAt = 1L,53 rendezvousSecretHex = secretHex,54 )55 val legacy = PairedDevice(56 deviceId = "legacy",57 longTermPublicKeyHex = "cd".repeat(32),58 pairedAt = 1L,59 )60 val secret = Rendezvous.secretFromHex(secretHex)61 val now = 500_000L * 3600 + 359962 val current = Rendezvous.deriveId(secret, 500_000)63 val previous = Rendezvous.deriveId(secret, 499_999)64 val next = Rendezvous.deriveId(secret, 500_001)6566 assertSame(peer, Rendezvous.resolvePeer(listOf(legacy, peer), current, now))67 assertSame(peer, Rendezvous.resolvePeer(listOf(legacy, peer), previous, now))68 assertNull(Rendezvous.resolvePeer(listOf(legacy, peer), next, now))69 assertNull(Rendezvous.resolvePeer(listOf(legacy, peer), "Z".repeat(43), now))70 assertNull(Rendezvous.resolvePeer(listOf(legacy), current, now))71 }7273 @Test fun `first-pair token is 128-bit base64url`() {74 val deterministic = object : SecureRandom() {75 override fun nextBytes(bytes: ByteArray) {76 bytes.indices.forEach { bytes[it] = it.toByte() }77 }78 }79 assertEquals("AAECAwQFBgcICQoLDA0ODw", Rendezvous.newPairingToken(deterministic))80 }8182 @Test fun `relay URL requires credential-free wss`() {83 assertEquals(84 "wss://relay.example.test/base",85 Rendezvous.normalizeRelayUrl("wss://relay.example.test/base/"),86 )87 assertThrows(IllegalArgumentException::class.java) {88 Rendezvous.normalizeRelayUrl("ws://relay.example.test")89 }90 assertThrows(IllegalArgumentException::class.java) {91 Rendezvous.normalizeRelayUrl("wss://user:[email protected]")92 }93 }9495 @Test fun `completed pairing persists secret and the listener relay`() {96 val device = PairedDevice(97 deviceId = "browser-1",98 deviceName = "Firefox",99 deviceType = "extension",100 longTermPublicKeyHex = "ab".repeat(32),101 pairedAt = 1L,102 )103 val bound = Rendezvous.bindPairState(104 device,105 handshakeHash,106 "wss://relay.example.test/",107 )108 assertEquals(109 "0803691c8ac7330883c0987e33cea4a171992366ce4b06b88691e84ca689773d",110 bound.rendezvousSecretHex,111 )112 assertEquals("wss://relay.example.test", bound.relayUrl)113 }114}