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.Test1213141516171819class 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 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}