RelayHubTest.kt

5.6 KB · raw

1package com.peachpasswords.sync23import kotlinx.coroutines.runBlocking4import org.junit.Assert.assertArrayEquals5import org.junit.Assert.assertEquals6import org.junit.Assert.assertTrue7import org.junit.Test89/**10 * The relay's whole job is to move bytes it must not understand between two11 * peers that cannot reach each other. The failures worth pinning are the quiet12 * ones: a rejection reason that collapses into the wrong one (the requester then13 * retries the same dead relay forever), and a payload that gets re-encoded on14 * the way through (the end-to-end session breaks with nothing in the logs).15 */16class RelayHubTest {1718    private val keyA = "aa".repeat(32)19    private val keyB = "bb".repeat(32)2021    private class Peer {22        val sent = mutableListOf<ByteArray>()23        val messages: List<RelayMessage?> get() = sent.map { decodeRelayMessage(it) }24        val sink: suspend (ByteArray) -> Unit = { sent.add(it) }25    }2627    private fun hub(enabled: Boolean = true) = RelayHub(object : RelayPolicy {28        override fun isRelayEnabled() = enabled29    })3031    @Test fun `relaying turned off is reported as disabled, not unavailable`() = runBlocking {32        val hub = hub(enabled = false)33        val a = Peer()34        val b = Peer()35        val linkA = hub.register(keyA, a.sink)36        hub.register(keyB, b.sink)3738        hub.onRequest(linkA, keyB)3940        assertEquals(listOf(RelayMessage.Disabled), a.messages)41        assertTrue("the target must not be told about a refused request", b.sent.isEmpty())42    }4344    @Test fun `an absent target is unavailable`() = runBlocking {45        val hub = hub()46        val a = Peer()47        val linkA = hub.register(keyA, a.sink)4849        hub.onRequest(linkA, keyB)5051        assertEquals(listOf(RelayMessage.Unavailable), a.messages)52    }5354    @Test fun `a target busy with its own sync is unavailable`() = runBlocking {55        val hub = hub()56        val a = Peer()57        val b = Peer()58        val linkA = hub.register(keyA, a.sink)59        val linkB = hub.register(keyB, b.sink)60        hub.beginSync(linkB)6162        hub.onRequest(linkA, keyB)63        assertEquals(listOf(RelayMessage.Unavailable), a.messages)6465        // …and becomes available again once that sync finishes.66        hub.endSync(linkB)67        hub.onRequest(linkA, keyB)68        assertEquals(RelayMessage.Accept, a.messages.last())69    }7071    @Test fun `a channel opens with the target told first`() = runBlocking {72        val hub = hub()73        val a = Peer()74        val b = Peer()75        val linkA = hub.register(keyA, a.sink)76        hub.register(keyB, b.sink)7778        hub.onRequest(linkA, keyB)7980        assertEquals(listOf(RelayMessage.Open(keyA)), b.messages)81        assertEquals(listOf(RelayMessage.Accept), a.messages)82    }8384    @Test fun `frames cross verbatim in both directions`() = runBlocking {85        val hub = hub()86        val a = Peer()87        val b = Peer()88        val linkA = hub.register(keyA, a.sink)89        val linkB = hub.register(keyB, b.sink)90        hub.onRequest(linkA, keyB)9192        // Not a RelayMessage encoding — whatever arrived must land unaltered,93        // which is exactly what the relay promises about the inner session.94        val opaque = "{\"type\":\"RELAY_FRAME\",\"payload\":\"ZmFrZQ==\"}".toByteArray()95        assertTrue(hub.forward(linkA, opaque))96        assertArrayEqualsLast(opaque, b.sent)9798        val back = "{\"type\":\"RELAY_FRAME\",\"payload\":\"b3RoZXI=\"}".toByteArray()99        assertTrue(hub.forward(linkB, back))100        assertArrayEqualsLast(back, a.sent)101    }102103    @Test fun `forwarding without a channel fails rather than guessing a destination`() = runBlocking {104        val hub = hub()105        val a = Peer()106        hub.register(keyB, Peer().sink)107        val linkA = hub.register(keyA, a.sink)108109        assertEquals(false, hub.forward(linkA, "x".toByteArray()))110    }111112    @Test fun `a dropped connection closes the other end of the channel`() = runBlocking {113        val hub = hub()114        val a = Peer()115        val b = Peer()116        val linkA = hub.register(keyA, a.sink)117        val linkB = hub.register(keyB, b.sink)118        hub.onRequest(linkA, keyB)119120        hub.unregister(linkA)121122        assertEquals(RelayMessage.Close, b.messages.last())123        // B is free to serve a new channel afterwards.124        val c = Peer()125        val linkC = hub.register("cc".repeat(32), c.sink)126        hub.onRequest(linkC, keyB)127        assertEquals(RelayMessage.Accept, c.messages.last())128    }129130    @Test fun `a link cannot relay to itself`() = runBlocking {131        val hub = hub()132        val a = Peer()133        val linkA = hub.register(keyA, a.sink)134135        hub.onRequest(linkA, keyA)136137        assertEquals(listOf(RelayMessage.Unavailable), a.messages)138    }139140    @Test fun `two laptop pairs can be relayed at once`() = runBlocking {141        val hub = hub()142        val peers = List(4) { Peer() }143        val keys = List(4) { "0$it".repeat(32) }144        val links = keys.mapIndexed { i, k -> hub.register(k, peers[i].sink) }145146        hub.onRequest(links[0], keys[1])147        hub.onRequest(links[2], keys[3])148149        assertEquals(RelayMessage.Accept, peers[0].messages.last())150        assertEquals(RelayMessage.Accept, peers[2].messages.last())151        assertTrue(hub.forward(links[0], "one".toByteArray()))152        assertTrue(hub.forward(links[2], "two".toByteArray()))153        assertArrayEqualsLast("one".toByteArray(), peers[1].sent)154        assertArrayEqualsLast("two".toByteArray(), peers[3].sent)155    }156157    private fun assertArrayEqualsLast(expected: ByteArray, sent: List<ByteArray>) {158        val last = sent.lastOrNull()159        assertTrue("nothing was forwarded", last != null)160        assertArrayEquals(expected, last)161    }162}