Files
flamenet-messenger-android/client/build.gradle.kts
T
Paul HittandClaude Opus 5 dd412b8680 obfs4 for Android, part 7: the OkHttp seam, off by default
RelayClient.defaultHttpClient(bridge) now carries every request inside
obfs4 when a bridge is configured. The parameter defaults to null, which
is the shipping configuration: nothing changes for anyone who does not
ask for it.

A real HTTP request now travels through obfs4 end to end via OkHttp.
That test is deliberately Kotlin on both ends, and says so: the wire
format is already pinned to real lyrebird in both directions by
InteropTest. What is unproven until here is the plumbing -- that the
handshake happens before any stream is handed out, that bytes arriving
alongside the handshake are not dropped, and that a frame decoding to
nothing does not read as end-of-stream. Each of those loses data without
producing a wrong byte anywhere.

The handshake runs inside connect(), not on first write. OkHttp starts
writing a request the moment it has a connected socket, so a deferred
handshake would interleave with request bytes and desynchronise the frame
decoder on the first request of every connection.

Obfs4Socket ignores the endpoint OkHttp asks for. That endpoint is the
RELAY's address; the socket goes to the bridge, which proxies on.
Connecting to the endpoint would defeat the entire transport while
appearing to work.

All five SocketFactory overloads are implemented. Leaving any of them
returning a plain socket means some path silently bypasses the transport
-- and the traffic still works, which is exactly why nobody would notice.

A wrong bridge key fails as a refusal rather than hanging. obfs4 bridges
stay silent for anyone they cannot verify, so without the timeout this is
an app that never finishes a request.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 13:39:24 -04:00

54 lines
1.8 KiB
Kotlin

// The relay transport.
//
// Plain Kotlin/JVM like `:engine`, and for the same reason: HTTP against a
// relay has nothing to do with Android, and keeping it off the platform means
// its tests run against a real local server on any machine, with no emulator.
// OkHttp works identically on both.
plugins {
kotlin("jvm")
jacoco
}
repositories { mavenCentral() }
dependencies {
// The obfuscated transport. Only reached when a bridge is configured; with
// none set, nothing in here runs and `defaultHttpClient()` is unchanged.
implementation(project(":obfs4"))
api(project(":engine"))
implementation("com.squareup.okhttp3:okhttp:4.12.0")
compileOnly("org.json:json:20240303")
testImplementation(kotlin("test"))
testImplementation("org.json:json:20240303")
// A real HTTP server, so the tests exercise headers, status codes and
// bodies rather than a hand-written fake that agrees with the client about
// everything including its mistakes.
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
}
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") }
}
// 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) }
}