Files
wz-phone/vault/Android/Maintenance.md
Siavash Sameni ed8a7ae5aa docs: protocol audit 2026-05-25, update architecture + Obsidian vault
Audit:
- docs/AUDIT-2026-05-25.md: full protocol audit covering 8 findings
  (4 critical, 2 high, 5 medium, 4 low) with code references and fix
  effort estimates
- vault/Audit/Tasks.md: Obsidian Tasks plugin file tracking all audit
  items with priorities, due dates, and per-step checklists

Architecture docs updated for Wire format v2 and Wave 5/6 features:
- ARCHITECTURE.md: adds wzp-video to dependency graph and project
  structure; wire format updated to v2 (16B header, 5B MiniHeader);
  relay concurrency section corrected (DashMap+RwLock is current, not
  a future optimization); test count 571→702; Android note
- PROGRESS.md: Wave 5 and Wave 6 sections appended; test count 372→702;
  current status and open blockers as of 2026-05-25
- ROAD-TO-VIDEO.md: implementation status table inserted (/🟡/🔴/🔲
  per phase); 6-step critical path to first video call
- WZP-SPEC.md: MediaHeader updated to v2 (16B byte-aligned); MiniHeader
  updated to 5B with seq_delta; codec IDs 9-12 added (H.264/H.265/AV1);
  version negotiation section added

Obsidian vault (vault/):
- 114 files across Architecture/, PRDs/, Reports/, Android/,
  Reference/, Audit/ with YAML frontmatter
- 00 - Home.md index note with wiki links
- .obsidian/app.json config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 06:00:17 +04:00

196 lines
6.9 KiB
Markdown

