Skip to content

Commit

Permalink
[uniffi] Reintroduce async support for UniFFI (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgeisler authored Mar 25, 2024
1 parent 98cf9fe commit b9b2715
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 25 deletions.
4 changes: 2 additions & 2 deletions mls-rs-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ mls-rs = { version = "0.39.0", path = "../mls-rs" }
mls-rs-core = { version = "0.18.0", path = "../mls-rs-core" }
mls-rs-crypto-openssl = { version = "0.9.0", path = "../mls-rs-crypto-openssl" }
thiserror = "1.0.57"
uniffi = "0.26.0"
uniffi = { git = "https://github.com/mozilla/uniffi-rs/", rev = "6b09f11", version = "0.26.0" }

[target.'cfg(mls_build_async)'.dependencies]
tokio = { version = "1.36.0", features = ["sync"] }

[dev-dependencies]
uniffi_bindgen = "0.26.0"
uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs/", rev = "6b09f11", version = "0.26.0" }
20 changes: 11 additions & 9 deletions mls-rs-uniffi/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ impl mls_rs_core::group::GroupStateStorage for ClientGroupStorage {
type Error = Error;

async fn state(&self, group_id: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
self.0.state(group_id.to_vec())
self.0.state(group_id.to_vec()).await
}

async fn epoch(&self, group_id: &[u8], epoch_id: u64) -> Result<Option<Vec<u8>>, Self::Error> {
self.0.epoch(group_id.to_vec(), epoch_id)
self.0.epoch(group_id.to_vec(), epoch_id).await
}

async fn write(
Expand All @@ -41,16 +41,18 @@ impl mls_rs_core::group::GroupStateStorage for ClientGroupStorage {
inserts: Vec<mls_rs_core::group::EpochRecord>,
updates: Vec<mls_rs_core::group::EpochRecord>,
) -> Result<(), Self::Error> {
self.0.write(
state.id,
state.data,
inserts.into_iter().map(Into::into).collect(),
updates.into_iter().map(Into::into).collect(),
)
self.0
.write(
state.id,
state.data,
inserts.into_iter().map(Into::into).collect(),
updates.into_iter().map(Into::into).collect(),
)
.await
}

async fn max_epoch_id(&self, group_id: &[u8]) -> Result<Option<u64>, Self::Error> {
self.0.max_epoch_id(group_id.to_vec())
self.0.max_epoch_id(group_id.to_vec()).await
}
}

Expand Down
25 changes: 23 additions & 2 deletions mls-rs-uniffi/src/config/group_state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use mls_rs::error::IntoAnyError;
use std::fmt::Debug;
#[cfg(not(mls_build_async))]
use std::sync::Mutex;
#[cfg(mls_build_async)]
use tokio::sync::Mutex;

use crate::Error;

Expand Down Expand Up @@ -28,9 +31,13 @@ impl From<EpochRecord> for mls_rs_core::group::EpochRecord {
}
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
// When building for async, uniffi::export has to be applied _after_
// maybe-async's injection of the async trait. When building for sync,
// the order has to be the opposite.
#[cfg_attr(mls_build_async, uniffi::export(with_foreign))]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
#[uniffi::export(with_foreign)]
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(not(mls_build_async), uniffi::export(with_foreign))]
pub trait GroupStateStorage: Send + Sync + Debug {
async fn state(&self, group_id: Vec<u8>) -> Result<Option<Vec<u8>>, Error>;
async fn epoch(&self, group_id: Vec<u8>, epoch_id: u64) -> Result<Option<Vec<u8>>, Error>;
Expand Down Expand Up @@ -59,9 +66,15 @@ impl<S> GroupStateStorageAdapter<S> {
Self(Mutex::new(group_state_storage))
}

#[cfg(not(mls_build_async))]
fn inner(&self) -> std::sync::MutexGuard<'_, S> {
self.0.lock().unwrap()
}

#[cfg(mls_build_async)]
async fn inner(&self) -> tokio::sync::MutexGuard<'_, S> {
self.0.lock().await
}
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
Expand All @@ -73,13 +86,17 @@ where
{
async fn state(&self, group_id: Vec<u8>) -> Result<Option<Vec<u8>>, Error> {
self.inner()
.await
.state(&group_id)
.await
.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()
.await
.epoch(&group_id, epoch_id)
.await
.map_err(|err| err.into_any_error().into())
}

Expand All @@ -91,17 +108,21 @@ where
epoch_updates: Vec<EpochRecord>,
) -> Result<(), Error> {
self.inner()
.await
.write(
mls_rs_core::group::GroupState { id, data },
epoch_inserts.into_iter().map(Into::into).collect(),
epoch_updates.into_iter().map(Into::into).collect(),
)
.await
.map_err(|err| err.into_any_error().into())
}

