PairingCodeTest.kt

3.0 KB · raw

1package com.peachpasswords.sync23import org.junit.Assert.assertEquals4import org.junit.Assert.assertNull5import org.junit.Test67/**8 * The phone is the only side that generates pairing codes and the browser is9 * the only side that reads them, so a drift here is invisible until a real10 * pairing fails with nothing to look at. The golden vector below was produced11 * by running the browser's own `shared/pairing-code.ts` — not derived from this12 * implementation — so it fails if either side moves.13 */14class PairingCodeTest {1516    // encodePairingCode("192.168.7.115", 49142, deadbeef01234567) in peach-browser.17    private val vectorCode = "R2M0-EWXZ-YVFA-VFQF-04HM-ASR"18    private val vectorToken = byteArrayOf(19        0xde.toByte(), 0xad.toByte(), 0xbe.toByte(), 0xef.toByte(), 0x01, 0x23, 0x45, 0x67,20    )2122    @Test fun `encodes the browser's golden vector`() {23        assertEquals(vectorCode, PairingCode.encode("192.168.7.115", 49142, vectorToken))24    }2526    @Test fun `parses the browser's golden vector`() {27        val parsed = PairingCode.parse(vectorCode)!!28        assertEquals("192.168.7.115", parsed.ip)29        assertEquals(49142, parsed.port)30        assertEquals("deadbeef01234567", parsed.tokenHex)31    }3233    @Test fun `round-trips a freshly generated token`() {34        val token = PairingCode.newToken()35        val parsed = PairingCode.parse(PairingCode.encode("10.0.0.7", 8765, token))!!36        assertEquals("10.0.0.7", parsed.ip)37        assertEquals(8765, parsed.port)38        assertEquals(token.joinToString("") { "%02x".format(it) }, parsed.tokenHex)39    }4041    @Test fun `folds the ambiguous glyphs a user might read off a screen`() {42        // I/L -> 1 and O -> 0, so the doc's own example resolves rather than43        // failing on characters Crockford's alphabet excludes.44        val typed = PairingCode.parse("r2mO-ewxz-yvfa-vfqf-O4hm-asr")45        assertEquals(PairingCode.parse(vectorCode), typed)46    }4748    @Test fun `rejects input that is not a pairing code`() {49        assertNull(PairingCode.parse(""))50        assertNull(PairingCode.parse("418207"))          // the 6-digit SAS51        assertNull(PairingCode.parse("not-a-code!!"))    // U is not in the alphabet52    }5354    @Test(expected = IllegalArgumentException::class)55    fun `refuses to encode a zero port`() {56        PairingCode.encode("10.0.0.1", 0, PairingCode.newToken())57    }5859    @Test fun `a generated code is four groups of four`() {60        // The length is what the user actually experiences, so pin it. Base3261        // is 5 bits per character, so this is fixed by TOKEN_BYTES — if that62        // grows, the code silently gains a group.63        val code = PairingCode.encode("192.168.1.20", 49142, PairingCode.newToken())64        assertEquals(listOf(4, 4, 4, 4), code.split("-").map { it.length })65        assertEquals(19, code.length)66    }6768    @Test(expected = IllegalArgumentException::class)69    fun `refuses a token below the browser's four-byte floor`() {70        PairingCode.encode("10.0.0.1", 8765, byteArrayOf(1, 2, 3))71    }72}