---
tags: [android, wzp]
type: reference
---
# Maintenance Guide
## Code Map — Where to Change Things
### Changing the relay address or room
Edit `CallViewModel.kt`:
```kotlin
companion object {
const val DEFAULT_RELAY = "172.16.81.125:4433"
const val DEFAULT_ROOM = "android"
}
```
For a proper settings screen, add a new Composable in `ui/` that persists to `SharedPreferences` and passes values to `viewModel.startCall(relay, room)`.
### Adding authentication
1. In `CallViewModel.startCall()`, pass a token parameter
2. In `engine.rs`, after QUIC connect but before CallOffer, send:
```rust
transport.send_signal(&SignalMessage::AuthToken { token: auth_token }).await?;
```
3. Wait for the relay to accept before proceeding to handshake
4. Start relay with `--auth-url <featherchat-endpoint>`
### Enabling media encryption
The crypto session is already derived in `engine.rs` but not applied to packets. To enable:
1. Pass `_session` (currently unused) to the send/recv tasks
2. Before `transport.send_media()`, encrypt the payload:
```rust
let mut ciphertext = Vec::new();
session.encrypt(&header_bytes, &payload, &mut ciphertext)?;
packet.payload = Bytes::from(ciphertext);
```
3. After `transport.recv_media()`, decrypt:
```rust
let mut plaintext = Vec::new();
session.decrypt(&header_bytes, &pkt.payload, &mut plaintext)?;
pkt.payload = Bytes::from(plaintext);
```
### Adding a new codec / quality profile
1. Define the profile in `wzp-proto/src/codec_id.rs`
2. Implement `AudioEncoder`/`AudioDecoder` traits in `wzp-codec`
3. Register in `AdaptiveEncoder`/`AdaptiveDecoder` switch logic
4. Add to `supported_profiles` in the CallOffer (engine.rs)
### Changing audio parameters
- **Sample rate**: Change `FRAME_SAMPLES` in `audio_android.rs` and `WzpOboeConfig.sample_rate` in `oboe_bridge.cpp`. Must match the codec's expected rate.
- **Frame duration**: Change `FRAME_SAMPLES` (960 = 20ms at 48kHz, 1920 = 40ms)
- **Ring buffer size**: Change `RING_CAPACITY` in `audio_android.rs`
- **AEC tail length**: Change the `100` in `Pipeline::new()` → `EchoCanceller::new(48000, 100)`
### Adding x86_64 support (emulator)
1. `build.gradle.kts`: add `"x86_64"` to `abiFilters`
2. `cargoNdkBuild` task: add `-t x86_64`
3. `build.rs`: handle `x86_64-linux-android` target for Oboe
4. Note: Oboe in the emulator uses a different audio HAL — audio quality will differ
## Dependency Overview
### Rust Crate Dependencies (wzp-android)
| Crate | Version | Purpose | Upgrade risk |
|-------|---------|---------|--------------|
| `jni` | 0.21 | Java FFI | Low — stable API |
| `tokio` | 1.x | Async runtime | Low |
| `quinn` | 0.11 | QUIC transport | Medium — breaking changes between 0.x |
| `rustls` | 0.23 | TLS for QUIC | Medium — tied to quinn version |
| `serde_json` | 1.x | Stats serialization | Low |
| `anyhow` | 1.x | Error handling | Low |
| `tracing` | 0.1 | Logging | Low |
| `rand` | 0.8 | Random seed generation | Low |
### Workspace Crate Dependencies
| Crate | Purpose | Key trait |
|-------|---------|-----------|
| `wzp-proto` | Shared types and traits | `MediaTransport`, `AudioEncoder`, `KeyExchange` |
| `wzp-codec` | Opus + Codec2 + signal processing | `AdaptiveEncoder`, `EchoCanceller` |
| `wzp-fec` | RaptorQ FEC | `RaptorQFecEncoder` |
| `wzp-crypto` | Key exchange + encryption | `WarzoneKeyExchange`, `ChaChaSession` |
| `wzp-transport` | QUIC connection management | `QuinnTransport`, `connect()` |
### Android/Kotlin Dependencies
| Library | Version | Purpose |
|---------|---------|---------|
| `compose-bom` | 2024.01.00 | Compose version alignment |
| `material3` | (from BOM) | UI components |
| `activity-compose` | 1.8.2 | Activity integration |
| `lifecycle-runtime-ktx` | 2.7.0 | ViewModel + coroutines |
| `core-ktx` | 1.12.0 | Kotlin extensions |
## Updating Dependencies
### Rust
```bash
cargo update -p wzp-android
cargo ndk -t arm64-v8a build --release -p wzp-android
```
Watch for `quinn`/`rustls` version coupling. They must be compatible:
- quinn 0.11 requires rustls 0.23
### Android/Kotlin
Update versions in `android/app/build.gradle.kts`. Key compatibility:
- `kotlinCompilerExtensionVersion` must match the Kotlin version
- `compose-bom` version determines all Compose library versions
- `compileSdk` and `targetSdk` should stay in sync
### NDK
If upgrading the NDK:
1. Update `ndkVersion` in `build.gradle.kts`
2. Update `ANDROID_NDK_HOME` environment variable
3. Update `CC_aarch64_linux_android` and friends
4. Verify Oboe still builds with the new toolchain
## Key Invariants to Preserve
1. **JNI function names must match package structure**: If the Kotlin package changes, all `Java_com_wzp_engine_WzpEngine_*` functions in `jni_bridge.rs` must be renamed.
2. **Manifest uses fully-qualified class names**: Never use `.ClassName` shorthand because the Gradle namespace (`com.wzp.phone`) differs from the Kotlin package (`com.wzp`).
3. **Stats JSON field names are snake_case**: Rust serializes with serde defaults (snake_case). Kotlin's `CallStats.fromJson()` expects `duration_secs`, `loss_pct`, etc.
4. **Ring buffer ordering**: Producer uses Release store on write index, consumer uses Acquire load. Breaking this causes torn reads.
5. **Codec thread owns Pipeline**: Pipeline is `!Send` (Opus encoder state). It must never be accessed from another thread.
6. **panic::catch_unwind on all JNI functions**: Rust panics unwinding across the FFI boundary is UB. Every JNI-exposed function must catch panics.
7. **Channel capacity (64)**: Both `send_tx` and `recv_tx` are bounded at 64 packets. If the network is slow, packets are dropped (`try_send` best-effort).
## Testing
### Unit Tests (Rust)
```bash
# Run all workspace tests (host, not Android)
cargo test
# Run only wzp-android tests (uses oboe_stub.cpp on host)
cargo test -p wzp-android
```
Note: Pipeline, codec, FEC, crypto tests run on the host. Audio tests use stubs.
### On-Device Testing
1. Build and install debug APK
2. Open app, tap CALL
3. Verify in logcat:
- `WzpEngine created via JNI`
- `connecting to relay...`
- `QUIC connected to relay`
- `CallOffer sent`
- `handshake complete, call active`
- `codec thread started`
4. Check stats overlay: frame counters should increment
5. Speak into mic — other connected device should hear audio
### Stress Testing
- Run a call for 30+ minutes — check for memory leaks (stats should be stable)
- Kill and restart the relay — client should eventually get a connection error
- Toggle mute rapidly — verify no crashes
- Switch speaker on/off — verify audio route changes
## Performance Monitoring
Key metrics to watch during a call:
| Metric | Healthy Range | Warning | Critical |
|--------|--------------|---------|----------|
| frames_encoded | Increasing ~50/sec | Stalled | 0 |
| frames_decoded | Increasing ~50/sec | Stalled | 0 |
| underruns | < 5/min | > 20/min | > 100/min |
| jitter_buffer_depth | 2-5 | 0 or >10 | N/A |
| loss_pct | < 5% | 5-20% | > 20% |
| rtt_ms | < 100ms | 100-300ms | > 500ms |