Files
IronStorage/docs/android/mobile-contract.md
T
hugo 180851087b
Dependency security audit / rustsec (push) Failing after 22s
Share the platform-neutral Rust mobile contract (#91)
2026-10-08 13:25:09 +02:00

128 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Shared mobile contract (#91)
`crates/mobile-bridge` is the single UniFFI source for phone records, enums,
conversions, objects, operations and OS callbacks. `crates/android` packages it
without features. `crates/apple` packages it with the explicit `watch` feature.
Neither packaging crate copies converters. The component namespace is
`ironstorage_mobile`; the native library names remain `ironstorage_android` and
`ironstorage_apple`. Swift source/header names follow the shared namespace.
## Public workflow inventory
| Storage owner | Shared phone exports | Identity / concurrency boundary |
| --- | --- | --- |
| mobile | shell, fixtures, tab selection | typed tabs/states/semantic icons |
| mobile_home / git | Home page, commits/changes/actions, controlled operations/progress | typed actions, repository operation serialization and cancellation |
| mobile_passwords | browse, search, folder/entry rows | storage entry paths and row IDs |
| mobile_authentication / authentication | unlock, reveal/copy, preferences, identity/appearance/timeout, lifecycle | generation-scoped authentication handles, monotonic expiry |
| mobile_entry / document / generate | structured fields, editor sessions, OTP image import, generation, save/discard | session u64 and field IDs; original ciphertext/conflict checks |
| mobile_mutation / mutation | preparation, request, commit, outcome | opaque revision token validated at execution; explicit overwrite decisions |
| mobile_totp / otp | discovery/progress/cancel, cached search, detail/code/copy | storage-owned catalog, Unix-time inputs and expiry |
| mobile_onboarding / config | server discovery, clone and local setup, token replacement | typed transport/phase errors, controlled cancellation |
| mobile_key_transfer / presentation | export/import, frames, progress, key selection | storage-owned QR envelopes and bounded decoding |
| mobile_watch | **Apple opt-in only:** preferences, selection, snapshots/status/receipts | no Watch enum, field, method or control in the Android contract |
`MobileIcon` expresses intent, never an asset name. UIKit maps it to SF Symbols
in `MobileIcons.swift`. Android's core maps it exhaustively to its presentation
enum; Compose maps navigation intents to vector drawables. Asset names and
selected variants belong to the native UI. Actions already use typed enums.
## OS boundaries
- `MobilePlatformPaths` accepts absolute private data and cache roots, rejects
traversal and overlapping roots, and lets storage derive config/vault/key
paths. Validation does not create or migrate files. #90 owns initialization,
permissions, relocation, backup and cache policy.
- `MobileSecretBackend` performs create/retrieve/replace/delete/lock/unlock on
a typed locator and protection supplied by storage. Values are opaque encoded
storage records. OS errors are enums. It cannot select accounts, protection,
enrollment recovery, cache lifetime or authentication policy. Its adapter is
usable by `mobileAuthenticationWithPlatform`; #93 implements Android Keystore.
Preferences use that same injected backend, never a hidden native fallback.
- `MobileClock` returns elapsed monotonic milliseconds from a stable origin.
It cannot mint or refresh leases. Rust uses the existing authentication clock
and generation checks. Unix timestamps required by TOTP/Git remain explicit
inputs, not values parsed from rendered countdowns.
- `MobileLifecycleEvent` reports foreground, background, device lock, real user
activity, or cancellation. Storage revokes leases/editors on background/lock,
checks expiry on foreground, and extends activity only for an active lease.
- `MobileClipboard` reads/writes/clears OS text and waits for a requested timeout
or cancellation. Rust's existing manager restores the prior text or clears
only if the copied value is still current; newer clipboard content survives.
Call off the UI thread; the facade serializes access to one OS clipboard.
- `MobileCameraFrame` supplies luminance bytes, dimensions and row/pixel stride.
Storage validates bounds (4096 pixels per dimension, 32 MiB plane, pixel
stride 1–4) and decodes QR payloads. Native UI never interprets envelopes.
- `MobileCredentialRequest` carries an opaque request ID, OS-asserted package,
signing-certificate SHA-256 and web origins. Rust validates/canonicalizes the
facts. A validated request is **not** an authorization grant. #109 implements
matching, selected-candidate authentication and scoped grants in storage.
## Ownership, cancellation and diagnostics
UniFFI objects are reference-counted Rust owners. Operation objects retain their
authentication owner while work runs. Kotlin `close()` / Swift deinitialization
release native handles; the final storage authentication owner destroys the
lease and locks its store. Frontends cancel the exact operation on navigation
or coroutine cancellation before releasing it. `MobileAuthentication.cancel`
revokes its generation and discards editor state; it is not interchangeable
with cancelling an individual discovery/Git operation. Late results are
discarded by facade revision IDs. Polling, repaint and progress never count as
user activity. Editor IDs and field IDs are stable within their owning session;
revision tokens are opaque and must be returned unchanged to storage.
Secrets and payloads are owned values, not persisted view state. Rust secret
buffers zeroize on drop. Swift/Kotlin cannot promise complete managed-memory
erasure, so callers release revealed/copy/OTP/frame values when their operation
ends and never log or save them. Generated records, associated-value enums and
errors have redacted diagnostic rendering; typed fields remain available for
deliberate UI presentation. Unexpected native/callback errors redact their
payloads. Mechanical generator hardening is version-checked and preserves wire
layouts and every UniFFI contract/API checksum.
## Contract gates
Every Android binding generation generates both Swift and Kotlin from the
compiled phone component. The build tool verifies all records/fields, types,
functions and operation checksums against metadata, writes `inventory.tsv`, and
rejects any Watch item in the phone surface. The Apple opt-in build generates
both languages too. `compare-contract` compares complete shared signatures;
only Watch items and the opt-in Watch inputs to `preferences` differ.
```sh
cargo build -p ironstorage-apple --lib
cargo run -p ironstorage-android-build -- contract-bindings \
target/debug/libironstorage_apple.dylib android/uniffi.toml /tmp/ironstorage-watch-contract watch
cargo run -p ironstorage-android-build -- compare-contract \
android/rust-bridge/build/generated/bindings-raw/debug/inventory.tsv \
/tmp/ironstorage-watch-contract/inventory.tsv
```
On non-macOS build hosts, use the host shared-library extension. Apple generation
copies the verified/hardened Swift source and header into `apple/Generated`.
Contract tests exercise typed errors, checksum failure, callback ownership,
cancellation/concurrency, camera bounds, large foreign payloads and redaction.
All app validation uses persistent emulator devices; no physical-device gate is
part of this milestone's testing scope.
The native Swift probe in `tools/android-build/tests/mobile_contract.swift` runs
against the generated source and the host Apple library:
```sh
xcrun swiftc -swift-version 5 -module-cache-path /tmp/ironstorage-swift-cache \
-I apple/Generated \
-Xcc -fmodule-map-file=apple/Generated/ironstorage_mobileFFI.modulemap \
-L target/debug -lironstorage_apple \
-Xlinker -rpath -Xlinker "$PWD/target/debug" \
apple/Generated/ironstorage_mobile.swift \
tools/android-build/tests/mobile_contract.swift -o /tmp/ironstorage-swift-contract
/tmp/ironstorage-swift-contract
```
The Android native probe is `MobileContractTest`: run it with the existing
`NativeRuntimeTest` on persistent 4 KB and strict 16 KB ARM64 emulators. UI and
native instrumentation suites run sequentially to preserve input focus. The
storage integration test exercises a real encrypted fixture with the injected
secret backend/clock, activity renewal, background revocation, editor invalidation
and final-owner cleanup without global configuration.