async fn max_epoch_id(&self, group_id: Vec<u8>) -> Result<Option<u64>, Error> {
self.inner()
.await
.max_epoch_id(&group_id)
.await
.map_err(|err| err.into_any_error().into())
}
}
13 changes: 11 additions & 2 deletions mls-rs-uniffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ impl TryFrom<mls_rs::CipherSuite> for CipherSuite {
/// See [`mls_rs::CipherSuiteProvider::signature_key_generate`]
/// for details.
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
#[uniffi::export]
pub async fn generate_signature_keypair(
cipher_suite: CipherSuite,
Expand Down Expand Up @@ -292,6 +293,7 @@ pub struct Client {
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
#[uniffi::export]
impl Client {
/// Create a new client.
Expand Down Expand Up @@ -495,6 +497,7 @@ pub struct Group {
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
impl Group {
#[cfg(not(mls_build_async))]
fn inner(&self) -> std::sync::MutexGuard<'_, mls_rs::Group<UniFFIConfig>> {
Expand All @@ -520,6 +523,7 @@ fn index_to_identity(

/// Extract the basic credential identifier from a from a key package.
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
async fn signing_identity_to_identifier(
signing_identity: &identity::SigningIdentity,
) -> Result<Vec<u8>, Error> {
Expand All @@ -531,6 +535,7 @@ async fn signing_identity_to_identifier(
}

#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
#[cfg_attr(mls_build_async, maybe_async::must_be_async)]
#[uniffi::export]
impl Group {
/// Write the current state of the group to storage defined by
Expand All @@ -545,8 +550,9 @@ impl Group {
/// This function is used to provide the current group tree to new
/// members when `use_ratchet_tree_extension` is set to false in
/// `ClientConfig`.
pub fn export_tree(&self) -> Result<RatchetTree, Error> {
self.inner().export_tree().try_into()
pub async fn export_tree(&self) -> Result<RatchetTree, Error> {
let group = self.inner().await;
group.export_tree().try_into()
}

/// Perform a commit of received proposals (or an empty commit).
Expand Down Expand Up @@ -703,8 +709,11 @@ impl Group {

#[cfg(test)]
mod tests {
#[cfg(not(mls_build_async))]
use super::*;
#[cfg(not(mls_build_async))]
use crate::config::group_state::{EpochRecord, GroupStateStorage};
#[cfg(not(mls_build_async))]
use std::collections::HashMap;

#[test]
Expand Down
15 changes: 15 additions & 0 deletions mls-rs-uniffi/tests/client_config_default_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import asyncio

from mls_rs_uniffi import Client, CipherSuite, generate_signature_keypair, client_config_default


async def scenario():
client_config = client_config_default()
key = await generate_signature_keypair(CipherSuite.CURVE25519_AES128)
alice = Client(b'alice', key, client_config)

group = await alice.create_group(None)
await group.write_to_storage()


asyncio.run(scenario())
2 changes: 1 addition & 1 deletion mls-rs-uniffi/tests/custom_storage_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def max_epoch_id(self, group_id: bytes):


group_state_storage = PythonGroupStateStorage()
client_config = ClientConfig(group_state_storage,
client_config = ClientConfig(group_state_storage=group_state_storage,
use_ratchet_tree_extension=True)

key = generate_signature_keypair(CipherSuite.CURVE25519_AES128)
Expand Down
11 changes: 11 additions & 0 deletions mls-rs-uniffi/tests/generate_signature_keypair_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from mls_rs_uniffi import CipherSuite, generate_signature_keypair
import asyncio


async def scenario():
signature_keypair = await generate_signature_keypair(
CipherSuite.CURVE25519_AES128)
assert signature_keypair.cipher_suite == CipherSuite.CURVE25519_AES128


asyncio.run(scenario())
20 changes: 20 additions & 0 deletions mls-rs-uniffi/tests/ratchet_tree_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import asyncio

from mls_rs_uniffi import CipherSuite, generate_signature_keypair, Client, \
client_config_default


async def scenario():
client_config = client_config_default()
client_config.use_ratchet_tree_extension = False

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

group = await alice.create_group(None)
commit = await group.commit()

assert commit.ratchet_tree is not None


asyncio.run(scenario())
15 changes: 7 additions & 8 deletions mls-rs-uniffi/tests/scenarios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,11 @@ macro_rules! generate_python_tests {
};
}

generate_python_tests!(generate_signature_keypair, None);
generate_python_tests!(client_config_default_sync, None);
generate_python_tests!(
generate_signature_keypair_sync,
generate_signature_keypair_async
);
generate_python_tests!(client_config_default_sync, client_config_default_async);
generate_python_tests!(custom_storage_sync, None);

// TODO(mulmarta): it'll break if we use async trait which will be
// supported in the next UniFFI release
// TODO(mgeisler): add back simple_scenario_async
generate_python_tests!(simple_scenario_sync, None);
generate_python_tests!(ratchet_tree_sync, None);
generate_python_tests!(simple_scenario_sync, simple_scenario_async);
generate_python_tests!(ratchet_tree_sync, ratchet_tree_async);
31 changes: 31 additions & 0 deletions mls-rs-uniffi/tests/simple_scenario_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import asyncio

from mls_rs_uniffi import CipherSuite, generate_signature_keypair, Client, \
client_config_default


async def scenario():
client_config = client_config_default()

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

key = await generate_signature_keypair(CipherSuite.CURVE25519_AES128)
bob = Client(b'bob', key, client_config)

alice = await alice.create_group(None)
message = await bob.generate_key_package_message()

commit = await alice.add_members([message])
await alice.process_incoming_message(commit.commit_message)
bob = (await bob.join_group(None, commit.welcome_messages[0])).group

msg = await alice.encrypt_application_message(b'hello, bob')
output = await bob.process_incoming_message(msg)

await alice.write_to_storage()

assert output.data == b'hello, bob'


asyncio.run(scenario())
2 changes: 1 addition & 1 deletion mls-rs-uniffi/uniffi-bindgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
uniffi = { version = "0.26.0", features = ["cli"] }
uniffi = { git = "https://github.com/mozilla/uniffi-rs/", rev = "6b09f11", version = "0.26.0", features = ["cli"] }

0 comments on commit b9b2715

Please sign in to comment.