• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 // Copyright by contributors to this project.
3 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
4 
5 //! UniFFI-compatible wrapper around mls-rs.
6 //!
7 //! This is an opinionated UniFFI-compatible wrapper around mls-rs:
8 //!
9 //! - Opinionated: the wrapper removes some flexiblity from mls-rs and
10 //!   focuses on exposing the minimum functionality necessary for
11 //!   messaging apps.
12 //!
13 //! - UniFFI-compatible: the wrapper exposes types annotated to be
14 //!   used with [UniFFI]. This makes it possible to automatically
15 //!   generate a Kotlin, Swift, ... code which calls into the Rust
16 //!   code.
17 //!
18 //! [UniFFI]: https://mozilla.github.io/uniffi-rs/
19 
20 mod config;
21 
22 use std::sync::Arc;
23 
24 use config::{ClientConfig, UniFFIConfig};
25 #[cfg(not(mls_build_async))]
26 use std::sync::Mutex;
27 #[cfg(mls_build_async)]
28 use tokio::sync::Mutex;
29 
30 use mls_rs::error::{IntoAnyError, MlsError};
31 use mls_rs::group;
32 use mls_rs::identity::basic;
33 use mls_rs::mls_rules;
34 use mls_rs::{CipherSuiteProvider, CryptoProvider};
35 use mls_rs_core::identity;
36 use mls_rs_core::identity::{BasicCredential, IdentityProvider};
37 use mls_rs_crypto_openssl::OpensslCryptoProvider;
38 
39 uniffi::setup_scaffolding!();
40 
41 /// Unwrap the `Arc` if there is a single strong reference, otherwise
42 /// clone the inner value.
arc_unwrap_or_clone<T: Clone>(arc: Arc<T>) -> T43 fn arc_unwrap_or_clone<T: Clone>(arc: Arc<T>) -> T {
44     // TODO(mgeisler): use Arc::unwrap_or_clone from Rust 1.76.
45     match Arc::try_unwrap(arc) {
46         Ok(t) => t,
47         Err(arc) => (*arc).clone(),
48     }
49 }
50 
51 #[derive(Debug, thiserror::Error, uniffi::Error)]
52 #[uniffi(flat_error)]
53 #[non_exhaustive]
54 pub enum Error {
55     #[error("A mls-rs error occurred: {inner}")]
56     MlsError {
57         #[from]
58         inner: mls_rs::error::MlsError,
59     },
60     #[error("An unknown error occurred: {inner}")]
61     AnyError {
62         #[from]
63         inner: mls_rs::error::AnyError,
64     },
65     #[error("A data encoding error occurred: {inner}")]
66     MlsCodecError {
67         #[from]
68         inner: mls_rs_core::mls_rs_codec::Error,
69     },
70     #[error("Unexpected callback error in UniFFI: {inner}")]
71     UnexpectedCallbackError {
72         #[from]
73         inner: uniffi::UnexpectedUniFFICallbackError,
74     },
75 }
76 
77 impl IntoAnyError for Error {}
78 
79 /// A [`mls_rs::crypto::SignaturePublicKey`] wrapper.
80 #[derive(Clone, Debug, uniffi::Record)]
81 pub struct SignaturePublicKey {
82     pub bytes: Vec<u8>,
83 }
84 
85 impl From<mls_rs::crypto::SignaturePublicKey> for SignaturePublicKey {
from(public_key: mls_rs::crypto::SignaturePublicKey) -> Self86     fn from(public_key: mls_rs::crypto::SignaturePublicKey) -> Self {
87         Self {
88             bytes: public_key.to_vec(),
89         }
90     }
91 }
92 
93 impl From<SignaturePublicKey> for mls_rs::crypto::SignaturePublicKey {
from(public_key: SignaturePublicKey) -> Self94     fn from(public_key: SignaturePublicKey) -> Self {
95         Self::new(public_key.bytes)
96     }
97 }
98 
99 /// A [`mls_rs::crypto::SignatureSecretKey`] wrapper.
100 #[derive(Clone, Debug, uniffi::Record)]
101 pub struct SignatureSecretKey {
102     pub bytes: Vec<u8>,
103 }
104 
105 impl From<mls_rs::crypto::SignatureSecretKey> for SignatureSecretKey {
from(secret_key: mls_rs::crypto::SignatureSecretKey) -> Self106     fn from(secret_key: mls_rs::crypto::SignatureSecretKey) -> Self {
107         Self {
108             bytes: secret_key.as_bytes().to_vec(),
109         }
110     }
111 }
112 
113 impl From<SignatureSecretKey> for mls_rs::crypto::SignatureSecretKey {
from(secret_key: SignatureSecretKey) -> Self114     fn from(secret_key: SignatureSecretKey) -> Self {
115         Self::new(secret_key.bytes)
116     }
117 }
118 
119 /// A ([`SignaturePublicKey`], [`SignatureSecretKey`]) pair.
120 #[derive(uniffi::Record, Clone, Debug)]
121 pub struct SignatureKeypair {
122     cipher_suite: CipherSuite,
123     public_key: SignaturePublicKey,
124     secret_key: SignatureSecretKey,
125 }
126 
127 /// A [`mls_rs::ExtensionList`] wrapper.
128 #[derive(uniffi::Object, Debug, Clone)]
129 pub struct ExtensionList {
130     _inner: mls_rs::ExtensionList,
131 }
132 
133 impl From<mls_rs::ExtensionList> for ExtensionList {
from(inner: mls_rs::ExtensionList) -> Self134     fn from(inner: mls_rs::ExtensionList) -> Self {
135         Self { _inner: inner }
136     }
137 }
138 
139 /// A [`mls_rs::Extension`] wrapper.
140 #[derive(uniffi::Object, Debug, Clone)]
141 pub struct Extension {
142     _inner: mls_rs::Extension,
143 }
144 
145 impl From<mls_rs::Extension> for Extension {
from(inner: mls_rs::Extension) -> Self146     fn from(inner: mls_rs::Extension) -> Self {
147         Self { _inner: inner }
148     }
149 }
150 
151 /// A [`mls_rs::Group`] and [`mls_rs::group::NewMemberInfo`] wrapper.
152 #[derive(uniffi::Record, Clone)]
153 pub struct JoinInfo {
154     /// The group that was joined.
155     pub group: Arc<Group>,
156     /// Group info extensions found within the Welcome message used to join
157     /// the group.
158     pub group_info_extensions: Arc<ExtensionList>,
159 }
160 
161 #[derive(Copy, Clone, Debug, uniffi::Enum)]
162 pub enum ProtocolVersion {
163     /// MLS version 1.0.
164     Mls10,
165 }
166 
167 impl TryFrom<mls_rs::ProtocolVersion> for ProtocolVersion {
168     type Error = Error;
169 
try_from(version: mls_rs::ProtocolVersion) -> Result<Self, Self::Error>170     fn try_from(version: mls_rs::ProtocolVersion) -> Result<Self, Self::Error> {
171         match version {
172             mls_rs::ProtocolVersion::MLS_10 => Ok(ProtocolVersion::Mls10),
173             _ => Err(MlsError::UnsupportedProtocolVersion(version))?,
174         }
175     }
176 }
177 
178 /// A [`mls_rs::MlsMessage`] wrapper.
179 #[derive(Clone, Debug, uniffi::Object)]
180 pub struct Message {
181     inner: mls_rs::MlsMessage,
182 }
183 
184 impl From<mls_rs::MlsMessage> for Message {
from(inner: mls_rs::MlsMessage) -> Self185     fn from(inner: mls_rs::MlsMessage) -> Self {
186         Self { inner }
187     }
188 }
189 
190 #[derive(Clone, Debug, uniffi::Object)]
191 pub struct Proposal {
192     _inner: mls_rs::group::proposal::Proposal,
193 }
194 
195 impl From<mls_rs::group::proposal::Proposal> for Proposal {
from(inner: mls_rs::group::proposal::Proposal) -> Self196     fn from(inner: mls_rs::group::proposal::Proposal) -> Self {
197         Self { _inner: inner }
198     }
199 }
200 
201 /// Update of a member due to a commit.
202 #[derive(Clone, Debug, uniffi::Record)]
203 pub struct MemberUpdate {
204     pub prior: Arc<SigningIdentity>,
205     pub new: Arc<SigningIdentity>,
206 }
207 
208 /// A set of roster updates due to a commit.
209 #[derive(Clone, Debug, uniffi::Record)]
210 pub struct RosterUpdate {
211     pub added: Vec<Arc<SigningIdentity>>,
212     pub removed: Vec<Arc<SigningIdentity>>,
213     pub updated: Vec<MemberUpdate>,
214 }
215 
216 impl RosterUpdate {
217     // This is an associated function because it felt wrong to hide
218     // the clones in an `impl From<&mls_rs::identity::RosterUpdate>`.
new(roster_update: &mls_rs::identity::RosterUpdate) -> Self219     fn new(roster_update: &mls_rs::identity::RosterUpdate) -> Self {
220         let added = roster_update
221             .added()
222             .iter()
223             .map(|member| Arc::new(member.signing_identity.clone().into()))
224             .collect();
225         let removed = roster_update
226             .removed()
227             .iter()
228             .map(|member| Arc::new(member.signing_identity.clone().into()))
229             .collect();
230         let updated = roster_update
231             .updated()
232             .iter()
233             .map(|update| MemberUpdate {
234                 prior: Arc::new(update.prior.signing_identity.clone().into()),
235                 new: Arc::new(update.new.signing_identity.clone().into()),
236             })
237             .collect();
238         RosterUpdate {
239             added,
240             removed,
241             updated,
242         }
243     }
244 }
245 
246 /// A [`mls_rs::group::ReceivedMessage`] wrapper.
247 #[derive(Clone, Debug, uniffi::Enum)]
248 pub enum ReceivedMessage {
249     /// A decrypted application message.
250     ApplicationMessage {
251         sender: Arc<SigningIdentity>,
252         data: Vec<u8>,
253     },
254 
255     /// A new commit was processed creating a new group state.
256     Commit {
257         committer: Arc<SigningIdentity>,
258         roster_update: RosterUpdate,
259     },
260 
261     // TODO(mgeisler): rename to `Proposal` when
262     // https://github.com/awslabs/mls-rs/issues/98 is fixed.
263     /// A proposal was received.
264     ReceivedProposal {
265         sender: Arc<SigningIdentity>,
266         proposal: Arc<Proposal>,
267     },
268 
269     /// Validated GroupInfo object.
270     GroupInfo,
271     /// Validated welcome message.
272     Welcome,
273     /// Validated key package.
274     KeyPackage,
275 }
276 
277 /// Supported cipher suites.
278 ///
279 /// This is a subset of the cipher suites found in
280 /// [`mls_rs::CipherSuite`].
281 #[derive(Copy, Clone, Debug, uniffi::Enum)]
282 pub enum CipherSuite {
283     // TODO(mgeisler): add more cipher suites.
284     Curve25519Aes128,
285 }
286 
287 impl From<CipherSuite> for mls_rs::CipherSuite {
from(cipher_suite: CipherSuite) -> mls_rs::CipherSuite288     fn from(cipher_suite: CipherSuite) -> mls_rs::CipherSuite {
289         match cipher_suite {
290             CipherSuite::Curve25519Aes128 => mls_rs::CipherSuite::CURVE25519_AES128,
291         }
292     }
293 }
294 
295 impl TryFrom<mls_rs::CipherSuite> for CipherSuite {
296     type Error = Error;
297 
try_from(cipher_suite: mls_rs::CipherSuite) -> Result<Self, Self::Error>298     fn try_from(cipher_suite: mls_rs::CipherSuite) -> Result<Self, Self::Error> {
299         match cipher_suite {
300             mls_rs::CipherSuite::CURVE25519_AES128 => Ok(CipherSuite::Curve25519Aes128),
301             _ => Err(MlsError::UnsupportedCipherSuite(cipher_suite))?,
302         }
303     }
304 }
305 
306 /// Generate a MLS signature keypair.
307 ///
308 /// This will use the default mls-lite crypto provider.
309 ///
310 /// See [`mls_rs::CipherSuiteProvider::signature_key_generate`]
311 /// for details.
312 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
313 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
314 #[uniffi::export]
generate_signature_keypair( cipher_suite: CipherSuite, ) -> Result<SignatureKeypair, Error>315 pub async fn generate_signature_keypair(
316     cipher_suite: CipherSuite,
317 ) -> Result<SignatureKeypair, Error> {
318     let crypto_provider = mls_rs_crypto_openssl::OpensslCryptoProvider::default();
319     let cipher_suite_provider = crypto_provider
320         .cipher_suite_provider(cipher_suite.into())
321         .ok_or(MlsError::UnsupportedCipherSuite(cipher_suite.into()))?;
322 
323     let (secret_key, public_key) = cipher_suite_provider
324         .signature_key_generate()
325         .await
326         .map_err(|err| MlsError::CryptoProviderError(err.into_any_error()))?;
327 
328     Ok(SignatureKeypair {
329         cipher_suite,
330         public_key: public_key.into(),
331         secret_key: secret_key.into(),
332     })
333 }
334 
335 /// An MLS client used to create key packages and manage groups.
336 ///
337 /// See [`mls_rs::Client`] for details.
338 #[derive(Clone, Debug, uniffi::Object)]
339 pub struct Client {
340     inner: mls_rs::client::Client<UniFFIConfig>,
341 }
342 
343 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
344 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
345 #[uniffi::export]
346 impl Client {
347     /// Create a new client.
348     ///
349     /// The user is identified by `id`, which will be used to create a
350     /// basic credential together with the signature keypair.
351     ///
352     /// See [`mls_rs::Client::builder`] for details.
353     #[uniffi::constructor]
new( id: Vec<u8>, signature_keypair: SignatureKeypair, client_config: ClientConfig, ) -> Self354     pub fn new(
355         id: Vec<u8>,
356         signature_keypair: SignatureKeypair,
357         client_config: ClientConfig,
358     ) -> Self {
359         let cipher_suite = signature_keypair.cipher_suite;
360         let public_key = signature_keypair.public_key;
361         let secret_key = signature_keypair.secret_key;
362         let crypto_provider = OpensslCryptoProvider::new();
363         let basic_credential = BasicCredential::new(id);
364         let signing_identity =
365             identity::SigningIdentity::new(basic_credential.into_credential(), public_key.into());
366         let commit_options = mls_rules::CommitOptions::default()
367             .with_ratchet_tree_extension(client_config.use_ratchet_tree_extension)
368             .with_single_welcome_message(true);
369         let mls_rules = mls_rules::DefaultMlsRules::new().with_commit_options(commit_options);
370         let client = mls_rs::Client::builder()
371             .crypto_provider(crypto_provider)
372             .identity_provider(basic::BasicIdentityProvider::new())
373             .signing_identity(signing_identity, secret_key.into(), cipher_suite.into())
374             .group_state_storage(client_config.group_state_storage.into())
375             .mls_rules(mls_rules)
376             .build();
377 
378         Client { inner: client }
379     }
380 
381     /// Generate a new key package for this client.
382     ///
383     /// The key package is represented in is MLS message form. It is
384     /// needed when joining a group and can be published to a server
385     /// so other clients can look it up.
386     ///
387     /// See [`mls_rs::Client::generate_key_package_message`] for
388     /// details.
generate_key_package_message(&self) -> Result<Message, Error>389     pub async fn generate_key_package_message(&self) -> Result<Message, Error> {
390         let message = self.inner.generate_key_package_message().await?;
391         Ok(message.into())
392     }
393 
signing_identity(&self) -> Result<Arc<SigningIdentity>, Error>394     pub fn signing_identity(&self) -> Result<Arc<SigningIdentity>, Error> {
395         let (signing_identity, _) = self.inner.signing_identity()?;
396         Ok(Arc::new(signing_identity.clone().into()))
397     }
398 
399     /// Create and immediately join a new group.
400     ///
401     /// If a group ID is not given, the underlying library will create
402     /// a unique ID for you.
403     ///
404     /// See [`mls_rs::Client::create_group`] and
405     /// [`mls_rs::Client::create_group_with_id`] for details.
create_group(&self, group_id: Option<Vec<u8>>) -> Result<Group, Error>406     pub async fn create_group(&self, group_id: Option<Vec<u8>>) -> Result<Group, Error> {
407         let extensions = mls_rs::ExtensionList::new();
408         let inner = match group_id {
409             Some(group_id) => {
410                 self.inner
411                     .create_group_with_id(group_id, extensions)
412                     .await?
413             }
414             None => self.inner.create_group(extensions).await?,
415         };
416         Ok(Group {
417             inner: Arc::new(Mutex::new(inner)),
418         })
419     }
420 
421     /// Join an existing group.
422     ///
423     /// You must supply `ratchet_tree` if the client that created
424     /// `welcome_message` did not set `use_ratchet_tree_extension`.
425     ///
426     /// See [`mls_rs::Client::join_group`] for details.
join_group( &self, ratchet_tree: Option<RatchetTree>, welcome_message: &Message, ) -> Result<JoinInfo, Error>427     pub async fn join_group(
428         &self,
429         ratchet_tree: Option<RatchetTree>,
430         welcome_message: &Message,
431     ) -> Result<JoinInfo, Error> {
432         let ratchet_tree = ratchet_tree.map(TryInto::try_into).transpose()?;
433         let (group, new_member_info) = self
434             .inner
435             .join_group(ratchet_tree, &welcome_message.inner)
436             .await?;
437 
438         let group = Arc::new(Group {
439             inner: Arc::new(Mutex::new(group)),
440         });
441         let group_info_extensions = Arc::new(new_member_info.group_info_extensions.into());
442         Ok(JoinInfo {
443             group,
444             group_info_extensions,
445         })
446     }
447 
448     /// Load an existing group.
449     ///
450     /// See [`mls_rs::Client::load_group`] for details.
load_group(&self, group_id: Vec<u8>) -> Result<Group, Error>451     pub async fn load_group(&self, group_id: Vec<u8>) -> Result<Group, Error> {
452         self.inner
453             .load_group(&group_id)
454             .await
455             .map(|g| Group {
456                 inner: Arc::new(Mutex::new(g)),
457             })
458             .map_err(Into::into)
459     }
460 }
461 
462 #[derive(Clone, Debug, PartialEq, uniffi::Record)]
463 pub struct RatchetTree {
464     pub bytes: Vec<u8>,
465 }
466 
467 impl TryFrom<mls_rs::group::ExportedTree<'_>> for RatchetTree {
468     type Error = Error;
469 
try_from(exported_tree: mls_rs::group::ExportedTree<'_>) -> Result<Self, Error>470     fn try_from(exported_tree: mls_rs::group::ExportedTree<'_>) -> Result<Self, Error> {
471         let bytes = exported_tree.to_bytes()?;
472         Ok(Self { bytes })
473     }
474 }
475 
476 impl TryFrom<RatchetTree> for group::ExportedTree<'static> {
477     type Error = Error;
478 
try_from(ratchet_tree: RatchetTree) -> Result<Self, Error>479     fn try_from(ratchet_tree: RatchetTree) -> Result<Self, Error> {
480         group::ExportedTree::from_bytes(&ratchet_tree.bytes).map_err(Into::into)
481     }
482 }
483 
484 #[derive(Clone, Debug, uniffi::Record)]
485 pub struct CommitOutput {
486     /// Commit message to send to other group members.
487     pub commit_message: Arc<Message>,
488 
489     /// Welcome message to send to new group members. This will be
490     /// `None` if the commit did not add new members.
491     pub welcome_message: Option<Arc<Message>>,
492 
493     /// Ratchet tree that can be sent out of band if the ratchet tree
494     /// extension is not used.
495     pub ratchet_tree: Option<RatchetTree>,
496 
497     /// A group info that can be provided to new members in order to
498     /// enable external commit functionality.
499     pub group_info: Option<Arc<Message>>,
500     // TODO(mgeisler): decide if we should expose unused_proposals()
501     // as well.
502 }
503 
504 impl TryFrom<mls_rs::group::CommitOutput> for CommitOutput {
505     type Error = Error;
506 
try_from(commit_output: mls_rs::group::CommitOutput) -> Result<Self, Error>507     fn try_from(commit_output: mls_rs::group::CommitOutput) -> Result<Self, Error> {
508         let commit_message = Arc::new(commit_output.commit_message.into());
509         let welcome_message = commit_output
510             .welcome_messages
511             .into_iter()
512             .next()
513             .map(|welcome_message| Arc::new(welcome_message.into()));
514         let ratchet_tree = commit_output
515             .ratchet_tree
516             .map(TryInto::try_into)
517             .transpose()?;
518         let group_info = commit_output
519             .external_commit_group_info
520             .map(|group_info| Arc::new(group_info.into()));
521 
522         Ok(Self {
523             commit_message,
524             welcome_message,
525             ratchet_tree,
526             group_info,
527         })
528     }
529 }
530 
531 #[derive(Clone, Debug, PartialEq, Eq, uniffi::Object)]
532 #[uniffi::export(Eq)]
533 pub struct SigningIdentity {
534     inner: identity::SigningIdentity,
535 }
536 
537 impl From<identity::SigningIdentity> for SigningIdentity {
from(inner: identity::SigningIdentity) -> Self538     fn from(inner: identity::SigningIdentity) -> Self {
539         Self { inner }
540     }
541 }
542 
543 /// An MLS end-to-end encrypted group.
544 ///
545 /// The group is used to send and process incoming messages and to
546 /// add/remove users.
547 ///
548 /// See [`mls_rs::Group`] for details.
549 #[derive(Clone, uniffi::Object)]
550 pub struct Group {
551     inner: Arc<Mutex<mls_rs::Group<UniFFIConfig>>>,
552 }
553 
554 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
555 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
556 impl Group {
557     #[cfg(not(mls_build_async))]
inner(&self) -> std::sync::MutexGuard<'_, mls_rs::Group<UniFFIConfig>>558     fn inner(&self) -> std::sync::MutexGuard<'_, mls_rs::Group<UniFFIConfig>> {
559         self.inner.lock().unwrap()
560     }
561 
562     #[cfg(mls_build_async)]
inner(&self) -> tokio::sync::MutexGuard<'_, mls_rs::Group<UniFFIConfig>>563     async fn inner(&self) -> tokio::sync::MutexGuard<'_, mls_rs::Group<UniFFIConfig>> {
564         self.inner.lock().await
565     }
566 }
567 
568 /// Find the identity for the member with a given index.
index_to_identity( group: &mls_rs::Group<UniFFIConfig>, index: u32, ) -> Result<identity::SigningIdentity, Error>569 fn index_to_identity(
570     group: &mls_rs::Group<UniFFIConfig>,
571     index: u32,
572 ) -> Result<identity::SigningIdentity, Error> {
573     let member = group
574         .member_at_index(index)
575         .ok_or(MlsError::InvalidNodeIndex(index))?;
576     Ok(member.signing_identity)
577 }
578 
579 /// Extract the basic credential identifier from a  from a key package.
580 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
581 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
signing_identity_to_identifier( signing_identity: &identity::SigningIdentity, ) -> Result<Vec<u8>, Error>582 async fn signing_identity_to_identifier(
583     signing_identity: &identity::SigningIdentity,
584 ) -> Result<Vec<u8>, Error> {
585     let identifier = basic::BasicIdentityProvider::new()
586         .identity(signing_identity, &mls_rs::ExtensionList::new())
587         .await
588         .map_err(|err| err.into_any_error())?;
589     Ok(identifier)
590 }
591 
592 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
593 #[cfg_attr(mls_build_async, maybe_async::must_be_async)]
594 #[uniffi::export]
595 impl Group {
596     /// Write the current state of the group to storage defined by
597     /// [`ClientConfig::group_state_storage`]
write_to_storage(&self) -> Result<(), Error>598     pub async fn write_to_storage(&self) -> Result<(), Error> {
599         let mut group = self.inner().await;
600         group.write_to_storage().await.map_err(Into::into)
601     }
602 
603     /// Export the current epoch's ratchet tree in serialized format.
604     ///
605     /// This function is used to provide the current group tree to new
606     /// members when `use_ratchet_tree_extension` is set to false in
607     /// `ClientConfig`.
export_tree(&self) -> Result<RatchetTree, Error>608     pub async fn export_tree(&self) -> Result<RatchetTree, Error> {
609         let group = self.inner().await;
610         group.export_tree().try_into()
611     }
612 
613     /// Perform a commit of received proposals (or an empty commit).
614     ///
615     /// TODO: ensure `path_required` is always set in
616     /// [`MlsRules::commit_options`](`mls_rs::MlsRules::commit_options`).
617     ///
618     /// Returns the resulting commit message. See
619     /// [`mls_rs::Group::commit`] for details.
commit(&self) -> Result<CommitOutput, Error>620     pub async fn commit(&self) -> Result<CommitOutput, Error> {
621         let mut group = self.inner().await;
622         let commit_output = group.commit(Vec::new()).await?;
623         commit_output.try_into()
624     }
625 
626     /// Commit the addition of one or more members.
627     ///
628     /// The members are representated by key packages. The result is
629     /// the welcome messages to send to the new members.
630     ///
631     /// See [`mls_rs::group::CommitBuilder::add_member`] for details.
add_members( &self, key_packages: Vec<Arc<Message>>, ) -> Result<CommitOutput, Error>632     pub async fn add_members(
633         &self,
634         key_packages: Vec<Arc<Message>>,
635     ) -> Result<CommitOutput, Error> {
636         let mut group = self.inner().await;
637         let mut commit_builder = group.commit_builder();
638         for key_package in key_packages {
639             commit_builder = commit_builder.add_member(arc_unwrap_or_clone(key_package).inner)?;
640         }
641         let commit_output = commit_builder.build().await?;
642         commit_output.try_into()
643     }
644 
645     /// Propose to add one or more members to this group.
646     ///
647     /// The members are representated by key packages. The result is
648     /// the proposal messages to send to the group.
649     ///
650     /// See [`mls_rs::Group::propose_add`] for details.
propose_add_members( &self, key_packages: Vec<Arc<Message>>, ) -> Result<Vec<Arc<Message>>, Error>651     pub async fn propose_add_members(
652         &self,
653         key_packages: Vec<Arc<Message>>,
654     ) -> Result<Vec<Arc<Message>>, Error> {
655         let mut group = self.inner().await;
656 
657         let mut messages = Vec::with_capacity(key_packages.len());
658         for key_package in key_packages {
659             let key_package = arc_unwrap_or_clone(key_package);
660             let message = group.propose_add(key_package.inner, Vec::new()).await?;
661             messages.push(Arc::new(message.into()));
662         }
663 
664         Ok(messages)
665     }
666 
667     /// Propose and commit the removal of one or more members.
668     ///
669     /// The members are representated by their signing identities.
670     ///
671     /// See [`mls_rs::group::CommitBuilder::remove_member`] for details.
remove_members( &self, signing_identities: &[Arc<SigningIdentity>], ) -> Result<CommitOutput, Error>672     pub async fn remove_members(
673         &self,
674         signing_identities: &[Arc<SigningIdentity>],
675     ) -> Result<CommitOutput, Error> {
676         let mut group = self.inner().await;
677 
678         // Find member indices
679         let mut member_indixes = Vec::with_capacity(signing_identities.len());
680         for signing_identity in signing_identities {
681             let identifier = signing_identity_to_identifier(&signing_identity.inner).await?;
682             let member = group.member_with_identity(&identifier).await?;
683             member_indixes.push(member.index);
684         }
685 
686         let mut commit_builder = group.commit_builder();
687         for index in member_indixes {
688             commit_builder = commit_builder.remove_member(index)?;
689         }
690         let commit_output = commit_builder.build().await?;
691         commit_output.try_into()
692     }
693 
694     /// Propose to remove one or more members from this group.
695     ///
696     /// The members are representated by their signing identities. The
697     /// result is the proposal messages to send to the group.
698     ///
699     /// See [`mls_rs::group::Group::propose_remove`] for details.
propose_remove_members( &self, signing_identities: &[Arc<SigningIdentity>], ) -> Result<Vec<Arc<Message>>, Error>700     pub async fn propose_remove_members(
701         &self,
702         signing_identities: &[Arc<SigningIdentity>],
703     ) -> Result<Vec<Arc<Message>>, Error> {
704         let mut group = self.inner().await;
705 
706         let mut messages = Vec::with_capacity(signing_identities.len());
707         for signing_identity in signing_identities {
708             let identifier = signing_identity_to_identifier(&signing_identity.inner).await?;
709             let member = group.member_with_identity(&identifier).await?;
710             let message = group.propose_remove(member.index, Vec::new()).await?;
711             messages.push(Arc::new(message.into()));
712         }
713 
714         Ok(messages)
715     }
716 
717     /// Encrypt an application message using the current group state.
encrypt_application_message(&self, message: &[u8]) -> Result<Message, Error>718     pub async fn encrypt_application_message(&self, message: &[u8]) -> Result<Message, Error> {
719         let mut group = self.inner().await;
720         let mls_message = group
721             .encrypt_application_message(message, Vec::new())
722             .await?;
723         Ok(mls_message.into())
724     }
725 
726     /// Process an inbound message for this group.
process_incoming_message( &self, message: Arc<Message>, ) -> Result<ReceivedMessage, Error>727     pub async fn process_incoming_message(
728         &self,
729         message: Arc<Message>,
730     ) -> Result<ReceivedMessage, Error> {
731         let message = arc_unwrap_or_clone(message);
732         let mut group = self.inner().await;
733         match group.process_incoming_message(message.inner).await? {
734             group::ReceivedMessage::ApplicationMessage(application_message) => {
735                 let sender =
736                     Arc::new(index_to_identity(&group, application_message.sender_index)?.into());
737                 let data = application_message.data().to_vec();
738                 Ok(ReceivedMessage::ApplicationMessage { sender, data })
739             }
740             group::ReceivedMessage::Commit(commit_message) => {
741                 let committer =
742                     Arc::new(index_to_identity(&group, commit_message.committer)?.into());
743                 let roster_update = RosterUpdate::new(commit_message.state_update.roster_update());
744                 Ok(ReceivedMessage::Commit {
745                     committer,
746                     roster_update,
747                 })
748             }
749             group::ReceivedMessage::Proposal(proposal_message) => {
750                 let sender = match proposal_message.sender {
751                     mls_rs::group::ProposalSender::Member(index) => {
752                         Arc::new(index_to_identity(&group, index)?.into())
753                     }
754                     _ => todo!("External and NewMember proposal senders are not supported"),
755                 };
756                 let proposal = Arc::new(proposal_message.proposal.into());
757                 Ok(ReceivedMessage::ReceivedProposal { sender, proposal })
758             }
759             // TODO: group::ReceivedMessage::GroupInfo does not have any
760             // public methods (unless the "ffi" Cargo feature is set).
761             // So perhaps we don't need it?
762             group::ReceivedMessage::GroupInfo(_) => Ok(ReceivedMessage::GroupInfo),
763             group::ReceivedMessage::Welcome => Ok(ReceivedMessage::Welcome),
764             group::ReceivedMessage::KeyPackage(_) => Ok(ReceivedMessage::KeyPackage),
765         }
766     }
767 }
768 
769 #[cfg(test)]
770 mod tests {
771     #[cfg(not(mls_build_async))]
772     use super::*;
773     #[cfg(not(mls_build_async))]
774     use crate::config::group_state::{EpochRecord, GroupStateStorage};
775     #[cfg(not(mls_build_async))]
776     use std::collections::HashMap;
777 
778     #[test]
779     #[cfg(not(mls_build_async))]
test_simple_scenario() -> Result<(), Error>780     fn test_simple_scenario() -> Result<(), Error> {
781         #[derive(Debug, Default)]
782         struct GroupStateData {
783             state: Vec<u8>,
784             epoch_data: Vec<EpochRecord>,
785         }
786 
787         #[derive(Debug)]
788         struct CustomGroupStateStorage {
789             groups: Mutex<HashMap<Vec<u8>, GroupStateData>>,
790         }
791 
792         impl CustomGroupStateStorage {
793             fn new() -> Self {
794                 Self {
795                     groups: Mutex::new(HashMap::new()),
796                 }
797             }
798 
799             fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<Vec<u8>, GroupStateData>> {
800                 self.groups.lock().unwrap()
801             }
802         }
803 
804         impl GroupStateStorage for CustomGroupStateStorage {
805             fn state(&self, group_id: Vec<u8>) -> Result<Option<Vec<u8>>, Error> {
806                 let groups = self.lock();
807                 Ok(groups.get(&group_id).map(|group| group.state.clone()))
808             }
809 
810             fn epoch(&self, group_id: Vec<u8>, epoch_id: u64) -> Result<Option<Vec<u8>>, Error> {
811                 let groups = self.lock();
812                 match groups.get(&group_id) {
813                     Some(group) => {
814                         let epoch_record =
815                             group.epoch_data.iter().find(|record| record.id == epoch_id);
816                         let data = epoch_record.map(|record| record.data.clone());
817                         Ok(data)
818                     }
819                     None => Ok(None),
820                 }
821             }
822 
823             fn write(
824                 &self,
825                 group_id: Vec<u8>,
826                 group_state: Vec<u8>,
827                 epoch_inserts: Vec<EpochRecord>,
828                 epoch_updates: Vec<EpochRecord>,
829             ) -> Result<(), Error> {
830                 let mut groups = self.lock();
831 
832                 let group = groups.entry(group_id).or_default();
833                 group.state = group_state;
834                 for insert in epoch_inserts {
835                     group.epoch_data.push(insert);
836                 }
837 
838                 for update in epoch_updates {
839                     for epoch in group.epoch_data.iter_mut() {
840                         if epoch.id == update.id {
841                             epoch.data = update.data;
842                             break;
843                         }
844                     }
845                 }
846 
847                 Ok(())
848             }
849 
850             fn max_epoch_id(&self, group_id: Vec<u8>) -> Result<Option<u64>, Error> {
851                 let groups = self.lock();
852                 Ok(groups
853                     .get(&group_id)
854                     .and_then(|GroupStateData { epoch_data, .. }| epoch_data.last())
855                     .map(|last| last.id))
856             }
857         }
858 
859         let alice_config = ClientConfig {
860             group_state_storage: Arc::new(CustomGroupStateStorage::new()),
861             ..Default::default()
862         };
863         let alice_keypair = generate_signature_keypair(CipherSuite::Curve25519Aes128)?;
864         let alice = Client::new(b"alice".to_vec(), alice_keypair, alice_config);
865 
866         let bob_config = ClientConfig {
867             group_state_storage: Arc::new(CustomGroupStateStorage::new()),
868             ..Default::default()
869         };
870         let bob_keypair = generate_signature_keypair(CipherSuite::Curve25519Aes128)?;
871         let bob = Client::new(b"bob".to_vec(), bob_keypair, bob_config);
872 
873         let alice_group = alice.create_group(None)?;
874         let bob_key_package = bob.generate_key_package_message()?;
875         let commit = alice_group.add_members(vec![Arc::new(bob_key_package)])?;
876         alice_group.process_incoming_message(commit.commit_message)?;
877 
878         let bob_group = bob
879             .join_group(None, &commit.welcome_message.unwrap())?
880             .group;
881         let message = alice_group.encrypt_application_message(b"hello, bob")?;
882         let received_message = bob_group.process_incoming_message(Arc::new(message))?;
883 
884         alice_group.write_to_storage()?;
885 
886         let ReceivedMessage::ApplicationMessage { sender: _, data } = received_message else {
887             panic!("Wrong message type: {received_message:?}");
888         };
889         assert_eq!(data, b"hello, bob");
890 
891         Ok(())
892     }
893 
894     #[test]
895     #[cfg(not(mls_build_async))]
test_ratchet_tree_not_included() -> Result<(), Error>896     fn test_ratchet_tree_not_included() -> Result<(), Error> {
897         let alice_config = ClientConfig {
898             use_ratchet_tree_extension: true,
899             ..ClientConfig::default()
900         };
901 
902         let alice_keypair = generate_signature_keypair(CipherSuite::Curve25519Aes128)?;
903         let alice = Client::new(b"alice".to_vec(), alice_keypair, alice_config);
904         let group = alice.create_group(None)?;
905 
906         assert_eq!(group.commit()?.ratchet_tree, None);
907         Ok(())
908     }
909 
910     #[test]
911     #[cfg(not(mls_build_async))]
test_ratchet_tree_included() -> Result<(), Error>912     fn test_ratchet_tree_included() -> Result<(), Error> {
913         let alice_config = ClientConfig {
914             use_ratchet_tree_extension: false,
915             ..ClientConfig::default()
916         };
917 
918         let alice_keypair = generate_signature_keypair(CipherSuite::Curve25519Aes128)?;
919         let alice = Client::new(b"alice".to_vec(), alice_keypair, alice_config);
920         let group = alice.create_group(None)?;
921 
922         let ratchet_tree: group::ExportedTree =
923             group.commit()?.ratchet_tree.unwrap().try_into().unwrap();
924         group.inner().apply_pending_commit()?;
925 
926         assert_eq!(ratchet_tree, group.inner().export_tree());
927         Ok(())
928     }
929 }
930