Files
flamenet-messenger-android/engine/src/test/kotlin/io/flamenet/e2e/LabelTest.kt
T
Paul HittandClaude Opus 5 7bd836636b The level picker explains itself, as it does on iOS
This screen showed chips and no explanation; iOS has carried one all along,
under a comment claiming both apps use the same words. They now do.

The last sentence is the boundary: a level decides who can open a file, not
where it can be put. Neither app stops somebody filing sensitive material
somewhere low, and saying so is the honest half of android#15.

Also repairs a comment the emoji sweep truncated, which left the test
saying 'the -property is NOT enforced'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 15:32:00 -04:00

170 lines
5.7 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package io.flamenet.e2e
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* The security lattice, and the two Bell–LaPadula rules it exists to enforce.
*/
class LabelTest {
private fun hex(bytes: ByteArray) = bytes.joinToString("") { "%02x".format(it) }
// ---- The encoding ----------------------------------------------------.
/** `int16be classification || uint64be compartment mask`. */
@Test
fun `the ten-byte encoding matches the specification`() {
assertEquals("00000000000000000000", hex(Label(0).bytes()))
assertEquals("00010000000000000000", hex(Label(1).bytes()))
assertEquals("00038000000000000001", hex(Label(3, setOf(0, 63)).bytes()))
assertEquals("01000000000000000100", hex(Label(256, setOf(8)).bytes()))
}
/**
* A negative classification and a full compartment mask together, the case
* where a sign-extension mistake or an `Int` in place of a `Long` shows up.
*/
@Test
fun `the extremes encode without sign-extension damage`() {
assertEquals("ffffffffffffffffffff", hex(Label(-1, (0..63).toSet()).bytes()))
}
/**
* Bit 63 is the sign bit of a Long.
*/
@Test
fun `the top compartment bit survives`() {
assertEquals("00008000000000000000", hex(Label(0, setOf(63)).bytes()))
assertTrue(Label(0, setOf(63)).mask != 0L)
}
@Test
fun `the encoding is always ten bytes`() {
assertEquals(10, Label(0).bytes().size)
assertEquals(10, Label(-1, (0..63).toSet()).bytes().size)
}
@Test
fun `a compartment outside the range is refused`() {
assertFailsWith<IllegalArgumentException> { Label(0, setOf(64)) }
assertFailsWith<IllegalArgumentException> { Label(0, setOf(-1)) }
}
// ---- Domination ------------------------------------------------------.
@Test
fun `a higher classification dominates a lower one`() {
assertTrue(Label(3).dominates(Label(1)))
assertFalse(Label(1).dominates(Label(3)))
assertTrue(Label(2).dominates(Label(2)))
}
/**
* The failure that reads as working.
*/
@Test
fun `a higher level does NOT dominate a compartment it is not in`() {
val topSecretNoAreas = Label(9)
val secretInArea = Label(5, setOf(2))
assertFalse(
topSecretNoAreas.dominates(secretInArea),
"a higher classification reached into a compartment it does not hold",
)
}
@Test
fun `domination needs a superset of areas, not an overlap`() {
val holder = Label(5, setOf(1, 2))
assertTrue(holder.dominates(Label(5, setOf(1))))
assertTrue(holder.dominates(Label(5, setOf(1, 2))))
assertFalse(
holder.dominates(Label(5, setOf(1, 3))),
"an overlapping but incomplete set was treated as domination",
)
}
/** Two labels can be incomparable, neither dominates the other. */
@Test
fun `disjoint compartments are incomparable in both directions`() {
val a = Label(5, setOf(1))
val b = Label(5, setOf(2))
assertFalse(a.dominates(b))
assertFalse(b.dominates(a))
}
@Test
fun `bottom is dominated by everything`() {
assertTrue(Label(0).dominates(Label.bottom))
assertTrue(Label(9, setOf(3)).dominates(Label.bottom))
}
// ---- Clearance: no read up -------------------------------------------.
@Test
fun `a clearance reads what it dominates`() {
val cleared = Clearance(maximum = Label(5, setOf(1, 2)))
assertTrue(cleared.mayRead(Label(3, setOf(1))))
assertTrue(cleared.mayRead(Label(5, setOf(1, 2))))
}
@Test
fun `a clearance cannot read above itself`() {
val cleared = Clearance(maximum = Label(5))
assertFalse(cleared.mayRead(Label(6)))
assertFalse(
cleared.mayRead(Label(5, setOf(1))),
"a compartment outside the clearance was readable",
)
}
/**
* The minimum is a real bound, not decoration.
*/
@Test
fun `a clearance with a floor cannot read below it`() {
val cleared = Clearance(maximum = Label(5), minimum = Label(2))
assertTrue(cleared.mayRead(Label(3)))
assertFalse(cleared.mayRead(Label(1)), "a label below the floor was readable")
}
// ---- Writing ---------------------------------------------------------.
/**
* Writing is gated by the same range check as reading, and no-write-down is
* NOT enforced, on either platform. Mirrored by ClearanceTests on the Swift
* side; the two change together or not at all.
*/
@Test
fun `writing uses the same range check as reading, star-property not enforced`() {
val cleared = Clearance(maximum = Label(5))
assertTrue(cleared.mayWrite(Label(5)))
assertTrue(
cleared.mayWrite(Label(1)),
"the star-property is now enforced, see android#15, and update the " +
"iOS side and this test together",
)
assertFalse(cleared.mayWrite(Label(8)))
assertFalse(cleared.mayRead(Label(8)))
}
@Test
fun `a floor bounds writing as well as reading`() {
val cleared = Clearance(maximum = Label(5), minimum = Label(2))
assertTrue(cleared.mayWrite(Label(3)))
assertFalse(cleared.mayWrite(Label(1)))
}
@Test
fun `a compartment outside the clearance cannot be written either`() {
val cleared = Clearance(maximum = Label(5, setOf(1)))
assertTrue(cleared.mayWrite(Label(5, setOf(1))))
assertFalse(
cleared.mayWrite(Label(5, setOf(1, 2))),
"a compartment outside the clearance was writable",
)
}
}