Skip to main content

MlsStorage

Trait MlsStorage 

Source
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§

Source

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 category
  • data - Binary data to store
Source

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 data
  • key_id - Unique identifier within the category
Source

fn delete(&self, key_type: &str, key_id: &str) -> StorageResult<()>

Deletes data for the given key type and ID.

Should succeed even if the key doesn’t exist.

§Arguments
  • key_type - Category of the data
  • key_id - Unique identifier within the category
Source

fn list_keys(&self, key_type: &str) -> StorageResult<Vec<String>>

Lists all key IDs for a given key type.

This is used for enumerating groups, key packages, etc.

§Arguments
  • key_type - Category of data to list

Provided Methods§

Source

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.

Source

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".

Implementors§