pub trait MlsStorage: Send + Sync {
// Required methods
fn store(
&self,
key_type: &str,
key_id: &str,
data: &[u8],
) -> StorageResult<()>;
fn load(
&self,
key_type: &str,
key_id: &str,
) -> StorageResult<Option<Vec<u8>>>;
fn delete(&self, key_type: &str, key_id: &str) -> StorageResult<()>;
fn list_keys(&self, key_type: &str) -> StorageResult<Vec<String>>;
// Provided methods
fn exists(&self, key_type: &str, key_id: &str) -> StorageResult<bool> { ... }
fn clear_type(&self, key_type: &str) -> StorageResult<()> { ... }
}Expand description
Trait for MLS key material storage.
Apps must implement this trait to provide secure storage for MLS cryptographic material. The implementation should use platform-native secure storage:
- iOS: Keychain Services
- Android: EncryptedSharedPreferences or Keystore
- Desktop: OS-specific secure storage or encrypted file storage
§Key Types
The storage uses a two-part key system: (key_type, key_id) where:
key_type: Category of data (e.g., “identity”, “key_package”, “group_state”)key_id: Unique identifier within that category
§Thread Safety
Implementations must be thread-safe (Send + Sync).
§Example
struct KeychainStorage;
impl MlsStorage for KeychainStorage {
fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> StorageResult<()> {
// Store in iOS Keychain
keychain_store(&format!("{}:{}", key_type, key_id), data)?;
Ok(())
}
// ... other methods
}Required Methods§
Sourcefn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> StorageResult<()>
fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> StorageResult<()>
Stores data with the given key type and ID.
If data already exists for this key, it should be overwritten.
Implementations should commit atomically for each (key_type, key_id) entry:
after Ok(()), a subsequent load() from any thread/process must return
either the old full value or the new full value, never a torn write.
Implementations should also provide best-effort crash durability so power
loss does not silently roll back acknowledged critical state writes.
Durability is ordering-critical, not just best practice: the SDK treats
a returned Ok(()) as persisted and immediately writes state that depends
on it. The per-install protocol-state record key is the sharp case — the
moment it stores, sealed records start landing in the (separate,
app-container) protocol-state store. A backend that acknowledges a write
it has only staged in memory (Android’s SharedPreferences.apply(), an
unflushed file handle) can crash into a state where those records exist
but their key never did, and they are then dropped as unauthentic.
§Arguments
key_type- Category of the data (e.g., “identity”, “group_state”)key_id- Unique identifier within the categorydata- Binary data to store
Sourcefn load(&self, key_type: &str, key_id: &str) -> StorageResult<Option<Vec<u8>>>
fn load(&self, key_type: &str, key_id: &str) -> StorageResult<Option<Vec<u8>>>
Loads data for the given key type and ID.
Returns Ok(None) if the key doesn’t exist.
Returns Err only for actual failures (corruption, access denied, etc.).
§Which error
Reserve StorageError::CorruptedData for a record that is present
and can never be read by any later launch: its bytes are damaged, or
it does not authenticate. A failure that a later launch might not hit
(a locked keystore, an I/O error, a device not yet unlocked after
boot) is StorageError::LoadFailed.
The distinction is acted on, not only logged. The engine answers
CorruptedData on the protocol-state record key by minting a new one,
which makes every protocol-state record sealed under the old key
unreadable for good; it answers LoadFailed by leaving the key alone
and not persisting sensitive state for that session. A transient fault
reported as CorruptedData therefore destroys recoverable state.
§Arguments
key_type- Category of the datakey_id- Unique identifier within the category
Provided Methods§
Sourcefn exists(&self, key_type: &str, key_id: &str) -> StorageResult<bool>
fn exists(&self, key_type: &str, key_id: &str) -> StorageResult<bool>
Checks if a key exists.
Default implementation uses load, but can be overridden for efficiency.
Sourcefn clear_type(&self, key_type: &str) -> StorageResult<()>
fn clear_type(&self, key_type: &str) -> StorageResult<()>
Clears all data of a given key type.
Default implementation lists and deletes each key, but can be overridden for efficiency.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".