clio/clio-android/description.md

137 lines
4.3 KiB
Markdown
Raw Normal View History

2026-05-19 19:48:13 -04:00
# Phone Recorder - Architecture Overview
## Purpose
Android app that records audio continuously, encrypts recordings immediately (never stored unencrypted), and syncs them to a server over LAN or internet.
## Security Model
```
Phone (has: server public key) Server (has: private key)
───────────────────────────── ────────────────────────
Record audio → temp.ogg
Encrypt with public key →
recording_*.ogg.enc
└──── HTTP POST ────────→ Receive encrypted file
Decrypt with private key
Store as .ogg in recordings/
```
- **Sealed box encryption**: X25519 key exchange + XSalsa20-Poly1305
- **Forward secrecy**: Each encryption uses ephemeral keys
- **Phone never stores unencrypted**: Temp file deleted immediately after encryption
- **If no public key configured**: Recordings are deleted (security-first design)
## File Format (SREC)
Encrypted files use a chunked format for streaming large files:
```
[4 bytes: "SREC" magic]
[1 byte: version (1)]
[4 bytes: chunk count, big-endian]
For each chunk (64KB max):
[4 bytes: encrypted length, big-endian]
[N bytes: sealed box ciphertext]
```
## Android App Structure
### Main Files
| File | Purpose |
|------|---------|
| `MainActivity.kt` | UI, settings, sync controls |
| `RecordingService.kt` | Foreground service for recording, encryption on stop |
| `StorageHelper.kt` | File paths, temp vs recordings directories |
### Crypto Package (`crypto/`)
| File | Purpose |
|------|---------|
| `CryptoHelper.kt` | SREC format encryption using lazysodium |
| `KeyManager.kt` | Store/retrieve server public key (SharedPreferences) |
### Sync Package (`sync/`)
| File | Purpose |
|------|---------|
| `SyncSettings.kt` | Sync mode enum, server URLs, LAN subnet prefix |
| `NetworkHelper.kt` | Detect WiFi, LAN (by IP prefix), network availability |
| `SyncTracker.kt` | Track which files synced successfully (SharedPreferences) |
| `SyncWorker.kt` | WorkManager background job for uploads |
| `SyncManager.kt` | Schedule periodic (15min) and immediate syncs |
### API Package (`api/`)
| File | Purpose |
|------|---------|
| `RecordingsApi.kt` | OkHttp client, upload with SHA-256 verification |
## Sync Modes
| Mode | Behavior |
|------|----------|
| Never | No sync, files stay on phone |
| LAN Only | Sync only when IP matches configured subnet (e.g., `10.0.0.`) |
| WiFi Only | Sync on any WiFi network |
| Any Network | Sync on WiFi or mobile data |
## Server Structure (Python/FastAPI)
| File | Purpose |
|------|---------|
| `main.py` | FastAPI endpoints |
| `crypto.py` | PyNaCl decryption, keypair management |
| `config.py` | Paths, ports |
### Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/health` | GET | Returns `{"status": "ok"}` |
| `/api/v1/public-key` | GET | Returns Base64 public key |
| `/api/v1/recordings/upload` | POST | Upload encrypted file, returns hash for verification |
| `/api/v1/recordings` | GET | List decrypted recordings |
## Data Flow
1. **Recording starts**: `RecordingService` writes to `temp/recording_TIMESTAMP.ogg`
2. **Recording stops**:
- `CryptoHelper.encryptFile()` reads temp, writes `recordings/recording_TIMESTAMP.ogg.enc`
- Temp file deleted
- `SyncManager.scheduleImmediateSync()` triggered
3. **Sync runs**:
- `SyncWorker` checks network conditions match `SyncMode`
- Computes SHA-256 of encrypted file
- Uploads to server
- Server decrypts, returns hash
- If hashes match, marks file as synced
4. **Server storage**: `recordings/YYYY/MM/DD/recording_TIMESTAMP.ogg`
## Key Dependencies
### Android
- `lazysodium-android` - libsodium bindings
- `androidx.work` - Background job scheduling
- `okhttp3` - HTTP client
### Server
- `fastapi` + `uvicorn` - Web framework
- `pynacl` - libsodium bindings
- `aiofiles` - Async file I/O
## Configuration
### Android (in-app settings)
- Sync mode (Never/LAN/WiFi/Any)
- LAN subnet prefix (default: `10.0.0.`)
- Server URLs (LAN and remote)
- Server public key (Base64)
### Server (config.py or env vars)
- `HOST` - Bind address (default: 0.0.0.0)
- `PORT` - Listen port (default: 8000)
- `BASE_DIR` - Base directory for keys and recordings