Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Client

In MLS terminology a client is a single device acting on behalf of a user — each phone, desktop, or web session that participates in MLS groups is a distinct client with its own identity and key material. CoreCrypto maps this concept to the CoreCrypto object itself. One CoreCrypto instance, backed by one Database, represents one MLS client.

Note

The Rust library internally calls this type Session to avoid ambiguity — “client” means something different at the Wire application layer (where it refers to a registered device in the Wire backend sense) and at the MLS protocol layer. The name Session does not appear in platform bindings; from Swift, Kotlin, and TypeScript, the type is always CoreCrypto.

Sharing a CoreCrypto Instance

CoreCrypto is cheap to clone. All internal state — the database connection, the MLS session, the Proteus state — is reference-counted, so cloning the object creates a new handle to the same shared state rather than copying anything. This means CoreCrypto can be freely passed to background tasks or across threads without wrapping it in an additional mutex or reference type.

Read-Only Operations

Most MLS work happens inside a transaction (see Transactions and the TransactionContext), but a handful of read-only operations are available directly on CoreCrypto without opening one. The set of read-only operations available on CoreCrypto is intentionally kept narrow.

Any operation that might change state — creating or deleting a conversation, adding members, encrypting or decrypting a message — requires a transaction.

Lifetimes

The lifetime for objects created by CoreCrypto vary slightly depending on the platform. Objects have a rust counterpart, and the rust counterpart might hold on to resources like files or database connections. When exactly the rust counterpart and its resources are freed depends on the platform.

Swift

When last reference to the object is dropped and deinit runs.

Kotlin

When the object is garbage collected or explicitly closed by calling close() (Autocloseable).

More details: https://mozilla.github.io/uniffi-rs/latest/kotlin/lifetimes.html

TypeScript

When the object is garbage collected or explicitly destroyed by calling uniffiDestroy().

More Details: https://jhugman.github.io/uniffi-bindgen-react-native/idioms/gc.html#garbage-collected-objects-trigger-a-drop-call-into-rust

Lifecycle

A CoreCrypto instance is typically created once at application startup and kept alive for the life of the process. The initialization sequence is:

const db = await Database.open(path, key);       // open the keystore
const cc = CoreCrypto.new(db);                   // construct CoreCrypto
await cc.transaction(async (ctx) => {
    await ctx.mlsInit(clientId, transport);      // initialize the MLS session
});
let db = try await Database.open(location: path, key: key)  // open the keystore
let cc = CoreCrypto(database: db)                            // construct CoreCrypto
try await cc.transaction { ctx in
    try await ctx.mlsInit(clientId: clientId, transport: transport)  // initialize the MLS session
}
val db = Database.open(path, key)  // open the keystore
val cc = CoreCrypto(db)            // construct CoreCrypto
cc.transaction { ctx ->
    ctx.mlsInit(clientId, transport)  // initialize the MLS session
}

After mlsInit() is committed, the instance is ready to use.

Multiple Instances

Only one CoreCrypto instance (and its clones) should be active against a given database file at a time. The iOS client has a file lock which prevents two instances of the CoreCrypto from interfering with each other. On other clients opening the same database from two independent CoreCrypto instances — whether in the same process or different processes — is not supported and will produce undefined behavior.