mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-18 07:34:36 +03:00
8221d4280b
Rolls up #150 + #151 + #153 (all merged on main) into a tagged release. Highlights: - Full Tunnel mode opens new HTTPS connections ~500-2000 ms faster (#153). The new connect_data tunnel op bundles the client's first bytes (typically TLS ClientHello) with the CONNECT call, eliminating one full Apps Script round trip per new flow. Backward compat is handled via UNSUPPORTED_OP detection + sticky AtomicBool fallback + pending_client_data replay so older deployments keep working without byte loss. New `connect_data preread: X win / Y loss / Z skip` metric in logs lets us measure the win ratio empirically. - Android ONLY-mode split fix (#150): when the allow-list contained only mhrv-rs or stale uninstalled packages, every addAllowedApplication call silently failed and Android applied the TUN to every app — looping our own proxy traffic. Now we count successful adds; if zero, we fall back to ALL-mode self-exclusion. Complements PR #143 which fixed the empty-list case. - Memory-safety cap on relay range stitching (#151): a hostile or buggy origin could advertise an absurd Content-Range total (e.g. 10 GiB) and force range-parallel to plan millions of chunks and preallocate a huge stitched buffer. Now capped at 64 MiB; larger totals fall back to a normal single GET. Tests: 91 lib tests pass (was 82; +1 from #151, +8 from #153). Tunnel-node: 6 tests pass (all new from #153). Local Android build verified — universal + four per-ABI APKs all produced at expected sizes (universal 53 MB, arm64-v8a 21 MB, armeabi-v7a 18 MB, x86_64 23 MB, x86 22 MB). Installed on mhrv_test emulator, app launches and renders correctly with v1.4.0 in title. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
221 lines
8.7 KiB
Kotlin
221 lines
8.7 KiB
Kotlin
import org.gradle.api.tasks.Exec
|
|
|
|
plugins {
|
|
id("com.android.application")
|
|
id("org.jetbrains.kotlin.android")
|
|
id("org.jetbrains.kotlin.plugin.compose")
|
|
}
|
|
|
|
android {
|
|
namespace = "com.therealaleph.mhrv"
|
|
compileSdk = 34
|
|
|
|
defaultConfig {
|
|
applicationId = "com.therealaleph.mhrv"
|
|
minSdk = 24 // Android 7.0 — covers 99%+ of live devices.
|
|
targetSdk = 34
|
|
versionCode = 136
|
|
versionName = "1.4.0"
|
|
|
|
// Ship all four mainstream Android ABIs:
|
|
// - arm64-v8a — 95%+ of real-world Android phones since 2019
|
|
// - armeabi-v7a — older/cheaper devices still on 32-bit ARM
|
|
// - x86_64 — Android emulator on Intel Macs + Chromebooks
|
|
// - x86 — legacy 32-bit Intel emulator; cheap to include
|
|
// Per-ABI .so files push the APK up to ~50 MB, but users expect one
|
|
// APK that Just Works rather than "pick the right ABI" which nobody
|
|
// does correctly. Google Play would auto-split; we ship universal.
|
|
ndk {
|
|
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
|
|
}
|
|
}
|
|
|
|
signingConfigs {
|
|
create("release") {
|
|
// Committed keystore — fixed signature across machines and
|
|
// across CI runs. Using the auto-generated debug keystore
|
|
// (as v1.0.0 / v1.0.1 did) makes every release APK fail to
|
|
// install over the previous one with
|
|
// INSTALL_FAILED_UPDATE_INCOMPATIBLE, because Android treats
|
|
// a signature change as "different app": the user has to
|
|
// uninstall first. That's awful UX.
|
|
//
|
|
// The password is in plaintext because this is an
|
|
// open-source project without Play Store identity. A
|
|
// forked/rebuilt APK signed with a different key is
|
|
// fundamentally a different install path anyway — the
|
|
// protection model here is "trust the source tree you
|
|
// pulled from," not "trust that we hold a key you can't
|
|
// see." If you're forking, generate your own key, commit
|
|
// it, and ship.
|
|
storeFile = file("release.jks")
|
|
storePassword = "mhrv-rs-release"
|
|
keyAlias = "mhrv-rs"
|
|
keyPassword = "mhrv-rs-release"
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = false
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro",
|
|
)
|
|
signingConfig = signingConfigs.getByName("release")
|
|
}
|
|
}
|
|
|
|
// Per-ABI APK splits in addition to the universal APK.
|
|
//
|
|
// Issue #136: GitHub Releases is filtered from inside IR, and the
|
|
// universal APK (~50 MB, all four ABIs bundled) is the bottleneck —
|
|
// users on slow or unstable censorship-tunnel paths often can't
|
|
// pull down 50 MB reliably. Per-ABI APKs are ~15 MB each (only one
|
|
// copy of libmhrv_rs.so + libtun2proxy.so instead of four), which
|
|
// is small enough to succeed where the universal fails.
|
|
//
|
|
// Keeping the universal APK too (`isUniversalApk = true`) because
|
|
// existing download paths / docs / Telegram mirrors all reference
|
|
// the universal name — removing it would break every link in the
|
|
// wild. The per-ABI outputs are additive.
|
|
splits {
|
|
abi {
|
|
isEnable = true
|
|
reset()
|
|
include("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
|
|
isUniversalApk = true
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
kotlinOptions {
|
|
jvmTarget = "17"
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
buildConfig = true
|
|
}
|
|
|
|
// libmhrv_rs.so is produced by `cargo ndk` in the repo root and dropped
|
|
// under app/src/main/jniLibs/<abi>/. The cargoBuild task below runs
|
|
// that before each assembleDebug / assembleRelease.
|
|
sourceSets["main"].jniLibs.srcDirs("src/main/jniLibs")
|
|
|
|
packaging {
|
|
resources.excludes += setOf(
|
|
"META-INF/AL2.0",
|
|
"META-INF/LGPL2.1",
|
|
)
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
val composeBom = platform("androidx.compose:compose-bom:2024.06.00")
|
|
implementation(composeBom)
|
|
androidTestImplementation(composeBom)
|
|
|
|
implementation("androidx.core:core-ktx:1.13.1")
|
|
implementation("androidx.activity:activity-compose:1.9.0")
|
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.2")
|
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2")
|
|
// AppCompatDelegate.setApplicationLocales is the only thing we need
|
|
// out of AppCompat — lets us flip the whole app locale at runtime
|
|
// from MhrvApp.onCreate without touching every composable.
|
|
implementation("androidx.appcompat:appcompat:1.7.0")
|
|
|
|
// Compose UI.
|
|
implementation("androidx.compose.ui:ui")
|
|
implementation("androidx.compose.ui:ui-graphics")
|
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
|
implementation("androidx.compose.material3:material3")
|
|
implementation("androidx.compose.material:material-icons-extended")
|
|
|
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
|
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Cross-compile the Rust crate to arm64 Android and drop the .so into the
|
|
// place Android's packager looks. We hand the work off to `cargo ndk` which
|
|
// wraps the right CC / AR / linker env vars for us.
|
|
//
|
|
// This ties to the `assemble*` task so every debug/release build triggers
|
|
// a `cargo ndk` — no manual step. In CI we'd cache the target/ dir to
|
|
// avoid full rebuilds.
|
|
// --------------------------------------------------------------------------
|
|
val rustCrateDir = rootProject.projectDir.parentFile
|
|
val jniLibsDir = file("src/main/jniLibs")
|
|
|
|
// After cargo-ndk dumps artifacts into each jniLibs/<abi>/ dir, the
|
|
// tun2proxy cdylib lands as `libtun2proxy-<hash>.so` (rustc's deps/ naming
|
|
// convention, because tun2proxy is a transitive dep not a root crate).
|
|
// Android's System.loadLibrary expects a stable name, and the hash changes
|
|
// between builds, so we normalize it to `libtun2proxy.so` in every ABI dir.
|
|
// Also deletes any stale hash-suffixed copies from previous builds.
|
|
fun normalizeTun2proxySo() {
|
|
val jniLibsRoot = file("src/main/jniLibs")
|
|
if (!jniLibsRoot.isDirectory) return
|
|
jniLibsRoot.listFiles()?.filter { it.isDirectory }?.forEach { abiDir ->
|
|
val hashed = abiDir.listFiles { f -> f.name.matches(Regex("libtun2proxy-[0-9a-f]+\\.so")) }
|
|
?: emptyArray()
|
|
val newest = hashed.maxByOrNull { it.lastModified() }
|
|
if (newest != null) {
|
|
val target = abiDir.resolve("libtun2proxy.so")
|
|
if (target.exists()) target.delete()
|
|
newest.copyTo(target, overwrite = true)
|
|
}
|
|
hashed.forEach { it.delete() }
|
|
}
|
|
}
|
|
|
|
// All ABIs we ship. Keep in sync with `android.defaultConfig.ndk.abiFilters`
|
|
// above; if these drift, the APK either includes .so files with no matching
|
|
// ABI entry (dead weight) or advertises ABIs with no .so (runtime
|
|
// UnsatisfiedLinkError on devices that pick that split).
|
|
val androidAbis = listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
|
|
|
|
tasks.register<Exec>("cargoBuildDebug") {
|
|
group = "build"
|
|
// Intentionally ALWAYS uses --release. The Rust debug build is 80+MB
|
|
// of unoptimized object code vs 3MB with release; the 20x APK bloat is
|
|
// never worth it just for a Rust stack trace you wouldn't see in
|
|
// logcat anyway. If you need Rust debug symbols, temporarily drop
|
|
// `--release` below and accept the APK size.
|
|
description = "Cross-compile mhrv_rs for all ABIs (release — same as cargoBuildRelease)"
|
|
workingDir = rustCrateDir
|
|
commandLine(buildList<String> {
|
|
add("cargo"); add("ndk")
|
|
androidAbis.forEach { add("-t"); add(it) }
|
|
add("-o"); add(jniLibsDir.absolutePath)
|
|
add("build"); add("--release")
|
|
})
|
|
doLast { normalizeTun2proxySo() }
|
|
}
|
|
|
|
tasks.register<Exec>("cargoBuildRelease") {
|
|
group = "build"
|
|
description = "Cross-compile mhrv_rs for all ABIs (release)"
|
|
workingDir = rustCrateDir
|
|
commandLine(buildList<String> {
|
|
add("cargo"); add("ndk")
|
|
androidAbis.forEach { add("-t"); add(it) }
|
|
add("-o"); add(jniLibsDir.absolutePath)
|
|
add("build"); add("--release")
|
|
})
|
|
doLast { normalizeTun2proxySo() }
|
|
}
|
|
|
|
// Hook the right cargo task in front of each Android build variant.
|
|
tasks.configureEach {
|
|
when (name) {
|
|
"mergeDebugJniLibFolders" -> dependsOn("cargoBuildDebug")
|
|
"mergeReleaseJniLibFolders" -> dependsOn("cargoBuildRelease")
|
|
}
|
|
}
|