diff --git a/mls-rs-uniffi/src/lib.rs b/mls-rs-uniffi/src/lib.rs index 873878be..c1f4aad6 100644 --- a/mls-rs-uniffi/src/lib.rs +++ b/mls-rs-uniffi/src/lib.rs @@ -198,6 +198,51 @@ impl From for Proposal { } } +/// Update of a member due to a commit. +#[derive(Clone, Debug, uniffi::Record)] +pub struct MemberUpdate { + pub prior: Arc, + pub new: Arc, +} + +/// A set of roster updates due to a commit. +#[derive(Clone, Debug, uniffi::Record)] +pub struct RosterUpdate { + pub added: Vec>, + pub removed: Vec>, + pub updated: Vec, +} + +impl RosterUpdate { + // This is an associated function because it felt wrong to hide + // the clones in an `impl From<&mls_rs::identity::RosterUpdate>`. + fn new(roster_update: &mls_rs::identity::RosterUpdate) -> Self { + let added = roster_update + .added() + .iter() + .map(|member| Arc::new(member.signing_identity.clone().into())) + .collect(); + let removed = roster_update + .removed() + .iter() + .map(|member| Arc::new(member.signing_identity.clone().into())) + .collect(); + let updated = roster_update + .updated() + .iter() + .map(|update| MemberUpdate { + prior: Arc::new(update.prior.signing_identity.clone().into()), + new: Arc::new(update.new.signing_identity.clone().into()), + }) + .collect(); + RosterUpdate { + added, + removed, + updated, + } + } +} + /// A [`mls_rs::group::ReceivedMessage`] wrapper. #[derive(Clone, Debug, uniffi::Enum)] pub enum ReceivedMessage { @@ -208,7 +253,10 @@ pub enum ReceivedMessage { }, /// A new commit was processed creating a new group state. - Commit { committer: Arc }, + Commit { + committer: Arc, + roster_update: RosterUpdate, + }, // TODO(mgeisler): rename to `Proposal` when // https://github.com/awslabs/mls-rs/issues/98 is fixed. @@ -343,6 +391,11 @@ impl Client { Ok(message.into()) } + pub fn signing_identity(&self) -> Result, Error> { + let (signing_identity, _) = self.inner.signing_identity()?; + Ok(Arc::new(signing_identity.clone().into())) + } + /// Create and immediately join a new group. /// /// If a group ID is not given, the underlying library will create @@ -475,7 +528,8 @@ impl TryFrom for CommitOutput { } } -#[derive(Clone, Debug, uniffi::Object)] +#[derive(Clone, Debug, PartialEq, Eq, uniffi::Object)] +#[uniffi::export(Eq)] pub struct SigningIdentity { inner: identity::SigningIdentity, } @@ -686,7 +740,11 @@ impl Group { group::ReceivedMessage::Commit(commit_message) => { let committer = Arc::new(index_to_identity(&group, commit_message.committer)?.into()); - Ok(ReceivedMessage::Commit { committer }) + let roster_update = RosterUpdate::new(commit_message.state_update.roster_update()); + Ok(ReceivedMessage::Commit { + committer, + roster_update, + }) } group::ReceivedMessage::Proposal(proposal_message) => { let sender = match proposal_message.sender { diff --git a/mls-rs-uniffi/tests/roster_update_sync.py b/mls-rs-uniffi/tests/roster_update_sync.py new file mode 100644 index 00000000..1b886814 --- /dev/null +++ b/mls-rs-uniffi/tests/roster_update_sync.py @@ -0,0 +1,22 @@ +from mls_rs_uniffi import Client, CipherSuite, generate_signature_keypair, client_config_default + +client_config = client_config_default() +alice = Client(b'alice', generate_signature_keypair(CipherSuite.CURVE25519_AES128), client_config) +bob = Client(b'bob', generate_signature_keypair(CipherSuite.CURVE25519_AES128), client_config) +carla = Client(b'carla', generate_signature_keypair(CipherSuite.CURVE25519_AES128), client_config) + +# Alice creates a group and adds Bob. +alice_group = alice.create_group(None) +output = alice_group.add_members([bob.generate_key_package_message()]) +alice_group.process_incoming_message(output.commit_message) + +# Bob join the group and adds Carla. +bob_group = bob.join_group(None, output.welcome_message).group +output = bob_group.add_members([carla.generate_key_package_message()]) +bob_group.process_incoming_message(output.commit_message) + +# Alice learns that Carla has been added to the group. +received = alice_group.process_incoming_message(output.commit_message) +assert received.roster_update.added == [carla.signing_identity()] +assert received.roster_update.removed == [] +assert received.roster_update.updated == [] diff --git a/mls-rs-uniffi/tests/scenarios.rs b/mls-rs-uniffi/tests/scenarios.rs index c1c73d18..47e5e931 100644 --- a/mls-rs-uniffi/tests/scenarios.rs +++ b/mls-rs-uniffi/tests/scenarios.rs @@ -50,3 +50,4 @@ generate_python_tests!(client_config_default_sync, client_config_default_async); generate_python_tests!(custom_storage_sync, None); generate_python_tests!(simple_scenario_sync, simple_scenario_async); generate_python_tests!(ratchet_tree_sync, ratchet_tree_async); +generate_python_tests!(roster_update_sync, None); diff --git a/mls-rs-uniffi/tests/simple_scenario_async.py b/mls-rs-uniffi/tests/simple_scenario_async.py index fb00c6c2..610efe36 100644 --- a/mls-rs-uniffi/tests/simple_scenario_async.py +++ b/mls-rs-uniffi/tests/simple_scenario_async.py @@ -18,7 +18,7 @@ async def scenario(): 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 + bob = (await bob.join_group(None, commit.welcome_message)).group msg = await alice.encrypt_application_message(b'hello, bob') output = await bob.process_incoming_message(msg)