package com.peachpasswords.sync import kotlinx.coroutines.runBlocking import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test /** * The relay's whole job is to move bytes it must not understand between two * peers that cannot reach each other. The failures worth pinning are the quiet * ones: a rejection reason that collapses into the wrong one (the requester then * retries the same dead relay forever), and a payload that gets re-encoded on * the way through (the end-to-end session breaks with nothing in the logs). */ class RelayHubTest { private val keyA = "aa".repeat(32) private val keyB = "bb".repeat(32) private class Peer { val sent = mutableListOf() val messages: List get() = sent.map { decodeRelayMessage(it) } val sink: suspend (ByteArray) -> Unit = { sent.add(it) } } private fun hub(enabled: Boolean = true) = RelayHub(object : RelayPolicy { override fun isRelayEnabled() = enabled }) @Test fun `relaying turned off is reported as disabled, not unavailable`() = runBlocking { val hub = hub(enabled = false) val a = Peer() val b = Peer() val linkA = hub.register(keyA, a.sink) hub.register(keyB, b.sink) hub.onRequest(linkA, keyB) assertEquals(listOf(RelayMessage.Disabled), a.messages) assertTrue("the target must not be told about a refused request", b.sent.isEmpty()) } @Test fun `an absent target is unavailable`() = runBlocking { val hub = hub() val a = Peer() val linkA = hub.register(keyA, a.sink) hub.onRequest(linkA, keyB) assertEquals(listOf(RelayMessage.Unavailable), a.messages) } @Test fun `a target busy with its own sync is unavailable`() = runBlocking { val hub = hub() val a = Peer() val b = Peer() val linkA = hub.register(keyA, a.sink) val linkB = hub.register(keyB, b.sink) hub.beginSync(linkB) hub.onRequest(linkA, keyB) assertEquals(listOf(RelayMessage.Unavailable), a.messages) // …and becomes available again once that sync finishes. hub.endSync(linkB) hub.onRequest(linkA, keyB) assertEquals(RelayMessage.Accept, a.messages.last()) } @Test fun `a channel opens with the target told first`() = runBlocking { val hub = hub() val a = Peer() val b = Peer() val linkA = hub.register(keyA, a.sink) hub.register(keyB, b.sink) hub.onRequest(linkA, keyB) assertEquals(listOf(RelayMessage.Open(keyA)), b.messages) assertEquals(listOf(RelayMessage.Accept), a.messages) } @Test fun `frames cross verbatim in both directions`() = runBlocking { val hub = hub() val a = Peer() val b = Peer() val linkA = hub.register(keyA, a.sink) val linkB = hub.register(keyB, b.sink) hub.onRequest(linkA, keyB) // Not a RelayMessage encoding — whatever arrived must land unaltered, // which is exactly what the relay promises about the inner session. val opaque = "{\"type\":\"RELAY_FRAME\",\"payload\":\"ZmFrZQ==\"}".toByteArray() assertTrue(hub.forward(linkA, opaque)) assertArrayEqualsLast(opaque, b.sent) val back = "{\"type\":\"RELAY_FRAME\",\"payload\":\"b3RoZXI=\"}".toByteArray() assertTrue(hub.forward(linkB, back)) assertArrayEqualsLast(back, a.sent) } @Test fun `forwarding without a channel fails rather than guessing a destination`() = runBlocking { val hub = hub() val a = Peer() hub.register(keyB, Peer().sink) val linkA = hub.register(keyA, a.sink) assertEquals(false, hub.forward(linkA, "x".toByteArray())) } @Test fun `a dropped connection closes the other end of the channel`() = runBlocking { val hub = hub() val a = Peer() val b = Peer() val linkA = hub.register(keyA, a.sink) val linkB = hub.register(keyB, b.sink) hub.onRequest(linkA, keyB) hub.unregister(linkA) assertEquals(RelayMessage.Close, b.messages.last()) // B is free to serve a new channel afterwards. val c = Peer() val linkC = hub.register("cc".repeat(32), c.sink) hub.onRequest(linkC, keyB) assertEquals(RelayMessage.Accept, c.messages.last()) } @Test fun `a link cannot relay to itself`() = runBlocking { val hub = hub() val a = Peer() val linkA = hub.register(keyA, a.sink) hub.onRequest(linkA, keyA) assertEquals(listOf(RelayMessage.Unavailable), a.messages) } @Test fun `two laptop pairs can be relayed at once`() = runBlocking { val hub = hub() val peers = List(4) { Peer() } val keys = List(4) { "0$it".repeat(32) } val links = keys.mapIndexed { i, k -> hub.register(k, peers[i].sink) } hub.onRequest(links[0], keys[1]) hub.onRequest(links[2], keys[3]) assertEquals(RelayMessage.Accept, peers[0].messages.last()) assertEquals(RelayMessage.Accept, peers[2].messages.last()) assertTrue(hub.forward(links[0], "one".toByteArray())) assertTrue(hub.forward(links[2], "two".toByteArray())) assertArrayEqualsLast("one".toByteArray(), peers[1].sent) assertArrayEqualsLast("two".toByteArray(), peers[3].sent) } private fun assertArrayEqualsLast(expected: ByteArray, sent: List) { val last = sent.lastOrNull() assertTrue("nothing was forwarded", last != null) assertArrayEquals(expected, last) } }