Test the mailbox state whose keys a stranger chooses
Two of these keys are not ours. An envelope id comes from the sender, so an unbounded list of them is storage anybody on the federation can grow; a relay name comes from a signed record, so a per-relay entry can be created by anyone who can get one published. Both are bounded or scoped deliberately, which is worth asserting rather than trusting to a comment — and the bound is now proven at 2,500 ids. Also pinned: each relay's cursor is its own (ids come from different databases, and one shared cursor would let the first mailbox's progress skip the second's mail), the served flag and the head pin are per relay, an existing global pin is still inherited so a phone already holding one keeps it, and dropping a mailbox takes its cursor with it — otherwise a re-added mailbox resumes mid-inbox and skips everything before. One of these tests asserted that two objects were not the same object, which is true of any two objects. It now writes with one vault and reads with a second over the same storage, which is what a restart is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ca49eedade
commit
356c89e1cd
@@ -0,0 +1,183 @@
|
||||
package io.flamenet.e2e
|
||||
|
||||
import javax.crypto.KeyGenerator
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* What the vault remembers about several mailboxes.
|
||||
*
|
||||
* 🔴 **Two of these keys are chosen by whoever writes to you.** An envelope id
|
||||
* comes from the sender, so an unbounded list of them is storage anybody on the
|
||||
* federation can grow — and a relay name comes from a signed record, so a
|
||||
* per-relay entry can be created by anyone who can get a record published. Both
|
||||
* are bounded or scoped deliberately, and that is worth asserting rather than
|
||||
* trusting to a comment.
|
||||
*/
|
||||
class MailboxStateTest {
|
||||
|
||||
private class MapStorage : Vault.Storage {
|
||||
val records = LinkedHashMap<String, ByteArray>()
|
||||
override fun read(name: String) = records[name]
|
||||
override fun write(name: String, bytes: ByteArray) { records[name] = bytes }
|
||||
override fun delete(name: String) { records.remove(name) }
|
||||
override fun names() = records.keys.toList()
|
||||
}
|
||||
|
||||
private val storage = MapStorage()
|
||||
/// Held so a SECOND vault can be opened over the same storage — which is
|
||||
/// what a restart is, and the only way to test that anything persisted.
|
||||
private val key = KeyGenerator.getInstance("AES").apply { init(256) }.generateKey()
|
||||
private val vault = Vault(storage, key)
|
||||
|
||||
// MARK: - Envelope ids
|
||||
|
||||
@Test
|
||||
fun `an envelope is remembered once and recognised again`() {
|
||||
assertFalse(vault.hasSeenEnvelope("env-1"))
|
||||
vault.rememberEnvelope("env-1")
|
||||
assertTrue(vault.hasSeenEnvelope("env-1"))
|
||||
assertFalse(vault.hasSeenEnvelope("env-2"))
|
||||
}
|
||||
|
||||
/**
|
||||
* It survives a restart, or a copy at a backup home is read twice on the
|
||||
* first poll after every launch.
|
||||
*
|
||||
* ⚠️ Written with one vault and read with a SECOND over the same storage,
|
||||
* which is what a restart actually is. The first version of this test
|
||||
* asserted that two objects were not the same object, which is true of any
|
||||
* two objects and proves nothing about persistence.
|
||||
*/
|
||||
@Test
|
||||
fun `what was seen survives reopening the vault`() {
|
||||
vault.rememberEnvelope("env-1")
|
||||
|
||||
val restarted = Vault(storage, key)
|
||||
assertTrue(restarted.hasSeenEnvelope("env-1"), "the dedupe list did not persist")
|
||||
assertFalse(restarted.hasSeenEnvelope("env-2"))
|
||||
}
|
||||
|
||||
/** The same for everything else a mailbox needs across a launch. */
|
||||
@Test
|
||||
fun `mailboxes and their cursors survive reopening the vault`() {
|
||||
vault.saveExtraHomes(listOf(Vault.ExtraHome("a.example.com", "me", "pw")))
|
||||
vault.setWatermarkFor("a.example.com", 900)
|
||||
vault.rememberKeyLogServed("a.example.com")
|
||||
|
||||
val restarted = Vault(storage, key)
|
||||
assertEquals(listOf("a.example.com"), restarted.extraHomes().map { it.relay })
|
||||
assertEquals(900, restarted.watermarkFor("a.example.com"))
|
||||
assertTrue(restarted.keyLogServed("a.example.com"))
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 **The key comes from whoever wrote to us.** Oldest first, so a
|
||||
* forgotten id can at worst show a very old message twice — far better than
|
||||
* a list anybody can grow without limit.
|
||||
*/
|
||||
@Test
|
||||
fun `the list of seen envelopes is bounded`() {
|
||||
for (i in 0 until 2_500) vault.rememberEnvelope("env-$i")
|
||||
|
||||
assertTrue(vault.hasSeenEnvelope("env-2499"), "the newest must be remembered")
|
||||
assertFalse(vault.hasSeenEnvelope("env-0"), "the list grew without limit")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remembering the same id twice does not grow the list`() {
|
||||
repeat(50) { vault.rememberEnvelope("env-same") }
|
||||
vault.rememberEnvelope("env-other")
|
||||
assertTrue(vault.hasSeenEnvelope("env-same"))
|
||||
assertTrue(vault.hasSeenEnvelope("env-other"))
|
||||
}
|
||||
|
||||
// MARK: - Per-relay state
|
||||
|
||||
/**
|
||||
* 🔴 Envelope ids come from each relay's own database, so id 40 there says
|
||||
* nothing about id 40 here. One shared cursor would let the first mailbox's
|
||||
* progress silently skip the second's mail.
|
||||
*/
|
||||
@Test
|
||||
fun `each relay keeps its own watermark`() {
|
||||
vault.setWatermarkFor("a.example.com", 900)
|
||||
assertEquals(900, vault.watermarkFor("a.example.com"))
|
||||
assertEquals(0, vault.watermarkFor("b.example.com"))
|
||||
assertEquals(0, vault.inboxWatermark, "the signed-in relay's cursor is separate again")
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 A single global flag meant that signing in to a second relay which
|
||||
* keeps no log read as the FIRST going quiet — a fork warning against an
|
||||
* honest deployment, and every contact on it unreachable.
|
||||
*/
|
||||
@Test
|
||||
fun `the served flag is per relay`() {
|
||||
assertFalse(vault.keyLogServed("a.example.com"))
|
||||
vault.rememberKeyLogServed("a.example.com")
|
||||
assertTrue(vault.keyLogServed("a.example.com"))
|
||||
assertFalse(vault.keyLogServed("b.example.com"))
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ The pin falls back to the pre-2026-08-30 global entry, so a phone
|
||||
* already holding one keeps it rather than starting trust-on-first-use
|
||||
* again — and a per-relay entry, once written, wins.
|
||||
*/
|
||||
@Test
|
||||
fun `an existing global pin is inherited until a per-relay one is written`() {
|
||||
vault.saveKeyLogPin(Vault.KeyLogPin(4, "old-root"))
|
||||
assertEquals("old-root", vault.loadKeyLogPin("a.example.com")?.root)
|
||||
|
||||
vault.saveKeyLogPin(Vault.KeyLogPin(9, "new-root"), "a.example.com")
|
||||
assertEquals("new-root", vault.loadKeyLogPin("a.example.com")?.root)
|
||||
assertEquals("old-root", vault.loadKeyLogPin("b.example.com")?.root,
|
||||
"another relay still inherits the legacy pin, not this one")
|
||||
}
|
||||
|
||||
// MARK: - Extra mailboxes
|
||||
|
||||
/**
|
||||
* ⚠️ **A credential per relay, not one shared.** An app password is minted
|
||||
* by the relay that issued it and refused everywhere else, so a single
|
||||
* stored credential authenticates at exactly one home and silently fails at
|
||||
* the rest — which reads as "the backup mailbox never has anything in it".
|
||||
*/
|
||||
@Test
|
||||
fun `each mailbox keeps its own credential and dial url`() {
|
||||
vault.saveExtraHomes(listOf(
|
||||
Vault.ExtraHome("a.example.com", "me", "pw-a"),
|
||||
Vault.ExtraHome("b.example.com", "me", "pw-b", "http://127.0.0.1:9"),
|
||||
))
|
||||
|
||||
val homes = vault.extraHomes()
|
||||
assertEquals(listOf("a.example.com", "b.example.com"), homes.map { it.relay })
|
||||
assertEquals(listOf("pw-a", "pw-b"), homes.map { it.appPassword })
|
||||
// The record names a hostname and cannot carry a port; how this device
|
||||
// dials that host is a separate field with a sensible default.
|
||||
assertEquals("https://a.example.com", homes[0].baseUrl)
|
||||
assertEquals("http://127.0.0.1:9", homes[1].baseUrl)
|
||||
}
|
||||
|
||||
/** Dropping one takes its cursor with it, or a re-add resumes mid-inbox. */
|
||||
@Test
|
||||
fun `forgetting a mailbox clears its cursor too`() {
|
||||
vault.saveExtraHomes(listOf(Vault.ExtraHome("a.example.com", "me", "pw")))
|
||||
vault.setWatermarkFor("a.example.com", 900)
|
||||
|
||||
vault.forgetExtraHome("a.example.com")
|
||||
|
||||
assertTrue(vault.extraHomes().isEmpty())
|
||||
assertEquals(0, vault.watermarkFor("a.example.com"),
|
||||
"a re-added mailbox would resume mid-inbox and skip everything before it")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an account with no extra mailboxes reads as none`() {
|
||||
assertTrue(vault.extraHomes().isEmpty())
|
||||
assertEquals(0, vault.watermarkFor("anything.example.com"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user