Skip to content

Client persistence

Konrad Kohbrok edited this page Jun 3, 2024 · 4 revisions

The coreclient stores all of its persistent state in sqlite databases. Sqlite is a performant and well-tested database that is the de-facto standard for mobile applications. It is open-source and available for all major plattforms.

Databases

Upon first startup, the client application creates the client identifier database. Empty at first, this database contains identifiers for all client instances created by the application device. When creating the first client instance, the client's identifier is stored in the client identifier database, before the application creates a separate database with client-specific information and fills it with the initial client data. Separation of client data via distinct databases allows us to back-up and generally manage data of different local clients individually.

Database schema overview

Each client database contains the following tables:

  • user_creation_state: Partial user/client data as a user/client is registered with the server to ensure that user/client creation can resume even if the application terminates during creation
  • own_client_info: Basic information relevant to this client such as the client's client id, as well as the URL of the provider's server
  • users: Information specific to individual users (including the user of this client) that the user of this client is aware of such as display names and profile pictures
  • contacts: Additional user-level data of users that the owner of this client has a connection with
  • conversations: Conversation information such as conversation id, associated MLS group id and conversation picture
  • conversation_participation: Pairs of user names and conversation ids that indicate which users participate in which conversation
  • conversation_messages: Messages sent and received in each individual conversation
  • groups: MLS group specific data indexed by the group id of the individual groups
  • group_membership: Data on which clients are members of which groups. This is required in addition to the MLS-based data, as the MLS-based data only contains the members' pseudonyms
  • leaf_keys: Private authentication key material of all groups that the client is a member of
  • client_credentials: Credentials of all clients that this client is aware of
  • as_credentials: AS credentials of all providers that the client is aware of
  • qs_verifying_key: The verifying key of the user's own provider's QS
  • queue_ratchets: Key material and sequence numbers for the client's AS and QS queues
  • randomness_seed: The client's current randomness. Updated and queried whenever the client samples randomness.

Consistency

Sqlite supports a variety of constraints that can help enforce certain properties of a dataset in a given table. In a similar way to how the client ensures properties of individual structs using Rust's type system, each table enforces properties that involve all instances of such a struct. For example, the conversations table ensures that conversation IDs are locally unique, or that for each conversation, each user can be a member only once. Similarly the foreign key constraint ensures that datasets stay consistent across multiple tables.

Another technique that helps with consistency is the trigger feature of Sqlite, which allows one operation in one table to trigger another operation in another. This helps, for example, ensure that client credentials are deleted as soon as the respective client is no longer in any group.

Persistence trait

Each table on the Sqlite side has a corresponding struct on the Rust side of things. Each of these structs implements the Persistence trait, which provides a few basic methods and the implementation of which contains the table creation statement. A similar trait exists to set up triggers pertaining to a given struct. During startup the application creates all necessary tables and triggers.

Since the way data is retrieved or modified depends on the individual struct, such functions are implemented outside of the trait.

All persisted structs live in their own Rust module, which in turn has a separate persistence submodule that contains any and all database logic.

The client uses the rusqlite crate to facilitate interactions with the Sqlite database.

Clone this wiki locally