pub enum StorageBackend {
Memory,
Sqlite {
path: PathBuf,
encryption_key: Option<Zeroizing<[u8; 32]>>,
},
SqliteReadOnly {
path: PathBuf,
encryption_key: Option<Zeroizing<[u8; 32]>>,
},
AsyncBlob {
blob_store: Arc<dyn AsyncBlobStore>,
},
AsyncBlobReadOnly {
blob_store: Arc<dyn AsyncBlobStore>,
},
}Expand description
[CR-4] re-export so hosts can build ClientConfig { storage_backend: … } without
pulling ping_mls_store in directly. AsyncBlobStore is the async
single-blob trait WASM hosts implement to back StorageBackend::IndexedDb;
BlobFuture is the matching future-type helper.
Where the persistent provider checkpoints MemoryStorage to. Selected at
MessagingClient::init time via [ping_core::ClientConfig::storage_backend].
Variants§
Memory
In-memory only. Default and what tests use; loses state on process exit.
Cold-start scenarios (iOS NSE, web Service Worker) MUST use Self::Sqlite
or [Self::IndexedDb] instead.
Sqlite
SQLite-backed; native targets only. The SDK owns the file at path; the
parent directory must exist. Pass encryption_key = Some(...) to enable
SQLCipher; absence means the file is unencrypted (tests, dev only).
Fields
SqliteReadOnly
SQLite-backed, opened READ-ONLY and never written back; native only.
Exists for one job: letting a SECOND process (the iOS Notification Service Extension) decrypt an inbound message for a lock-screen preview while the main app remains the sole writer.
That restriction is not caution, it is the only sound arrangement given
how this crate persists. Self::Sqlite checkpoints the ENTIRE
MemoryStorage map as one CBOR blob in a single row, so two writers do
not merge — the second one to flush replaces every mutation the first
made, silently discarding epochs, ratchet state, and freshly-stored key
material. Read-only removes that failure mode by construction: a reader
takes a consistent WAL snapshot, decrypts from it, and drops everything.
Nothing it touches can outlive the process, so nothing it does can be
observed by the writer.
The cost is that the ratchet advance is thrown away, so the main app decrypts the same message again from its own state. That is correct and intended — the two are independent readers of the same snapshot, and neither can desynchronise the other.
[PersistentMlsProvider::checkpoint] is a NO-OP against this backend
rather than an error, so shared code paths that flush opportunistically
stay usable. Pair it with a read-only host Storage and
[ping_core::MessagingClient::open_read_only], which refuses the
state-creating paths (LocalDevice minting, DeviceGroup creation) that
would otherwise write through a channel this backend does not cover.
Fields
path: PathBufAbsolute path to a file another process owns. Must already exist —
unlike Self::Sqlite, this variant never creates one, because a
created file would mean the writer’s real store was not found and
silently decrypting nothing is worse than failing loudly.
AsyncBlob
Host-supplied async blob storage. Available on every target.
The provider snapshots the entire MemoryStorage HashMap into a
single CBOR blob and round-trips it through the
AsyncBlobStore the host implements. This is the universal
persistence path:
- WASM: host wraps its IndexedDB layer (PingStorageWeb, which already AES-GCM-encrypts every row under a non-extractable wrap key kept inside the same IDB).
- iOS / macOS / Android: host wraps the same
Storagetrait it already provides for conv metadata + cursors (typically Keychain-encrypted SQLite or AsyncStorage), so the OpenMLS snapshot sits under a reserved("__mls", "snapshot")slot alongside the host’s other persisted KV.
Why this replaces the wasm-only IndexedDb variant: any host
that already implements Storage (which every binding does)
gets persistence for free, without per-platform path management,
Keychain key plumbing, or SQLCipher dependency. The SQLite
backend remains available for hosts that want a separate file
(e.g. iOS NSE cold-start where the main app’s Storage isn’t
reachable from the extension).
Fields
blob_store: Arc<dyn AsyncBlobStore>AsyncBlobReadOnly
Self::AsyncBlob read-only — the universal-target counterpart to
Self::SqliteReadOnly, for the same job on a platform with no SQLite.
The snapshot is read through the host’s blob store at open and never written back, so a second context (a web Service Worker decrypting a push for its notification, the mirror of the iOS NSE) can read the tab’s state without racing it. Same reasoning, same guarantee: the whole-state blob means two writers replace rather than merge, so the reader must not be one.
This variant is what keeps
[ping_core::MessagingClient::open_read_only] a genuinely cross-platform
API rather than a native-only one — on WASM it is the only backend that
can satisfy it.
Fields
blob_store: Arc<dyn AsyncBlobStore>Trait Implementations§
Source§impl Clone for StorageBackend
impl Clone for StorageBackend
Source§fn clone(&self) -> StorageBackend
fn clone(&self) -> StorageBackend
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for StorageBackend
impl Debug for StorageBackend
Source§impl Default for StorageBackend
impl Default for StorageBackend
Source§fn default() -> StorageBackend
fn default() -> StorageBackend
Auto Trait Implementations§
impl !RefUnwindSafe for StorageBackend
impl !UnwindSafe for StorageBackend
impl Freeze for StorageBackend
impl Send for StorageBackend
impl Sync for StorageBackend
impl Unpin for StorageBackend
impl UnsafeUnpin for StorageBackend
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Declassify for T
impl<T> Declassify for T
type Declassified = T
fn declassify(self) -> T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more