Skip to content

Commit

Permalink
[uniffi] Add a client_config_default function
Browse files Browse the repository at this point in the history
This function returns a client config with some simple defaults. Right
now, the default is in-memory storage.

The new `GroupStateStorageAdapter` struct allows us to use any mls-rs
group state storage, so we could easily surface the Sqlite storage as
well now.
  • Loading branch information
mgeisler committed Mar 12, 2024
1 parent bb7a5e1 commit 5576dd3
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 12 deletions.
18 changes: 16 additions & 2 deletions mls-rs-uniffi/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::{fmt::Debug, sync::Arc};
use std::fmt::Debug;
use std::sync::Arc;

use mls_rs::{
client_builder::{self, WithGroupStateStorage},
identity::basic,
storage_provider::in_memory::InMemoryGroupStateStorage,
};
use mls_rs_crypto_openssl::OpensslCryptoProvider;

use self::group_state::GroupStateStorage;
use self::group_state::{GroupStateStorage, GroupStateStorageAdapter};
use crate::Error;

pub mod group_state;
Expand Down Expand Up @@ -63,3 +65,15 @@ pub type UniFFIConfig = client_builder::WithIdentityProvider<
pub struct ClientConfig {
pub group_state_storage: Arc<dyn GroupStateStorage>,
}

// TODO(mgeisler): turn into an associated function when UniFFI
// supports them: https://github.com/mozilla/uniffi-rs/issues/1074.
/// Create a client config with an in-memory group state storage.
#[uniffi::export]
pub fn client_config_default() -> ClientConfig {
ClientConfig {
group_state_storage: Arc::new(GroupStateStorageAdapter::new(
InMemoryGroupStateStorage::new(),
)),
}
}
87 changes: 77 additions & 10 deletions mls-rs-uniffi/src/config/group_state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use mls_rs::error::IntoAnyError;
use std::fmt::Debug;
use std::sync::Mutex;

use crate::Error;

Expand All @@ -21,20 +23,26 @@ pub struct EpochRecord {
}

impl From<mls_rs_core::group::GroupState> for GroupState {
fn from(value: mls_rs_core::group::GroupState) -> Self {
Self {
id: value.id,
data: value.data,
}
fn from(mls_rs_core::group::GroupState { id, data }: mls_rs_core::group::GroupState) -> Self {
Self { id, data }
}
}

impl From<GroupState> for mls_rs_core::group::GroupState {
fn from(GroupState { id, data }: GroupState) -> Self {
Self { id, data }
}
}

impl From<mls_rs_core::group::EpochRecord> for EpochRecord {
fn from(value: mls_rs_core::group::EpochRecord) -> Self {
Self {
id: value.id,
data: value.data,
}
fn from(mls_rs_core::group::EpochRecord { id, data }: mls_rs_core::group::EpochRecord) -> Self {
Self { id, data }
}
}

impl From<EpochRecord> for mls_rs_core::group::EpochRecord {
fn from(EpochRecord { id, data }: EpochRecord) -> Self {
Self { id, data }
}
}

Expand All @@ -54,3 +62,62 @@ pub trait GroupStateStorage: Send + Sync + Debug {

async fn max_epoch_id(&self, group_id: Vec<u8>) -> Result<Option<u64>, Error>;
}

/// Adapt a mls-rs `GroupStateStorage` implementation.
///
/// This is used to adapt a mls-rs `GroupStateStorage` implementation
/// to our own `GroupStateStorage` trait. This way we can use any
/// standard mls-rs group state storage from the FFI layer.
#[derive(Debug)]
pub(crate) struct GroupStateStorageAdapter<S>(Mutex<S>);

impl<S> GroupStateStorageAdapter<S> {
pub fn new(group_state_storage: S) -> GroupStateStorageAdapter<S> {
Self(Mutex::new(group_state_storage))
}

fn inner(&self) -> std::sync::MutexGuard<'_, S> {
self.0.lock().unwrap()
}
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
impl<S, Err> GroupStateStorage for GroupStateStorageAdapter<S>
where
S: mls_rs::GroupStateStorage<Error = Err> + Debug,
Err: IntoAnyError,
{
async fn state(&self, group_id: Vec<u8>) -> Result<Option<Vec<u8>>, Error> {
self.inner()
.state(&group_id)
.map_err(|err| err.into_any_error().into())
}

async fn epoch(&self, group_id: Vec<u8>, epoch_id: u64) -> Result<Option<Vec<u8>>, Error> {
self.inner()
.epoch(&group_id, epoch_id)
.map_err(|err| err.into_any_error().into())
}

async fn write(
&self,
state: GroupState,
epoch_inserts: Vec<EpochRecord>,
epoch_updates: Vec<EpochRecord>,
) -> Result<(), Error> {
self.inner()
.write(
state.into(),
epoch_inserts.into_iter().map(Into::into).collect(),
epoch_updates.into_iter().map(Into::into).collect(),
)
.map_err(|err| err.into_any_error().into())
}

async fn max_epoch_id(&self, group_id: Vec<u8>) -> Result<Option<u64>, Error> {
self.inner()
.max_epoch_id(&group_id)
.map_err(|err| err.into_any_error().into())
}
}
8 changes: 8 additions & 0 deletions mls-rs-uniffi/tests/client_config_default_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from mls_rs_uniffi import Client, CipherSuite, generate_signature_keypair, client_config_default

client_config = client_config_default()
key = generate_signature_keypair(CipherSuite.CURVE25519_AES128)
alice = Client(b'alice', key, client_config)

group = alice.create_group(None)
group.write_to_storage()
1 change: 1 addition & 0 deletions mls-rs-uniffi/tests/scenarios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ macro_rules! generate_python_tests {
}

generate_python_tests!(generate_signature_keypair, None);
generate_python_tests!(client_config_default_sync, None);

// TODO(mulmarta): it'll break if we use async trait which will be
// supported in the next UniFFI release
Expand Down

0 comments on commit 5576dd3

Please sign in to comment.