# chanora_storage
Two strictly separated storage concerns per SAD-067:
1. **`BookmarkRepository`** — non-secret bookmark state via SQLite with optional encrypted password fields (`rusqlite` bundled, DEC-013.1).
2. **`IdentityFileStore`** — Beta fallback storage for identity material while platform secure-storage backends mature.
## Architecture
### IdentityFileStore
- Persists a single TS3 identity to `
/identity.tskey` encrypted with ChaCha20-Poly1305.
- The Data Encryption Key (DEK) is 32 random bytes stored in the platform keyring (Linux Secret Service, macOS Keychain, Windows Credential Manager, iOS Keychain) when available, with a best-effort file fallback at `identity.dek` (mode 0600 on Unix).
- Legacy plaintext files from pre-Beta are still readable; the next `save()` upgrades them to encrypted form.
- Audio metadata (`transmit_mode`, `release_tail_ms`, PTT binding) is persisted alongside as `audio_meta.json` (plaintext, non-secret).
### BookmarkRepository
- SQLite-backed store at `/chanora.db`.
- Schema v1: basic bookmark columns. Schema v2: adds `password_blob` for encrypted passwords.
- When constructed via `with_crypto()`, the `password` column is replaced by a ChaCha20-Poly1305 envelope under the same per-install DEK.
- Legacy plaintext passwords are transparently read and upgraded on the next `update()`.
### Crypto abstraction
- `Crypto` trait: `encrypt(plaintext)` / `decrypt(blob)` — callers see only the encrypt/decrypt pair.
- `DekCrypto` — concrete implementation sharing the same per-install DEK with `IdentityFileStore`.
## Public API Summary
### Types
| Type | Role |
|---|---|
| `IdentityFileStore` | Encrypted identity file store |
| `BookmarkRepository` | SQLite bookmark store with optional password encryption |
| `Bookmark` | Bookmark DTO: id, display_name, host, nickname, password |
| `PttBindingMeta` | Persisted PTT binding metadata |
| `StorageError` | NotFound, Migration, Sqlite, SecureStore, Io, Crypto |
| `Crypto` trait | Encrypt/decrypt abstraction |
### IdentityFileStore methods
- `new(dir)` — create or open store, ensure DEK exists
- `load()` → `Option` — read identity (handles legacy plaintext)
- `save(identity)` — persist encrypted (ChaCha20-Poly1305, atomic write)
- `clear()` — remove identity file
- `crypto()` — obtain a `Crypto` handle sharing the DEK
- `set_transmit_mode(mode)` / `get_transmit_mode()` — audio settings persistence
- `set_release_tail_ms(ms)` / `get_release_tail_ms()` — release-tail persistence
- `set_ptt_binding(...)` / `get_ptt_binding()` — PTT binding persistence
### BookmarkRepository methods
- `new(dir)` / `with_crypto(dir, crypto)` — open (plain or encrypted)
- `add(bookmark)` → `i64` — insert, return id
- `update(bookmark)` — replace by id
- `delete(id)` — remove by id
- `list()` → `Vec` — all bookmarks ordered by id
- `upsert_or_add(bookmark)` — insert or update by host, preserves user's display name
- `encrypts_passwords()` — whether password encryption is active
## Platform notes
- Unix: files written with mode 0600.
- Keyring access can be disabled via `CHANORA_DISABLE_KEYRING=1` for tests/headless environments.
- Android: file in app-private storage (not encrypted at rest — documented Beta gap).
- iOS/Windows/macOS: caller provides the storage directory; platform sandbox handles access control.