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