Skip to content

[uniffi] Reintroduce async support for UniFFI #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"] }