mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Two halves of the same gap in `transports/quic.md`. **The direct path.** The binding has a second delivery mode we had not built: the sender dials the receiver, opens one unidirectional stream and writes records with no control envelope at all. It is deliberately smaller than the broker path — the dialed endpoint is already the one receiver, so there is no room to claim — and it negotiates its own ALPN so an incompatible change to either mode cannot reach the other. Note the inverted direction: here the RECEIVER listens and the SENDER dials, which is also why v1 gives it no start-payload discovery and it is only usable against an endpoint known out of band. Only the sending half is here. `:quic` is a client stack with no server role, so this module can dial a direct receiver but cannot be one; that is recorded in the README rather than half-built. **The pin.** Preview endpoints and brokers are commonly self-signed and the binding expects that, saying a client MAY pin by exact DER or SHA-256 fingerprint. What we had instead was `PermissiveCertificateValidator` on the CLI path, which is not a weaker trust model — it is none, and anyone on the path can be the broker. `PinnedCertificateValidator` replaces the chain and the hostname check and nothing else: the peer still has to sign the TLS transcript with the pinned certificate's private key, so copying a public certificate off the wire buys an attacker nothing. `amy marmot stream send|watch` takes `--pin-sha256`, and `--insecure` still exists for a throwaway local broker but now has to be asked for by name. Both are verified against the reference implementation, which is the only thing that can tell an ALPN string, a stream direction, an absent envelope and a frame prefix from an implementation agreeing with itself: our direct sender against `wn stream receive`, and the pin — accepted and refused — against a real handshake with `marmot-quic-broker`. One thing that only showed up under a real handshake: a certificate the validator refuses closes the connection before it is established, and the transport was reporting that as PeerClosed. A caller walking a candidate list reads that kind to decide what to do next, and "never connected" is not "the peer hung up on us", so it is classified on the connection's actual status now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kCuA6tc4JQzHPCDd39GHq
89 lines
2.4 KiB
Kotlin
89 lines
2.4 KiB
Kotlin
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
|
|
plugins {
|
|
alias(libs.plugins.kotlinMultiplatform)
|
|
alias(libs.plugins.androidKotlinMultiplatformLibrary)
|
|
}
|
|
|
|
kotlin {
|
|
jvm {
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_21)
|
|
}
|
|
}
|
|
|
|
android {
|
|
namespace = "com.vitorpamplona.marmotquic"
|
|
compileSdk =
|
|
libs.versions.android.compileSdk
|
|
.get()
|
|
.toInt()
|
|
minSdk =
|
|
libs.versions.android.minSdk
|
|
.get()
|
|
.toInt()
|
|
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_21)
|
|
}
|
|
|
|
withHostTest {}
|
|
}
|
|
|
|
sourceSets {
|
|
commonMain {
|
|
dependencies {
|
|
implementation(libs.kotlin.stdlib)
|
|
implementation(libs.kotlinx.coroutines.core)
|
|
api(project(":quartz"))
|
|
implementation(project(":quic"))
|
|
}
|
|
}
|
|
|
|
commonTest {
|
|
dependencies {
|
|
implementation(libs.kotlin.test)
|
|
implementation(libs.kotlinx.coroutines.test)
|
|
}
|
|
}
|
|
|
|
val jvmAndroid =
|
|
create("jvmAndroid") {
|
|
dependsOn(commonMain.get())
|
|
}
|
|
|
|
jvmMain {
|
|
dependsOn(jvmAndroid)
|
|
}
|
|
|
|
androidMain {
|
|
dependsOn(jvmAndroid)
|
|
}
|
|
|
|
jvmTest {
|
|
dependencies {
|
|
implementation(libs.kotlin.test)
|
|
implementation(libs.kotlinx.coroutines.test)
|
|
implementation(libs.secp256k1.kmp.jni.jvm)
|
|
}
|
|
}
|
|
|
|
getByName("androidHostTest") {
|
|
dependencies {
|
|
implementation(libs.kotlin.test)
|
|
implementation(libs.kotlinx.coroutines.test)
|
|
implementation(libs.secp256k1.kmp.jni.jvm)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Forward the interop opt-ins from the Gradle JVM to the test workers.
|
|
// Without this, `-DmarmotQuicBroker=...` / `-DmarmotWn=...` never reach the
|
|
// tests and every interop case silently skips. Mirrors `:nestsClient`.
|
|
tasks.withType<Test>().configureEach {
|
|
System.getProperty("marmotQuicBroker")?.let { systemProperty("marmotQuicBroker", it) }
|
|
System.getProperty("marmotQuicBrokerPin")?.let { systemProperty("marmotQuicBrokerPin", it) }
|
|
System.getProperty("marmotWn")?.let { systemProperty("marmotWn", it) }
|
|
}
|