AliasVault Rust Core
Cross-platform core library providing shared business logic for all AliasVault clients:
- 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 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") │
└─────────────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ WASM Module │ │ UniFFI (FFI) │ │ C FFI (.NET) │
│ wasm-bindgen │ │ Swift + Kotlin │ │ P/Invoke │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Browser │ │ iOS & Android │ │ Server │
│ Extensions │ │ Mobile Apps │ │ (.NET) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Core Modules
vault_merge
Last-Write-Wins (LWW) merge algorithm for syncing local and server vaults.
vault_pruner
Permanently deletes items in trash older than retention period (default: 30 days).
credential_matcher
Priority-based credential filtering for autofill with anti-phishing protection.
Building
./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
Testing
cargo test # All tests
cargo test vault_merge # Specific module
cargo test --features uniffi # With UniFFI enabled
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/:
// 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<MyOutput> {
// Implementation
Ok(MyOutput { result: input.data })
}
/// JSON wrapper for cross-platform convenience.
pub fn my_function_json(input_json: &str) -> VaultResult<String> {
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
// src/lib.rs
pub mod my_module;
pub use my_module::{my_function, MyInput, MyOutput};
3. Add WASM Bindings (src/wasm.rs)
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<JsValue, JsValue> {
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<String, JsValue> {
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)
use crate::error::VaultError;
/// JSON API only - UniFFI handles type conversion automatically.
#[uniffi::export]
pub fn my_function_json(input_json: String) -> Result<String, VaultError> {
crate::my_module::my_function_json(&input_json)
}
5. Add C FFI Bindings (src/ffi.rs) - If Needed for .NET
/// 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:
[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
// 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
# Build for all targets you support
./build.sh --browser --ios --android
# Or just the ones you need during development
./build.sh --browser
Platform Consumption
After building, the outputs are distributed to:
| 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
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"}');
iOS Usage (Swift)
// Generated UniFFI bindings in aliasvault_core.swift
let result = try myFunctionJson(inputJson: jsonString)
Android Usage (Kotlin)
// Generated UniFFI bindings
val result = uniffi.aliasvault_core.myFunctionJson(jsonString)
Design Decisions
JSON-First Communication
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:
use crate::error::{VaultError, VaultResult};
pub fn my_function() -> VaultResult<String> {
// 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())
}
File Reference
| 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 |