Files
greenart7c3 d8e4caad67 Fix Android Studio's inspection findings and turn off its spell check
Android Studio's batch inspections reported about 800 problems besides
spelling, which the project profile now leaves off (it mostly flagged
the translations).

Real bugs among them: the desktop's strings files had empty strings
where Android has translations (German, Spanish, Brazilian Portuguese,
Chinese), showing blank text; and IS_FDROID_BUILD, which the F-Droid
workflow flips with sed, was read as a constant the IDE wanted to
simplify away, so it is now read through BuildFlavorChecker.isFdroidBuild().
Account loads in coroutines (relay, intent and UI paths) and logout no
longer block a thread with runBlocking; the blocking ContentProvider
path and the desktop lock say why they do.

Also remove what was dead (the drawable IconRow overload, PermissionCard,
ConfirmationToastMsg, the desktop OkHttpWebSocket.Builder and
DeleteAfterType.screenCode, the QR drawer's zero corner radius math and
the strings only they used), move to the Duration overloads, give
subject-less when blocks their subject, drop redundant opt-ins, suspend
modifiers, qualifiers and SAM constructors, fix broken KDoc links, take
the ktlint and KSP plugin versions from the catalog, strip editor
metadata from the SVGs, align the Markdown tables and fix real grammar
and spelling in comments, docs and three English strings.
2026-10-26 17:28:06 -03:00

97 lines
6.3 KiB
Markdown

# Arti (built-in Tor)
Amber's built-in Tor runs [Arti](https://gitlab.torproject.org/tpo/core/arti)
(Tor in Rust) in-process through a small JNI wrapper, `src/lib.rs`. The
wrapper starts a Tor client, puts a SOCKS5 proxy on `127.0.0.1` in front of it
and reports bootstrap progress; Kotlin's `TorManager` drives it through
`com.greenart7c3.nostrsigner.tor.ArtiNative`.
The wrapper is adapted from Amethyst's `tools/arti-build`
(<https://github.com/vitorpamplona/amethyst>, © 2025 Vitor Pamplona, MIT License).
## Prebuilt libraries
The libraries are built once with `build.sh` and committed; CI does not build Rust.
| App | Path | Built with |
| ----------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Android (`free`, `benchmark`) | `app/src/tor/jniLibs/{arm64-v8a,armeabi-v7a,x86_64,x86}/libamber_arti.so` | `./build.sh android` (cargo-ndk, NDK pinned in `build.sh`) |
| Desktop Linux | `desktop/appResources/linux-{x64,arm64}/libamber_arti.so` (glibc ≥ 2.28) | `./build.sh desktop` (cargo-zigbuild) |
| Desktop Windows | `desktop/appResources/windows-x64/amber_arti.dll` | `./build.sh desktop` (cargo-zigbuild) |
| Desktop macOS | `desktop/appResources/macos-{x64,arm64}/libamber_arti.dylib` | `./build.sh macos`, on a Mac |
The Android ABIs must match `splits.abi` in `app/build.gradle.kts`. On the
desktop, Compose ships the current OS's directory as app resources
(`appResourcesRootDir`), and `ArtiNative` loads the library from
`compose.application.resources.dir`.
## Rebuilding
Rebuild after changing `src/lib.rs`, `Cargo.toml` or `Cargo.lock`, and commit
the regenerated libraries together with the source change. Every library has
to come from the same source, so rebuild all of them.
Prerequisites: rustup (the compiler is pinned in `rust-toolchain.toml`; the
script adds the targets), plus
- android: `cargo install cargo-ndk` and the NDK revision in `build.sh`
- desktop: `cargo install cargo-zigbuild` and zig (`pip install ziglang` is enough)
- macos: a Mac with the Xcode command line tools (the link needs Apple's
frameworks, which zig cannot provide)
```bash
./build.sh android desktop # on Linux
./build.sh macos # on a Mac, from a copy of this repository
```
The script checks that every library exports the JNI entry points and prints
their SHA-256 hashes.
## Reproducibility
Rebuilding from a fresh checkout (any path: `build.sh` remaps host paths out
of the binaries) gives byte-identical libraries with the pinned toolchain and
`Cargo.lock`. The APK packages `libamber_arti.so` unstripped
(`packaging.jniLibs.keepDebugSymbols` in `app/build.gradle.kts`), so the copy in
a release is the committed file itself:
```bash
unzip -p amber-arm64-v8a-vX.Y.Z.apk lib/arm64-v8a/libamber_arti.so | sha256sum
sha256sum app/src/tor/jniLibs/arm64-v8a/libamber_arti.so
```
AGP still strips the app's other native libraries with the NDK pinned in
`app/build.gradle.kts` (`ndkVersion`), which the reproducibility `Dockerfile`
installs too.
## Patched dependency: `saturating-time`
`Cargo.toml` replaces `saturating-time` 0.5.0 (used by `tor-netdoc`) with the
copy in `patches/saturating-time`. Upstream's `find_limit` halves its step down
to 1ns and only stops on `None`, but Windows' `SystemTime` ticks in 100ns, so a
1-99ns `checked_sub` returns the same time instead of `None` and the loop never
ends: Arti then hangs at 15% ("fetching a consensus") with one core pinned the
first time it parses a consensus. The patch treats "no progress" like `None`
(see the "Amber patch" comment). Drop it once a fixed release is out.
## Updating Arti
1. Bump `arti-client` and `tor-rtcompat` in `Cargo.toml` (and `version`, which
tracks the Arti release: arti 2.7.0 = crates 0.47.0).
2. `cargo update -p arti-client -p tor-rtcompat` to refresh `Cargo.lock`.
3. Rebuild every library as above.
## JNI surface
| Kotlin (`ArtiNative`) | Purpose |
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `initialize(dataDir)` | Create the Tor client (state + cache under `dataDir`) and start the directory download in the background |
| `isBootstrapped()` / `bootstrapProgressPermille()` | Readiness and download progress, polled by `TorManager` |
| `startSocksProxy(port, username, password)` / `stopSocksProxy()` | Bind / release the SOCKS5 listener; the client stays alive. The listener only accepts RFC 1929 username/password auth with the given per-listener secret, which `TorSocksAuthenticator` hands to Java's SOCKS client |
| `destroy()` | Drop the client so the next `initialize` starts over (used by `restart`) |
| `setLogCallback(cb)` / `getVersion()` | Logging into Amber's log and version string |
The JNI symbol names are derived from the Kotlin package and class
(`Java_com_greenart7c3_nostrsigner_tor_ArtiNative_*`), so the Android and
desktop copies of `ArtiNative` must keep that package and name.