refactor: split the library into fumi-core and make it embeddable
This commit is contained in:
parent
d3cd0afb4f
commit
8fb5661b53
28 changed files with 975 additions and 724 deletions
41
README.md
41
README.md
|
|
@ -61,26 +61,45 @@ Mail is stored sealed and opened on demand; there is no plaintext at rest. A fir
|
|||
|
||||
```
|
||||
nix build # or: nix develop -c cargo build
|
||||
nix develop -c cargo test
|
||||
nix develop -c cargo test --workspace
|
||||
```
|
||||
|
||||
The crate is a workspace: `core/` is the `fumi-core` library — everything but the command line — and `cli/` is the `fumi` binary. GUI hosts depend on `fumi-core` alone; it pulls no argument parsing, no logging globals, and no keyfile conventions. `bundled-sqlite` (default) vendors libsqlite3; embedders that link their own SQLite, as Android's NDK does, build with `default-features = false`.
|
||||
|
||||
`bunshin` and the reference clients make a complete test rig: register against a local server, exchange mail in both directions, then rotate and fetch the mail that the superseded key still receives.
|
||||
|
||||
## Embedding
|
||||
|
||||
`fumi-core` is the protocol core of a GUI client, not just this CLI's engine. The contract an embedder can rely on:
|
||||
|
||||
- **Secrets stay bytes in the API, files stay in the CLI.** `Account::new(master, index)` takes the master as bytes and `Store::open(path)` takes only mail and state; `identity.key` next to `fumi.db` is a CLI convention. A GUI keeps the master wherever its platform wants it (Android Keystore, OS keychain) and hands it over per operation.
|
||||
- **One shareable store handle.** `Store` is `Send + Sync`: every operation locks an inner mutex, and the database runs in WAL mode, so a host holds one `Store` behind an executor and reads it while a fetch writes. Each envelope is committed as it is verified — a concurrent reader listing the inbox sees fetch progress without any callback.
|
||||
- **Decisions, not prose.** `error::Error` is an enum: `NotPinned`, `PinMismatch`, `KeyChanged { address, known, offered }`, `NotRegistered`, `AuthFailed`, `RateLimited`, `QuotaExceeded`, `SchemaVersion`, ... — the distinctions a UI routes on. `Display` still produces the message the CLI prints.
|
||||
- **Long operations can stop.** `fetch_with` takes a `FetchOptions` with a cancellation token checked between pages; the cursor is saved per page, so a cancelled fetch resumes rather than repeats.
|
||||
- **Composable flows.** `restore` remains the one-call convenience, but its pieces are public: `connect` + `resolve`, `account::find_rotation_index`, then the `Store` setters — a wizard can hold the master and the address before it has connectivity, and show each step. Trust outcomes arrive as values: `TrustChange` from `trust_key` and `send`, `Session::unpinned_static` for the sec 4 warning.
|
||||
- **A versioned store.** The schema carries a `user_version`; a store written by a newer build is refused (`Error::SchemaVersion`) rather than misread, and within a version the schema is stable. The host, not the library, decides when to upgrade the binary.
|
||||
- **Binding from other languages.** The API is `Send`-friendly and free of CLI globals, so plain Rust bindings (flutter_rust_bridge, uniffi) work against it. `core/vectors.json` holds the reference vectors the unit tests pin — derivations, seals, a full envelope — so a port or FFI binding can verify itself end to end without the reference stack.
|
||||
|
||||
The intended call graph: `Store::open` once, `Account::new(master, rotations)` per session, then `client::*` operations taking `&Store` and `&Account`. The store outlives accounts; nothing in the library caches between calls.
|
||||
|
||||
## Modules
|
||||
|
||||
| File | Contents |
|
||||
|---|---|
|
||||
| `src/crypto.rs` | base32, HKDF, HMAC, SHA-256, the Ed25519→X25519 map with SPEC.md §2's checks |
|
||||
| `src/address.rs` | parsing for all four address forms, username validation, fingerprints |
|
||||
| `src/account.rs` | master, `seed_n` derivation, accept-key and tokens, rotation certificates |
|
||||
| `src/message.rs` | envelope seal/open (§5.1–§5.3), message id (§5.4), frontmatter (§5.5) |
|
||||
| `src/transport.rs` | `Transport` trait, bind values, operation and status constants |
|
||||
| `src/tcp.rs` | Noise_NX initiator, `len u32 \|\| op u8 \|\| body` framing, static-key pinning |
|
||||
| `src/rns/` | the Reticulum carrier behind the `rns` feature: FFI over `shim/`, path discovery, one link per session |
|
||||
| `src/client.rs` | the six operations, AUTH, chain walking, token sync, fetch pipeline |
|
||||
| `src/store.rs` | SQLite: state, contacts, accepted, tokens, seen ids, inbox, sent |
|
||||
| `core/src/crypto.rs` | base32, HKDF, HMAC, SHA-256, the Ed25519→X25519 map with SPEC.md §2's checks |
|
||||
| `core/src/address.rs` | parsing for all four address forms, username validation, fingerprints |
|
||||
| `core/src/account.rs` | master, `seed_n` derivation, accept-key and tokens, rotation certificates, `find_rotation_index` |
|
||||
| `core/src/message.rs` | envelope seal/open (§5.1–§5.3), message id (§5.4), frontmatter (§5.5) |
|
||||
| `core/src/transport.rs` | `Transport` trait, bind values, operation and status constants |
|
||||
| `core/src/tcp.rs` | Noise_NX initiator, `len u32 \|\| op u8 \|\| body` framing, static-key pinning |
|
||||
| `core/src/rns/` | the Reticulum carrier behind the `rns` feature: FFI over `core/shim/`, path discovery, one link per session |
|
||||
| `core/src/client.rs` | the six operations, AUTH, chain walking, token sync, fetch pipeline with cancellation |
|
||||
| `core/src/store.rs` | SQLite: state, contacts, accepted, tokens, seen ids, inbox, sent; `Send + Sync`, WAL, versioned schema |
|
||||
| `core/src/error.rs` | the `Error` enum embedders match on, the status-code mapping |
|
||||
| `core/vectors.json` | the committed reference vectors, the tests' source of truth |
|
||||
| `cli/src/main.rs` | the command line: keyfile and `--db` conventions, hints, output |
|
||||
|
||||
Unit tests pin every derivation against vectors generated from `smolmail.py`, including a full envelope reproduced byte for byte.
|
||||
Unit tests pin every derivation against `vectors.json`, whose values were generated from `smolmail.py`, including a full envelope reproduced byte for byte.
|
||||
|
||||
## References
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue