JaCoCo was not configured on either JVM module, so the Android numbers were unknown rather than good. :engine and :client are plain Kotlin/JVM precisely so they can be measured without an emulator; now they are. Baseline: engine 84.9% lines, client 47.3%. Vault was the outlier in engine at 53%, and its uncovered half was almost entirely save/load pairs — credentials, trust pins, the delivery key, the key-log pins, the roster, avatars, per-line deletion. That is not an arbitrary gap. It is the exact shape of a bug that has already shipped in this app twice. `requireKeyLog` was computed, held in memory and never written down, so a security control the UI offered silently reverted on every relaunch. `Trust.changed` says the same thing in its own doc comment: returned by `record()`, persisted by nothing, so `state()` could never answer CHANGED, the "Accept new keys" button could never render, and a contact who reinstalled became permanently unmessageable while the header insisted nothing had changed. Neither was hard to write. Both were invisible because a save and a load that disagree still compile and only differ after a restart, which no test performed. Every test here writes with one Vault and reads with a second built over the same bytes, because that is the only arrangement in which the two halves are forced to agree. Includes the AAD binding the class doc promises: a trust record copied into another contact's slot must not open, or someone with file access could attribute a stranger's keys to someone you trust. Vault 53.0% -> 84.9% lines, engine 84.9% -> 92.3%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
2.9 KiB
Kotlin
71 lines
2.9 KiB
Kotlin
plugins {
|
|
kotlin("jvm")
|
|
jacoco
|
|
}
|
|
|
|
repositories { mavenCentral() }
|
|
|
|
dependencies {
|
|
testImplementation(kotlin("test"))
|
|
// `org.json` is part of the Android framework, so on a phone this costs
|
|
// zero shipped bytes and is `compileOnly` for that reason -- bundling a
|
|
// second copy would shadow the platform's and is a classic duplicate-class
|
|
// build failure. On the JVM (tests, the interop driver) it has to be a real
|
|
// dependency.
|
|
//
|
|
// Safe here only because nothing security-relevant depends on key order:
|
|
// Android's implementation preserves insertion order and the reference one
|
|
// does not, and the two bytes-must-match encodings in this engine -- the
|
|
// ratchet header and the canonical log leaf -- are both built by hand.
|
|
compileOnly("org.json:json:20240303")
|
|
testImplementation("org.json:json:20240303")
|
|
|
|
// The engine's ONE real dependency, and it is here because the platform
|
|
// leaves a hole rather than because it was convenient.
|
|
//
|
|
// Android serves Ed25519 key generation from `AndroidKeyStore` and nowhere
|
|
// else -- probed on API 35, see ProviderProbeTest -- and Keystore only ever
|
|
// makes keys it keeps. This engine needs Ed25519 keys it holds: a device
|
|
// identity that lives in our own vault, and an account key derived from a
|
|
// passphrase. Neither can be a Keystore key by definition.
|
|
//
|
|
// That left implementing RFC 8032 by hand, which for SIGNING means
|
|
// non-constant-time arithmetic over a secret scalar, or taking a vetted
|
|
// constant-time one. Only the low-level `math.ec.rfc8032.Ed25519` class is
|
|
// used -- not the JCA provider, which is awkward to register on Android --
|
|
// so R8 shrinks nearly all of it away.
|
|
implementation("org.bouncycastle:bcprov-jdk18on:1.79")
|
|
}
|
|
|
|
// Bytecode level 17, which is what Android's toolchain consumes, produced by
|
|
// whatever JDK happens to be installed. A `jvmToolchain(17)` here would demand
|
|
// a JDK 17 on every machine that builds this and fail on anything newer, which
|
|
// is a poor trade for a library with no JDK 17-specific behaviour.
|
|
kotlin {
|
|
compilerOptions {
|
|
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
|
}
|
|
}
|
|
|
|
java {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
tasks.test {
|
|
useJUnitPlatform()
|
|
testLogging {
|
|
events("passed", "failed", "skipped")
|
|
showStandardStreams = true
|
|
}
|
|
}
|
|
|
|
// Coverage for this module. `:engine` and `:client` are plain JVM libraries
|
|
// precisely so they can be measured and tested without an emulator, so there is
|
|
// no reason for their numbers to be a guess.
|
|
tasks.withType<Test>().configureEach { finalizedBy(tasks.named("jacocoTestReport")) }
|
|
tasks.named<JacocoReport>("jacocoTestReport") {
|
|
dependsOn(tasks.named("test"))
|
|
reports { xml.required.set(true); html.required.set(true) }
|
|
}
|