From af2404e61cf8aff2b34b832b5f782bddd99113e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 02:17:22 +0000 Subject: [PATCH] test(cordn): pin the on-disk format of the file stores Drives FileCordnGroupStore, FileCordnKeyPackageStore, FileCordnCoordinatorStore, FileCordnHandoffStore, EncryptedAppendLog and CordnMigrationStores through a representative sequence with a deterministic cipher, and asserts the exact tree of relative paths and the exact bytes of every file, captured from the current java.io implementation. A second case lays those pinned bytes down on a clean disk and reads them back through the stores, which is the direction an upgrade travels. The migration read also pins the base64 strings a handoff carries. This is the guard for moving the stores to okio in commonMain: the files hold encrypted MLS group state that cannot be re-derived, so the move must not change a single byte. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../cordn/CordnStorageFormatGoldenTest.kt | 532 ++++++++++++++++++ 1 file changed, 532 insertions(+) create mode 100644 commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/cordn/CordnStorageFormatGoldenTest.kt diff --git a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/cordn/CordnStorageFormatGoldenTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/cordn/CordnStorageFormatGoldenTest.kt new file mode 100644 index 0000000000..ebe242ad07 --- /dev/null +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/cordn/CordnStorageFormatGoldenTest.kt @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.cordn + +import com.vitorpamplona.amethyst.commons.storage.EncryptedAppendLog +import com.vitorpamplona.quartz.cordn.appMultiDevice.CordnCarriedKeyPackage +import com.vitorpamplona.quartz.cordn.spec02Envelopes.CordnDeliveredMessage +import com.vitorpamplona.quartz.cordn.spec02Envelopes.CordnDeliveredMessageCodec +import com.vitorpamplona.quartz.cordn.spec02Envelopes.CordnEnvelope +import com.vitorpamplona.quartz.cordn.sync.EchoState +import com.vitorpamplona.quartz.cordn.sync.GroupCursor +import com.vitorpamplona.quartz.cordn.sync.PendingEpochOperation +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer +import kotlinx.coroutines.test.runTest +import java.io.ByteArrayOutputStream +import java.io.File +import java.util.Base64 +import kotlin.io.path.createTempDirectory +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail + +/** + * The on-disk format of the cordn stores and of [EncryptedAppendLog], pinned + * byte for byte. + * + * These files hold a user's encrypted MLS group state and message history, and + * none of it can be re-derived from anywhere else on the device: a layout or + * framing change that the next build cannot read is a group the user has lost. + * So this drives every writer through a representative sequence — overwrites, + * deletes, dedup, appends, a fold, a legacy blob, a torn tail, a migration — + * and compares the resulting tree against constants captured from the + * implementation that shipped. It then goes the other way, laying those exact + * bytes down on a clean disk and reading them back, which is the direction an + * upgrade actually travels. + * + * The cipher is a deterministic stand-in (a tag byte plus XOR) so the bytes are + * stable; it also makes a segment's ciphertext one byte longer than its + * plaintext, so a length prefix that recorded the wrong one would show. + * + * If this fails, the format changed. Do not regenerate the constants to make it + * pass unless the change is deliberate and comes with a reader for the old one. + */ +class CordnStorageFormatGoldenTest { + /** Deterministic, reversible, and length-changing. */ + private class TaggedXorCipher : CordnBlobCipher { + override fun encrypt(bytes: ByteArray): ByteArray = byteArrayOf(TAG) + ByteArray(bytes.size) { (bytes[it].toInt() xor MASK).toByte() } + + override fun decrypt(bytes: ByteArray): ByteArray { + require(bytes.isNotEmpty() && bytes[0] == TAG) { "not ours" } + return ByteArray(bytes.size - 1) { (bytes[it + 1].toInt() xor MASK).toByte() } + } + + companion object { + const val TAG: Byte = 0x7C + const val MASK = 0x5A + } + } + + private val rootFile: File = createTempDirectory("cordn-golden").toFile() + private val cipher = TaggedXorCipher() + + private val account = "a".repeat(64) + private val coordinatorA = "b".repeat(64) + private val coordinatorB = "c".repeat(64) + private val handedBackAccount = "d".repeat(64) + private val migratedAccount = "e".repeat(64) + + // ---- Construction glue: the only part of this file that follows the stores' API. ---- + + private fun groupStore(coordinator: HexKey) = FileCordnGroupStore(CordnStorageLayout.directoryFor(rootFile, account, coordinator), cipher) + + private fun keyPackageStore(coordinator: HexKey) = FileCordnKeyPackageStore(CordnStorageLayout.directoryFor(rootFile, account, coordinator), cipher) + + private fun coordinatorStore() = FileCordnCoordinatorStore(CordnStorageLayout.accountDirectoryFor(rootFile, account), cipher) + + private fun handoffStore(owner: HexKey) = FileCordnHandoffStore(CordnStorageLayout.accountDirectoryFor(rootFile, owner)) + + private fun appendLog(compactAfterSegments: Int) = + EncryptedAppendLog( + encrypt = cipher::encrypt, + decrypt = { runCatching { cipher.decrypt(it) }.getOrNull() }, + compactAfterSegments = compactAfterSegments, + ) + + private fun logFile(name: String) = File(rootFile, "logs/$name") + + private suspend fun migrationWrite(snapshot: CordnMigrationSnapshot) = CordnMigrationStores.write(rootFile, migratedAccount, cipher, snapshot) + + private suspend fun migrationRead(configs: List) = CordnMigrationStores.read(rootFile, migratedAccount, cipher, configs) + + // ---- End of construction glue. ---- + + /** The test's own byte surgery, beneath the API under test. */ + private fun raw(relative: String) = File(rootFile, relative) + + @AfterTest + fun cleanUp() { + rootFile.deleteRecursively() + } + + private fun message( + content: String, + cursor: Long, + ) = CordnDeliveredMessage(CordnEnvelope.build("aa".repeat(32), 1_757_000_000L + cursor, 9, content = content), cursor = cursor) + + private val m1 = message("first", 1) + private val m2 = message("second ✓", 2) + private val m3 = message("third", 3) + + private fun relay(url: String) = RelayUrlNormalizer.normalizeOrNull(url)!! + + private val coordinators = + listOf( + CoordinatorConfig(coordinatorA, listOf(relay("wss://one.example.com")), CoordinatorConfig.Origin.MANUAL, "Work"), + CoordinatorConfig(coordinatorB, listOf(relay("wss://two.example.com"), relay("wss://three.example.com")), CoordinatorConfig.Origin.GROUP_REF), + ) + + private val room = CordnRoomState("héllo draft ✓", 42) + private val echoes = EchoState(listOf(PendingEpochOperation("c2VhbGVk", localStateApplied = false)), listOf(3L, 5L)) + + /** The pre-segment format: `uint32 count, (uint32 len, bytes)*`, encrypted as one blob. */ + private fun legacyPlain(vararg entries: String): ByteArray { + val out = ByteArrayOutputStream() + + fun int(v: Int) = out.write(byteArrayOf((v shr 24).toByte(), (v shr 16).toByte(), (v shr 8).toByte(), v.toByte())) + int(entries.size) + entries.forEach { + val bytes = it.encodeToByteArray() + int(bytes.size) + out.write(bytes) + } + return out.toByteArray() + } + + private fun b64(bytes: ByteArray) = Base64.getEncoder().encodeToString(bytes) + + private val migrationSnapshot = + CordnMigrationSnapshot( + accountPubKey = migratedAccount, + groups = + listOf( + CordnMigrationGroup( + coordinatorPubKey = coordinatorA, + coordinatorRelays = listOf("wss://coord.example"), + gid = "mg-1", + clientStateBase64 = b64("mls-state-1".encodeToByteArray()), + cursor = 77, + roomStateBase64 = b64(CordnRoomStateCodec.encode(CordnRoomState("migrated draft", 70))), + echoStateBase64 = b64(EchoStateCodec.encode(EchoState(ownMessageCursors = listOf(76L)))), + joinedViaRequest = true, + messages = listOf(CordnDeliveredMessageCodec.encode(m1), CordnDeliveredMessageCodec.encode(m2)), + ), + CordnMigrationGroup( + coordinatorPubKey = coordinatorB, + coordinatorRelays = listOf("wss://other.example"), + gid = "mg-2", + clientStateBase64 = b64(byteArrayOf(0, 1, 2, -1, -2)), + cursor = 0, + ), + ), + keyPackages = listOf(CordnCarriedKeyPackage(coordinatorA, "kp-ref", b64("bundle!".encodeToByteArray()))), + ) + + /** Every writer, through the paths that shape the tree: overwrite, delete, dedup, fold, repair. */ + private suspend fun drive() { + groupStore(coordinatorA).apply { + saveGroup("g1", "state-v1".encodeToByteArray()) + saveGroup("g1", "state-v2-longer".encodeToByteArray()) + saveCursor("g1", GroupCursor(fetchCursor = 0x0102030405060708L, lastCursor = 0x1112131415161718L)) + saveJoinedViaRequest("g1") + saveRoomState("g1", room) + saveEchoState("g1", echoes) + appendMessage("g1", m1) + appendMessage("g1", m2) + appendMessage("g1", m1) + appendMessage("g1", m3) + + saveGroup("../odd/gid ✓", byteArrayOf(9, 8, 7)) + + saveGroup("blanked", byteArrayOf(1)) + saveRoomState("blanked", room) + saveRoomState("blanked", CordnRoomState()) + saveEchoState("blanked", echoes) + saveEchoState("blanked", EchoState()) + + saveGroup("gone", byteArrayOf(2)) + saveCursor("gone", GroupCursor(5, 5)) + appendMessage("gone", m1) + deleteGroup("gone") + } + groupStore(coordinatorB).saveGroup("g1", "other-coordinator".encodeToByteArray()) + + keyPackageStore(coordinatorA).apply { + save("ref-1", "bundle-1".encodeToByteArray()) + save("ref/2", "bundle-2".encodeToByteArray()) + save("ref-3", "bundle-3".encodeToByteArray()) + delete("ref-3") + } + + coordinatorStore().save(coordinators) + + handoffStore(account).save(true) + handoffStore(handedBackAccount).save(true) + handoffStore(handedBackAccount).save(false) + + // Folding: a rewrite, three loose segments and a fold, then a restart + // that has to find the fold boundary in the header, not in memory. + appendLog(3).apply { (1..5).forEach { append(logFile("folding"), "e$it") } } + appendLog(3).apply { (6..7).forEach { append(logFile("folding"), "e$it") } } + + // Legacy: a headerless blob, rewritten into the segmented format by the next append. + raw("logs").mkdirs() + raw("logs/legacy").writeBytes(cipher.encrypt(legacyPlain("old-1", "old-2"))) + appendLog(200).append(logFile("legacy"), "new-1") + + // Torn tail: a half-written segment is cut off the file, and the next + // append lands after the last good one. + appendLog(200).apply { + append(logFile("torn"), "t1") + append(logFile("torn"), "t2") + } + raw("logs/torn").appendBytes(byteArrayOf(0, 0, 0, 0x40, 1, 2, 3)) + appendLog(200).apply { + assertEquals(listOf("t1", "t2"), readAll(logFile("torn"))) + append(logFile("torn"), "t3") + } + + appendLog(200).apply { + append(logFile("rewritten"), "r1") + append(logFile("rewritten"), "r2") + rewrite(logFile("rewritten"), listOf("x", "y")) + append(logFile("rewritten"), "z") + } + + // Migration: replaces the account's tree wholesale, stale files included. + raw("cordn/$migratedAccount/stale").apply { + parentFile.mkdirs() + writeBytes(byteArrayOf(1, 2, 3)) + } + val started = migrationWrite(migrationSnapshot) + assertEquals( + listOf( + CoordinatorConfig(coordinatorA, listOf(relay("wss://coord.example")), CoordinatorConfig.Origin.MANUAL), + CoordinatorConfig(coordinatorB, listOf(relay("wss://other.example")), CoordinatorConfig.Origin.MANUAL), + ), + started, + ) + } + + /** What every store reads back from the tree [drive] leaves — before an upgrade and after it. */ + private suspend fun assertReadsBack() { + groupStore(coordinatorA).apply { + assertEquals(listOf("../odd/gid ✓", "blanked", "g1"), listGroups().sorted()) + assertContentEquals("state-v2-longer".encodeToByteArray(), loadGroup("g1")) + assertEquals(GroupCursor(0x0102030405060708L, 0x1112131415161718L), loadCursor("g1")) + assertTrue(loadJoinedViaRequest("g1")) + assertEquals(room, loadRoomState("g1")) + assertEquals(echoes, loadEchoState("g1")) + assertEquals(listOf(m1, m2, m3), loadMessages("g1")) + assertEquals(CordnMessageSummary(m3, 3), loadMessageSummary("g1")) + assertContentEquals(byteArrayOf(9, 8, 7), loadGroup("../odd/gid ✓")) + assertFalse(loadJoinedViaRequest("blanked")) + assertEquals(CordnRoomState(), loadRoomState("blanked")) + assertEquals(EchoState(), loadEchoState("blanked")) + assertNull(loadGroup("gone")) + assertNull(loadCursor("gone")) + assertEquals(emptyList(), loadMessages("gone")) + } + assertEquals(listOf("g1"), groupStore(coordinatorB).listGroups()) + assertContentEquals("other-coordinator".encodeToByteArray(), groupStore(coordinatorB).loadGroup("g1")) + + keyPackageStore(coordinatorA).apply { + assertEquals(listOf("ref-1", "ref/2"), list().sorted()) + assertContentEquals("bundle-2".encodeToByteArray(), load("ref/2")) + assertNull(load("ref-3")) + } + + assertEquals(coordinators, coordinatorStore().load()) + assertTrue(handoffStore(account).load()) + assertFalse(handoffStore(handedBackAccount).load()) + + assertEquals((1..7).map { "e$it" }, appendLog(3).readAll(logFile("folding"))) + assertEquals(listOf("old-1", "old-2", "new-1"), appendLog(200).readAll(logFile("legacy"))) + assertEquals(listOf("t1", "t2", "t3"), appendLog(200).readAll(logFile("torn"))) + assertEquals(listOf("x", "y", "z"), appendLog(200).readAll(logFile("rewritten"))) + + // The migration document carries base64 of what the stores hold; the + // strings are pinned too, since another device decodes them. + val read = migrationRead(migrationSnapshot.groups.map { CoordinatorConfig(it.coordinatorPubKey, listOf(relay(it.coordinatorRelays.single()))) }) + assertEquals(migratedAccount, read.accountPubKey) + assertEquals( + listOf( + CordnMigrationGroup( + coordinatorPubKey = coordinatorA, + coordinatorRelays = listOf("wss://coord.example/"), + gid = "mg-1", + clientStateBase64 = "bWxzLXN0YXRlLTE=", + cursor = 77, + roomStateBase64 = "AAEADm1pZ3JhdGVkIGRyYWZ0AAAAAAAAAEY=", + echoStateBase64 = "AAEAAAAIAAAAAAAAAEw=", + joinedViaRequest = true, + messages = listOf(CordnDeliveredMessageCodec.encode(m1), CordnDeliveredMessageCodec.encode(m2)), + ), + CordnMigrationGroup( + coordinatorPubKey = coordinatorB, + coordinatorRelays = listOf("wss://other.example/"), + gid = "mg-2", + clientStateBase64 = "AAEC//4=", + cursor = 0, + roomStateBase64 = "AAEAAAAAAAAAAAAA", + echoStateBase64 = "AAEAAAAA", + joinedViaRequest = false, + messages = emptyList(), + ), + ), + read.groups.sortedBy { it.gid }, + ) + assertEquals(listOf(CordnCarriedKeyPackage(coordinatorA, "kp-ref", "YnVuZGxlIQ==")), read.keyPackages) + } + + @Test + fun `the stores write exactly the pinned files`() = + runTest { + drive() + val actual = tree(rootFile) + if (actual != EXPECTED) fail(mismatch(actual)) + assertReadsBack() + } + + @Test + fun `the pinned files read back as the values that wrote them`() = + runTest { + // The upgrade direction: files written by the build that shipped, + // read by this one. + EXPECTED.forEach { (path, hex) -> + val target = File(rootFile, path.removeSuffix("/")) + if (path.endsWith("/")) { + target.mkdirs() + } else { + target.parentFile.mkdirs() + target.writeBytes(hex.hexToByteArray()) + } + } + assertReadsBack() + } + + private fun tree(root: File): Map = + root + .walkTopDown() + .filter { it != root } + .associate { file -> + val relative = file.relativeTo(root).invariantSeparatorsPath + if (file.isDirectory) "$relative/" to "" else relative to file.readBytes().toHexString() + }.toSortedMap() + + /** The diff, plus the literal to paste if (and only if) the change is deliberate. */ + private fun mismatch(actual: Map): String = + buildString { + appendLine("the on-disk format changed") + (EXPECTED.keys - actual.keys).forEach { appendLine(" missing: $it") } + (actual.keys - EXPECTED.keys).forEach { appendLine(" unexpected: $it") } + actual.filter { (k, v) -> k in EXPECTED && EXPECTED[k] != v }.forEach { (k, _) -> appendLine(" bytes differ: $k") } + appendLine("---- actual ----") + actual.forEach { (path, hex) -> + appendLine(" \"$path\" to") + val chunks = hex.chunked(120).ifEmpty { listOf("") } + appendLine(chunks.joinToString(" +\n ", prefix = " ", postfix = ",") { "\"$it\"" }) + } + } + + companion object { + private val EXPECTED: Map = + sortedMapOf( + "cordn/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/Li4vb2RkL2dpZCDinJM/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/Li4vb2RkL2dpZCDinJM/state" to + "7c53525d", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/YmxhbmtlZA/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/YmxhbmtlZA/state" to + "7c5b", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/ZzE/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/ZzE/cursor" to + "7c5b58595e5f5c5d524b48494e4f4c4d42", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/ZzE/echoes" to + "7c5a5b5a515a5239680c32381d0c315a5a4a5a5a5a5a5a5a5a595a5a5a5a5a5a5a5f", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/ZzE/messages" to + "4d524d544c4f47330000000000000101000000ed7c5a5a5a5b5a5a5abe21782c78606b76783978606b76783f78602178333e7860783b6b6b6d6d6a6c" + + "696a6f6b3e3b6e686d6c393c3f6262396e696a6f3f623c3f3e38626c3f623e3f3e6f6863396d6b6f6c3f3b6f3e3b383c626b696a383e6e3e3b787678" + + "2a2f38313f237860783b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b" + + "3b3b3b3b3b3b3b3b3b3b3b3b3b78767839283f3b2e3f3e053b2e78606b6d6f6d6a6a6a6a6a6b76783133343e78606376782e3b3d2978600107767839" + + "35342e3f342e7860783c3328292e782727000000f27c5a5a5a5b5a5a5ab321782c78606b76783978606876783f78602178333e78607838696b6d6f3b" + + "636e3c3e6e6e3c6f69696c3f6e3f3e39393868396e3b6b6c6d636938696b3f38626e623f3e69686f69636d6c3b6e3e39683b636239396c6b3c3b7876" + + "782a2f38313f237860783b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b" + + "3b3b3b3b3b3b3b3b3b3b3b3b3b3b78767839283f3b2e3f3e053b2e78606b6d6f6d6a6a6a6a6a6876783133343e78606376782e3b3d29786001077678" + + "3935342e3f342e786078293f3935343e7ab8c6c9782727000000ed7c5a5a5a5b5a5a5abe21782c78606b76783978606976783f78602178333e786078" + + "6838636f3c3c3f3f6d633c3f6a6f6b3963396a6f6c6f6363393c396e6d6c393b3b3e3e696d3c6e3c6b6e3b3c396b626a6a636a6c6a38396d3b386e63" + + "396e6c697876782a2f38313f237860783b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b" + + "3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b78767839283f3b2e3f3e053b2e78606b6d6f6d6a6a6a6a6a6976783133343e78606376782e3b3d29" + + "7860010776783935342e3f342e7860782e3233283e782727", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/ZzE/newest" to + "7c5a5b5a5a5a595abe21782c78606b76783978606976783f78602178333e7860786838636f3c3c3f3f6d633c3f6a6f6b3963396a6f6c6f6363393c39" + + "6e6d6c393b3b3e3e696d3c6e3c6b6e3b3c396b626a6a636a6c6a38396d3b386e63396e6c697876782a2f38313f237860783b3b3b3b3b3b3b3b3b3b3b" + + "3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b78767839283f3b" + + "2e3f3e053b2e78606b6d6f6d6a6a6a6a6a6976783133343e78606376782e3b3d297860010776783935342e3f342e7860782e3233283e782727", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/ZzE/room" to + "7c5a5b5a4a3299f33636357a3e283b3c2e7ab8c6c95a5a5a5a5a5a5a70", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/ZzE/state" to + "7c292e3b2e3f772c68773635343d3f28", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/ZzE/via-request" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/keypackages/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/keypackages/cmVmLTE" to + "7c382f343e363f776b", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/keypackages/cmVmLzI" to + "7c382f343e363f7768", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc/groups/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc/groups/ZzE/" to + "", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc/groups/ZzE/state" to + "7c352e323f2877393535283e33343b2e3528", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/coordinators" to + "7c5a5b5a585a1a3838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838" + + "38383838383838383838385a5c171b140f1b165a5e0d3528315a5b5a4c2d292960757535343f743f223b372a363f74393537755a1a39393939393939" + + "3939393939393939393939393939393939393939393939393939393939393939393939393939393939393939393939393939393939393939395a531d" + + "08150f0a05081f1c5a5a5a585a4c2d29296075752e2d35743f223b372a363f74393537755a422d29296075752e32283f3f743f223b372a363f743935" + + "3775", + "cordn/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/handed-off" to + "", + "cordn/dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd/" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/bWctMQ/" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/bWctMQ/cursor" to + "7c5a5a5a5a5a5a5a175a5a5a5a5a5a5a17", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/bWctMQ/echoes" to + "7c5a5b5a5a5a525a5a5a5a5a5a5a16", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/bWctMQ/messages" to + "4d524d544c4f47330000000000000101000000ed7c5a5a5a5b5a5a5abe21782c78606b76783978606b76783f78602178333e7860783b6b6b6d6d6a6c" + + "696a6f6b3e3b6e686d6c393c3f6262396e696a6f3f623c3f3e38626c3f623e3f3e6f6863396d6b6f6c3f3b6f3e3b383c626b696a383e6e3e3b787678" + + "2a2f38313f237860783b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b" + + "3b3b3b3b3b3b3b3b3b3b3b3b3b78767839283f3b2e3f3e053b2e78606b6d6f6d6a6a6a6a6a6b76783133343e78606376782e3b3d2978600107767839" + + "35342e3f342e7860783c3328292e782727000000f27c5a5a5a5b5a5a5ab321782c78606b76783978606876783f78602178333e78607838696b6d6f3b" + + "636e3c3e6e6e3c6f69696c3f6e3f3e39393868396e3b6b6c6d636938696b3f38626e623f3e69686f69636d6c3b6e3e39683b636239396c6b3c3b7876" + + "782a2f38313f237860783b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b" + + "3b3b3b3b3b3b3b3b3b3b3b3b3b3b78767839283f3b2e3f3e053b2e78606b6d6f6d6a6a6a6a6a6876783133343e78606376782e3b3d29786001077678" + + "3935342e3f342e786078293f3935343e7ab8c6c9782727", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/bWctMQ/newest" to + "7c5a5b5a5a5a585ab321782c78606b76783978606876783f78602178333e78607838696b6d6f3b636e3c3e6e6e3c6f69696c3f6e3f3e39393868396e" + + "3b6b6c6d636938696b3f38626e623f3e69686f69636d6c3b6e3e39683b636239396c6b3c3b7876782a2f38313f237860783b3b3b3b3b3b3b3b3b3b3b" + + "3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b78767839283f3b" + + "2e3f3e053b2e78606b6d6f6d6a6a6a6a6a6876783133343e78606376782e3b3d297860010776783935342e3f342e786078293f3935343e7ab8c6c978" + + "2727", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/bWctMQ/room" to + "7c5a5b5a5437333d283b2e3f3e7a3e283b3c2e5a5a5a5a5a5a5a1c", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/bWctMQ/state" to + "7c37362977292e3b2e3f776b", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/groups/bWctMQ/via-request" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/keypackages/" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/keypackages/a3AtcmVm" to + "7c382f343e363f7b", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc/" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc/groups/" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc/groups/bWctMg/" to + "", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc/groups/bWctMg/cursor" to + "7c5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", + "cordn/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc/groups/bWctMg/state" to + "7c5a5b58a5a4", + "logs/" to + "", + "logs/folding" to + "4d524d544c4f473300000000000000550000000b7c5a5a5a5b5a5a5a583f6b000000177c5a5a5a595a5a5a583f685a5a5a583f695a5a5a583f6e0000" + + "00177c5a5a5a595a5a5a583f6f5a5a5a583f6c5a5a5a583f6d", + "logs/legacy" to + "4d524d544c4f47330000000000000034000000207c5a5a5a595a5a5a5f35363e776b5a5a5a5f35363e77685a5a5a5f343f2d776b", + "logs/rewritten" to + "4d524d544c4f473300000000000000230000000f7c5a5a5a585a5a5a5b225a5a5a5b230000000a7c5a5a5a5b5a5a5a5b20", + "logs/torn" to + "4d524d544c4f4733000000000000001f0000000b7c5a5a5a5b5a5a5a582e6b0000000b7c5a5a5a5b5a5a5a582e680000000b7c5a5a5a5b5a5a5a582e" + + "69", + ) + } +}