From fa127360395ace7d44411ef600b56e894b68b62f Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Fri, 2 Jan 2026 16:36:19 +0100 Subject: [PATCH] Update Rust core README (#1404) --- core/rust/README.md | 473 +++++++++++++++++++++++++++++--------------- 1 file changed, 317 insertions(+), 156 deletions(-) diff --git a/core/rust/README.md b/core/rust/README.md index ef83bf0c6..12288a384 100644 --- a/core/rust/README.md +++ b/core/rust/README.md @@ -1,210 +1,371 @@ # AliasVault Rust Core -Cross-platform core library for AliasVault, providing shared business logic for **native platforms**: -- **Mobile Apps** (iOS via Swift bindings, Android via Kotlin bindings) -- **Server** (.NET via P/Invoke) -- **Desktop Apps** (future) +Cross-platform core library providing shared business logic for all AliasVault clients: -> **Note:** Browser extensions use the TypeScript [VaultMergeService](../../apps/browser-extension/src/utils/VaultMergeService.ts) with sql.js (pre-compiled SQLite WASM). This is more efficient than re-compiling SQLite via Rust to WASM. +- **Browser Extensions** (Chrome, Firefox, Edge, Safari via WASM) +- **Mobile Apps** (iOS via Swift bindings, Android via Kotlin bindings) +- **Server** (.NET via P/Invoke - currently only scaffolding, not actively used) ## Architecture ``` ┌─────────────────────────────────────────────────────────────────────────┐ -│ Rust Core Library │ -│ ┌─────────────────────────────────────────────────────────────────────┐│ -│ │ src/lib.rs - Main library entry point ││ -│ │ src/merge.rs - Vault merge service (LWW strategy) ││ -│ │ src/types.rs - Common types and table configurations ││ -│ │ src/error.rs - Error types ││ -│ └─────────────────────────────────────────────────────────────────────┘│ +│ Rust Core Library │ +│ │ +│ src/ │ +│ ├── lib.rs Entry point, module exports │ +│ ├── error.rs VaultError type │ +│ ├── vault_merge/ LWW merge algorithm │ +│ ├── vault_pruner/ Trash retention cleanup │ +│ └── credential_matcher/ Autofill filtering │ +│ │ +│ Platform Interfaces: │ +│ ├── wasm.rs WASM bindings (#[wasm_bindgen]) │ +│ ├── uniffi_api.rs UniFFI bindings (#[uniffi::export]) │ +│ └── ffi.rs C FFI for .NET (extern "C") │ └─────────────────────────────────────────────────────────────────────────┘ │ ┌──────────────────────────┼──────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ UniFFI (FFI) │ │ UniFFI (FFI) │ │ C FFI (.NET) │ -│ Swift bindings │ │ Kotlin bindings │ │ P/Invoke │ +│ WASM Module │ │ UniFFI (FFI) │ │ C FFI (.NET) │ +│ wasm-bindgen │ │ Swift + Kotlin │ │ P/Invoke │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ iOS Mobile │ │ Android Mobile │ │ Server │ -│ App │ │ App │ │ (.NET) │ +│ Browser │ │ iOS & Android │ │ Server │ +│ Extensions │ │ Mobile Apps │ │ (.NET) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` -## Prerequisites +## Core Modules -### Required -- **Rust** (1.70+): Install via [rustup](https://rustup.rs/) - ```bash - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - ``` +### vault_merge +Last-Write-Wins (LWW) merge algorithm for syncing local and server vaults. -### For iOS builds (macOS only) -- **Xcode** with command line tools +### vault_pruner +Permanently deletes items in trash older than retention period (default: 30 days). -### For Android builds -- **Android NDK**: Set `ANDROID_NDK_HOME` environment variable -- **cargo-ndk**: For Android cross-compilation (installed automatically) - ```bash - cargo install cargo-ndk - ``` +### credential_matcher +Priority-based credential filtering for autofill with anti-phishing protection. ## Building -### Build all targets ```bash -./build.sh +./build.sh --browser # WASM for browser extension +./build.sh --ios # iOS device + simulator with Swift bindings +./build.sh --android # Android ABIs with Kotlin bindings +./build.sh --dotnet # Native library for .NET +./build.sh --mobile # iOS + Android +./build.sh --all # All targets ``` -### Build specific targets +## Testing + ```bash -./build.sh --ios # iOS only (macOS required) -./build.sh --android # Android only (NDK required) -./build.sh --csharp # C#/.NET only +cargo test # All tests +cargo test vault_merge # Specific module +cargo test --features uniffi # With UniFFI enabled ``` -### Manual builds (for development) +--- + +# Developer Guide + +This section documents the interface structure and how to add new functionality. + +## Interface Overview + +The Rust core exposes functions through three interface files, each targeting different platforms: + +| Interface | File | Platforms | Attribute | +|-----------|------|-----------|-----------| +| WASM | `src/wasm.rs` | Browser extensions | `#[wasm_bindgen]` | +| UniFFI | `src/uniffi_api.rs` | iOS, Android | `#[uniffi::export]` | +| C FFI | `src/ffi.rs` | .NET Server | `#[no_mangle] extern "C"` | + +All interfaces follow a JSON-in/JSON-out pattern for simplicity. Each platform handles its own database I/O and passes data as JSON to Rust. + +## Current Exported Functions + +| Function | WASM | UniFFI | C FFI | Description | +|----------|------|--------|-------|-------------| +| `getSyncableTableNames` | ✓ | ✓ | ✓ | Returns list of syncable table names | +| `mergeVaults` / `mergeVaultsJson` | ✓ | ✓ (JSON only) | ✓ | LWW merge of local + server vaults | +| `pruneVault` / `pruneVaultJson` | ✓ | ✓ (JSON only) | ✓ | Remove expired trash items | +| `filterCredentials` / `filterCredentialsJson` | ✓ | ✓ (JSON only) | ✓ | Credential matching for autofill | +| `extractDomain` | ✓ | ✓ | - | Extract domain from URL | +| `extractRootDomain` | ✓ | ✓ | - | Extract root domain (handles .co.uk etc) | + +## Adding a New Function + +When adding a new module or function to the Rust core, you need to update multiple files. Follow this checklist: + +### 1. Implement the Core Logic + +Create or update a module in `src/`: + +```rust +// src/my_module/mod.rs + +use serde::{Deserialize, Serialize}; +use crate::error::VaultResult; + +#[derive(Serialize, Deserialize)] +pub struct MyInput { + pub data: String, +} + +#[derive(Serialize, Deserialize)] +pub struct MyOutput { + pub result: String, +} + +/// Core function with typed input/output. +pub fn my_function(input: MyInput) -> VaultResult { + // Implementation + Ok(MyOutput { result: input.data }) +} + +/// JSON wrapper for cross-platform convenience. +pub fn my_function_json(input_json: &str) -> VaultResult { + let input: MyInput = serde_json::from_str(input_json)?; + let output = my_function(input)?; + Ok(serde_json::to_string(&output)?) +} +``` + +### 2. Export from lib.rs + +```rust +// src/lib.rs + +pub mod my_module; +pub use my_module::{my_function, MyInput, MyOutput}; +``` + +### 3. Add WASM Bindings (src/wasm.rs) + +```rust +use crate::my_module::{my_function, MyInput, MyOutput}; + +/// Typed API - accepts/returns JsValue. +#[wasm_bindgen(js_name = myFunction)] +pub fn my_function_js(input: JsValue) -> Result { + let input: MyInput = serde_wasm_bindgen::from_value(input) + .map_err(|e| JsValue::from_str(&format!("Failed to parse input: {}", e)))?; + + let output: MyOutput = my_function(input) + .map_err(|e| JsValue::from_str(&format!("Failed: {}", e)))?; + + serde_wasm_bindgen::to_value(&output) + .map_err(|e| JsValue::from_str(&format!("Failed to serialize: {}", e))) +} + +/// JSON API - accepts/returns strings. +#[wasm_bindgen(js_name = myFunctionJson)] +pub fn my_function_json_js(input_json: &str) -> Result { + crate::my_module::my_function_json(input_json) + .map_err(|e| JsValue::from_str(&format!("Failed: {}", e))) +} +``` + +### 4. Add UniFFI Bindings (src/uniffi_api.rs) + +```rust +use crate::error::VaultError; + +/// JSON API only - UniFFI handles type conversion automatically. +#[uniffi::export] +pub fn my_function_json(input_json: String) -> Result { + crate::my_module::my_function_json(&input_json) +} +``` + +### 5. Add C FFI Bindings (src/ffi.rs) - If Needed for .NET + +```rust +/// C-compatible function for .NET P/Invoke. +/// +/// # Safety +/// - `input_json` must be a valid null-terminated C string +/// - Returned pointer must be freed with `free_string()` +#[no_mangle] +pub unsafe extern "C" fn my_function_ffi(input_json: *const c_char) -> *mut c_char { + if input_json.is_null() { + return ptr::null_mut(); + } + + let c_str = match CStr::from_ptr(input_json).to_str() { + Ok(s) => s, + Err(_) => return ptr::null_mut(), + }; + + let input: MyInput = match serde_json::from_str(c_str) { + Ok(i) => i, + Err(e) => return create_error_response(&format!("Parse failed: {}", e)), + }; + + let output = match my_function(input) { + Ok(o) => o, + Err(e) => return create_error_response(&format!("Failed: {}", e)), + }; + + match serde_json::to_string(&output) { + Ok(json) => string_to_c_char(json), + Err(e) => create_error_response(&format!("Serialize failed: {}", e)), + } +} +``` + +### 6. Update Cargo.toml Features (If Needed) + +If your module requires additional dependencies, ensure they're properly feature-gated: + +```toml +[features] +wasm = ["dep:wasm-bindgen", "dep:serde-wasm-bindgen"] +uniffi = ["dep:uniffi"] +ffi = [] + +[dependencies] +my-new-dep = { version = "1.0", optional = true } +``` + +### 7. Add Tests + +```rust +// In src/my_module/mod.rs or src/my_module/tests.rs + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_my_function() { + let input = MyInput { data: "test".to_string() }; + let output = my_function(input).unwrap(); + assert_eq!(output.result, "test"); + } + + #[test] + fn test_my_function_json() { + let input = r#"{"data": "test"}"#; + let output = my_function_json(input).unwrap(); + assert!(output.contains("test")); + } +} +``` + +### 8. Build and Distribute + ```bash -# iOS (device) -cargo build --release --target aarch64-apple-ios --features uniffi +# Build for all targets you support +./build.sh --browser --ios --android -# Android (arm64) -cargo ndk --target aarch64-linux-android --platform 21 -- build --release --features uniffi - -# Native (current platform) -cargo build --release +# Or just the ones you need during development +./build.sh --browser ``` -## Output Structure +## Platform Consumption -After building, artifacts are placed in `dist/` and distributed to consumer apps: +After building, the outputs are distributed to: -``` -dist/ -├── ios/ # iOS libraries -│ ├── device/ -│ │ └── libaliasvault_core.a -│ ├── simulator/ -│ │ └── libaliasvault_core.a (universal) -│ └── aliasvault_core.swift -├── android/ # Android libraries -│ ├── arm64-v8a/ -│ │ └── libaliasvault_core.so -│ ├── armeabi-v7a/ -│ │ └── libaliasvault_core.so -│ ├── x86_64/ -│ │ └── libaliasvault_core.so -│ ├── x86/ -│ │ └── libaliasvault_core.so -│ └── aliasvault_core.kt -└── csharp/ # C# libraries - ├── osx-arm64/ - │ └── libaliasvault_core.dylib - ├── osx-x64/ - │ └── libaliasvault_core.dylib - ├── linux-x64/ - │ └── libaliasvault_core.so - └── VaultMergeService.cs +| Platform | Output Location | Files | +|----------|----------------|-------| +| Browser Extension | `apps/browser-extension/src/utils/dist/core/rust/` | `aliasvault_core.js`, `.wasm`, `.d.ts` | +| Blazor WASM | `apps/server/AliasVault.Client/wwwroot/wasm/` | `aliasvault_core.js`, `.wasm` | +| iOS | `apps/mobile-app/ios/VaultStoreKit/RustCore/` | `.a`, `.h`, `.swift` | +| Android | `apps/mobile-app/android/app/src/main/jniLibs/` | `.so` per ABI + `.kt` | + +### Browser Extension Usage + +```typescript +import init, { myFunction, myFunctionJson } from '@/utils/dist/core/rust/aliasvault_core.js'; + +// Initialize once +await init(wasmBytes); + +// Typed API +const result = myFunction({ data: "test" }); + +// JSON API +const jsonResult = myFunctionJson('{"data": "test"}'); ``` -## Usage - -### Swift (iOS) +### iOS Usage (Swift) ```swift -import AliasVaultCore - -let mergeService = VaultMergeService() - -do { - let result = try mergeService.merge( - localVaultBase64: localVault, - serverVaultBase64: serverVault - ) - - print("Merged \(result.stats.tablesProcessed) tables") - print("New vault: \(result.mergedVaultBase64.prefix(50))...") -} catch { - print("Merge failed: \(error)") -} +// Generated UniFFI bindings in aliasvault_core.swift +let result = try myFunctionJson(inputJson: jsonString) ``` -### Kotlin (Android) +### Android Usage (Kotlin) ```kotlin -import net.aliasvault.core.VaultMergeService +// Generated UniFFI bindings +val result = uniffi.aliasvault_core.myFunctionJson(jsonString) +``` -val mergeService = VaultMergeService() +## Design Decisions -try { - val result = mergeService.merge( - localVaultBase64 = localVault, - serverVaultBase64 = serverVault - ) +### JSON-First Communication - println("Merged ${result.stats.tablesProcessed} tables") - println("Conflicts: ${result.stats.conflicts}") -} catch (e: Exception) { - println("Merge failed: ${e.message}") +All interfaces use JSON strings for input/output. This simplifies: +- Cross-language type marshalling +- Debugging (human-readable) +- Consistency across platforms + +For WASM, we also provide typed APIs using `serde-wasm-bindgen` for better TypeScript integration. + +### Feature Flags + +| Feature | Purpose | Used By | +|---------|---------|---------| +| `uniffi` | UniFFI runtime support | iOS/Android builds | +| `uniffi-cli` | UniFFI binding generator | Build script only | +| `wasm` | WASM + JS interop | Browser extension | +| `ffi` | C-compatible exports | .NET server | + +Use `uniffi` (not `uniffi-cli`) for library builds to avoid pulling in heavy bindgen dependencies. + +### Error Handling + +Use `VaultError` from `src/error.rs` for all errors: + +```rust +use crate::error::{VaultError, VaultResult}; + +pub fn my_function() -> VaultResult { + // serde errors auto-convert via From impl + let data: MyType = serde_json::from_str(json)?; + + // Manual error creation + if data.invalid { + return Err(VaultError::General("Invalid data".to_string())); + } + + Ok("success".to_string()) } ``` -### C# (.NET) +## File Reference -```csharp -using AliasVault.Shared.RustCore; - -var result = VaultMergeService.Merge(localVaultBase64, serverVaultBase64); - -Console.WriteLine($"Merged {result.Stats.TablesProcessed} tables"); -Console.WriteLine($"Conflicts: {result.Stats.Conflicts}"); -Console.WriteLine($"Records from server: {result.Stats.RecordsFromServer}"); -``` - -## Development - -### Running tests -```bash -cargo test -``` - -### Checking code -```bash -cargo clippy -cargo fmt --check -``` - -### Adding new functionality - -1. Add Rust implementation in `src/` -2. Update `src/aliasvault_core.udl` for UniFFI bindings -3. Run `./build.sh` to generate all bindings - -## Binary Size Considerations - -The compiled native libraries include SQLite bundled statically. Approximate sizes: - -| Target | Approximate Size | -|--------|-----------------| -| iOS (arm64 static) | ~20MB | -| Android (arm64 shared) | ~2-3MB | -| macOS (.dylib) | ~16KB (dynamic) | - -> **Note:** Binaries are NOT committed to the repository. They are built: -> 1. Locally via `./build.sh` when needed -> 2. In CI pipelines for deployment -> 3. In Docker builds for containerized deployments - -## Why Rust for Native, TypeScript for Browser? - -- **Browser Extensions**: Already use sql.js (SQLite compiled to WASM). Re-compiling SQLite via Rust to WASM adds complexity without benefit since sql.js already handles this well. -- **Native Platforms**: Rust compiles to native code with excellent SQLite support. UniFFI generates idiomatic Swift/Kotlin bindings automatically. -- **Consistency**: Both implementations follow the same LWW merge algorithm, ensuring identical behavior across all platforms. - -## License - -MIT License - see the main AliasVault repository for details. +| File | Purpose | +|------|---------| +| `src/lib.rs` | Entry point, exports all modules | +| `src/error.rs` | `VaultError` and `VaultResult` types | +| `src/vault_merge/mod.rs` | LWW merge implementation | +| `src/vault_merge/types.rs` | Table configurations, composite keys | +| `src/vault_pruner/mod.rs` | Trash cleanup implementation | +| `src/credential_matcher/mod.rs` | Autofill filtering algorithm | +| `src/credential_matcher/domain.rs` | URL/domain extraction utilities | +| `src/credential_matcher/stop_words.rs` | Text filtering for service name matching | +| `src/wasm.rs` | WASM bindings (browser) | +| `src/uniffi_api.rs` | UniFFI bindings (iOS/Android) | +| `src/ffi.rs` | C FFI bindings (.NET) | +| `uniffi-bindgen.rs` | UniFFI CLI binary entry point | +| `build.sh` | Multi-platform build script | +| `Cargo.toml` | Dependencies and feature flags | \ No newline at end of file