PairingRelayLimitsTest.kt

4.7 KB · raw

1package com.peachpasswords.sync23import kotlinx.coroutines.async4import kotlinx.coroutines.launch5import kotlinx.coroutines.runBlocking6import org.junit.Assert.assertArrayEquals7import org.junit.Assert.assertEquals8import org.junit.Assert.assertFalse9import org.junit.Assert.assertTrue10import org.junit.Assert.fail11import org.junit.Test1213/**14 * PB-26-205 (Android mirror): the pairing/adoption relay inbox is bounded15 * before any allocate/copy of attacker-controlled frame data, with the same16 * ceilings as the browser (`src/shared/pairing-dial.ts`). Both clients must17 * agree on accept/reject at these boundaries.18 */19class PairingRelayLimitsTest {20    private fun inbox(limitCalls: MutableList<Int> = mutableListOf()) =21        RelayPairingInbox { limitCalls.add(1) }2223    @Test fun `shared ceilings match the browser vectors`() {24        assertEquals(1024 * 1024, MAX_PAIRING_FRAME_BYTES)25        assertEquals(12 * 1024 * 1024, MAX_PAIRING_INBOX_BYTES)26        assertEquals(256, MAX_PAIRING_INBOX_MESSAGES)27    }2829    @Test fun `max frame is accepted intact`() = runBlocking {30        val inbox = inbox()31        val frame = ByteArray(MAX_PAIRING_FRAME_BYTES) { (it % 7).toByte() }32        assertTrue(inbox.onFrame(frame.size) { frame.copyOf() })33        assertArrayEquals(frame, inbox.receive())34    }3536    @Test fun `max-plus-one is refused before the copy`() = runBlocking {37        val limitCalls = mutableListOf<Int>()38        val inbox = inbox(limitCalls)39        var copied = false40        assertFalse(inbox.onFrame(MAX_PAIRING_FRAME_BYTES + 1) { copied = true; ByteArray(0) })41        assertFalse("oversize frame must be rejected before read/copy", copied)42        assertEquals(1, limitCalls.size)43        try {44            inbox.receive()45            fail("expected PairingResourceLimitException")46        } catch (error: PairingResourceLimitException) {47            assertEquals("pairing_resource_limit", error.message)48        }49    }5051    @Test fun `individually valid frames tripping the byte budget close the inbox`() = runBlocking {52        val limitCalls = mutableListOf<Int>()53        val inbox = inbox(limitCalls)54        val frames = MAX_PAIRING_INBOX_BYTES / MAX_PAIRING_FRAME_BYTES55        repeat(frames) {56            assertTrue(inbox.onFrame(MAX_PAIRING_FRAME_BYTES) { ByteArray(MAX_PAIRING_FRAME_BYTES) })57        }58        assertFalse(inbox.onFrame(1) { ByteArray(1) })59        assertEquals(1, limitCalls.size)60        try {61            inbox.receive()62            fail("expected PairingResourceLimitException")63        } catch (_: PairingResourceLimitException) {64        }65    }6667    @Test fun `tiny frames trip the message-count limit`() = runBlocking {68        val limitCalls = mutableListOf<Int>()69        val inbox = inbox(limitCalls)70        repeat(MAX_PAIRING_INBOX_MESSAGES) {71            assertTrue(inbox.onFrame(1) { ByteArray(1) })72        }73        assertTrue(limitCalls.isEmpty())74        assertFalse(inbox.onFrame(1) { ByteArray(1) })75        assertEquals(1, limitCalls.size)76        try {77            inbox.receive()78            fail("expected PairingResourceLimitException")79        } catch (_: PairingResourceLimitException) {80        }81    }8283    @Test fun `dequeue relieves the budget for later valid frames`() = runBlocking {84        val limitCalls = mutableListOf<Int>()85        val inbox = inbox(limitCalls)86        repeat(MAX_PAIRING_INBOX_MESSAGES) {87            assertTrue(inbox.onFrame(1) { ByteArray(1) })88        }89        assertEquals(1, inbox.receive().size)90        assertTrue(inbox.onFrame(1) { ByteArray(1) })91        assertTrue(limitCalls.isEmpty())92        repeat(MAX_PAIRING_INBOX_MESSAGES) { inbox.receive() }93    }9495    @Test fun `pending readers share one terminal error and later frames are ignored`() = runBlocking {96        val limitCalls = mutableListOf<Int>()97        val inbox = inbox(limitCalls)98        val first = async {99            try {100                inbox.receive()101                null102            } catch (error: PairingResourceLimitException) {103                error104            }105        }106        val second = async {107            try {108                inbox.receive()109                null110            } catch (error: PairingResourceLimitException) {111                error112            }113        }114        // Let both readers park, then trip the limit.115        launch { inbox.onFrame(MAX_PAIRING_FRAME_BYTES + 1) { ByteArray(0) } }.join()116        assertEquals("pairing_resource_limit", first.await()?.message)117        assertEquals("pairing_resource_limit", second.await()?.message)118        assertFalse(inbox.onFrame(1) { ByteArray(1) })119        assertEquals(1, limitCalls.size)120        try {121            inbox.receive()122            fail("expected PairingResourceLimitException")123        } catch (_: PairingResourceLimitException) {124        }125    }126}