1package com.peachpasswords.codex23import org.junit.Assert.assertArrayEquals4import org.junit.Assert.assertEquals5import org.junit.Assert.assertFalse6import org.junit.Assert.assertTrue7import org.junit.Test8import kotlin.random.Random9101112131415class CodexSuiteTest {1617 1819 @Test fun deflate_inflate_round_trips_small() {20 val data = "the quick brown duck".toByteArray()21 assertArrayEquals(data, CodexCodec.inflate(CodexCodec.deflate(data)))22 }2324 @Test fun deflate_inflate_round_trips_empty() {25 assertArrayEquals(ByteArray(0), CodexCodec.inflate(CodexCodec.deflate(ByteArray(0))))26 }2728 @Test fun deflate_inflate_round_trips_large_random() {29 val rng = Random(42)30 val data = ByteArray(50_000) { rng.nextInt().toByte() }31 assertArrayEquals(data, CodexCodec.inflate(CodexCodec.deflate(data)))32 }3334 @Test fun deflate_shrinks_compressible_data() {35 val data = "A".repeat(10_000).toByteArray()36 assertTrue(CodexCodec.deflate(data).size < data.size)37 }3839 4041 @Test fun parity_shards_are_at_least_one() {42 assertTrue(computeParityShards(1) >= 1)43 assertTrue(computeParityShards(6) >= 1)44 }4546 @Test fun parity_shards_scale_with_data() {47 48 assertTrue(computeParityShards(100) >= computeParityShards(10))49 }5051 5253 private fun encodedShards(data: Int, parity: Int, size: Int, seed: Int): Pair<Array<ByteArray>, Array<ByteArray>> {54 val rng = Random(seed)55 val original = Array(data) { ByteArray(size) { rng.nextInt().toByte() } }56 val shards = Array(data + parity) { i -> if (i < data) original[i].copyOf() else ByteArray(size) }57 ReedSolomon.encode(shards, data, parity)58 return original to shards59 }6061 @Test fun rs_recovers_after_removing_one_shard() {62 val data = 6; val parity = computeParityShards(data)63 val (original, shards) = encodedShards(data, parity, 32, 1)64 val present = BooleanArray(data + parity) { true }65 shards[2] = ByteArray(0); present[2] = false66 ReedSolomon.reconstruct(shards, data, parity, present)67 for (i in 0 until data) assertArrayEquals(original[i], shards[i])68 }6970 @Test fun rs_recovers_after_removing_max_erasures() {71 val data = 6; val parity = computeParityShards(data)72 val (original, shards) = encodedShards(data, parity, 32, 2)73 val present = BooleanArray(data + parity) { true }74 75 repeat(parity) { shards[it] = ByteArray(0); present[it] = false }76 ReedSolomon.reconstruct(shards, data, parity, present)77 for (i in 0 until data) assertArrayEquals(original[i], shards[i])78 }7980 @Test fun rs_fails_beyond_max_erasures() {81 val data = 6; val parity = computeParityShards(data)82 val (_, shards) = encodedShards(data, parity, 32, 3)83 val present = BooleanArray(data + parity) { true }84 85 repeat(parity + 1) { shards[it] = ByteArray(0); present[it] = false }86 var thrown: CodexException? = null87 try {88 ReedSolomon.reconstruct(shards, data, parity, present)89 } catch (e: CodexException) {90 thrown = e91 }92 assertEquals(CodexErrorCode.INSUFFICIENT_CHUNKS, thrown?.code)93 }9495 9697 private fun payload(blobLen: Int) = CodexPayload(98 version = 2, verifyCode = "PX-AB12-CD34",99 vaultBlob = "v".repeat(blobLen), timestamp = 1, entryCount = 3,100 vaultFingerprint = "ABCD".repeat(8), generatedAt = 2,101 )102103 private fun encodePayload(p: CodexPayload): Pair<List<ChunkEnvelope>, CodexLayout> {104 val bytes = CodexCodec.json.encodeToString(CodexPayload.serializer(), p).toByteArray()105 return CodexCodec.encodeToEnvelopes(CodexCodec.deflate(bytes), p.verifyCode)106 }107108 @Test fun every_envelope_passes_integrity() {109 val (envelopes, layout) = encodePayload(payload(3000))110 assertTrue(envelopes.all { CodexCodec.verifyChunkIntegrity(it) })111 assertEquals(layout.dataChunks + layout.parityChunks, envelopes.size)112 }113114 @Test fun full_set_reconstructs_payload() {115 val p = payload(3000)116 val (envelopes, _) = encodePayload(p)117 val recovered = CodexCodec.reconstructCompressed(envelopes)118 val parsed = CodexCodec.json.decodeFromString(CodexPayload.serializer(), String(CodexCodec.inflate(recovered)))119 assertEquals(p, parsed)120 }121122 @Test fun large_blob_round_trips_through_envelopes() {123 val p = payload(40_000)124 val (envelopes, _) = encodePayload(p)125 val recovered = CodexCodec.reconstructCompressed(envelopes)126 val parsed = CodexCodec.json.decodeFromString(CodexPayload.serializer(), String(CodexCodec.inflate(recovered)))127 assertEquals(p, parsed)128 }129130 @Test fun tampered_envelope_fails_integrity() {131 val (envelopes, _) = encodePayload(payload(500))132 val tampered = envelopes.first().copy(d = CodexCodec.base64Url("evil".toByteArray()))133 assertFalse(CodexCodec.verifyChunkIntegrity(tampered))134 }135136 137138 @Test fun base64url_round_trips() {139 val rng = Random(9)140 val bytes = ByteArray(256) { rng.nextInt().toByte() }141 assertArrayEquals(bytes, CodexCodec.fromBase64Url(CodexCodec.base64Url(bytes)))142 }143144 @Test fun sha256_hex_is_stable_and_64_chars() {145 val h = CodexCodec.sha256Hex("peach".toByteArray())146 assertEquals(64, h.length)147 assertEquals(h, CodexCodec.sha256Hex("peach".toByteArray()))148 }149}