1use super::{
2 group_membership::GroupMembership,
3 group_permissions::{MembershipPolicies, MetadataPolicies, PermissionsPolicies},
4};
5use crate::groups::mls_ext::WelcomePointersExtension;
6use openmls::key_packages::KeyPackage;
7use openmls::prelude::{
8 MlsMessageOut,
9 tls_codec::{Error as TlsCodecError, Serialize},
10};
11use prost::{Message, bytes::Bytes};
12use std::collections::{HashMap, HashSet};
13use thiserror::Error;
14use xmtp_common::types::Address;
15use xmtp_configuration::{
16 GROUP_KEY_ROTATION_INTERVAL_NS, WELCOME_POINTEE_ENCRYPTION_AEAD_TYPES_EXTENSION_ID,
17};
18use xmtp_id::key_package::{
19 KeyPackageVerificationError, VerifiedKeyPackageV2, WrapperAlgorithm, WrapperEncryptionExtension,
20};
21use xmtp_mls_common::{
22 app_data::{
23 component_id::ComponentId,
24 fields::{FieldWrite, WriteOperation},
25 },
26 group_mutable_metadata::MetadataField,
27};
28use xmtp_proto::{
29 ConversionError,
30 xmtp::mls::database::{
31 AccountAddresses, AddressesOrInstallationIds as AddressesOrInstallationIdsProtoWrapper,
32 CommitPendingProposalsData, InstallationIds, PostCommitAction as PostCommitActionProto,
33 ProposeGroupContextExtensionData, ProposeMemberUpdateData, ReaddInstallationsData,
34 SendMessageData, UpdateAdminListsData, UpdateGroupMembershipData, UpdateMetadataData,
35 UpdatePermissionData,
36 addresses_or_installation_ids::AddressesOrInstallationIds as AddressesOrInstallationIdsProto,
37 commit_pending_proposals_data,
38 post_commit_action::{
39 Installation as InstallationProto, Kind as PostCommitActionKind,
40 SendWelcomes as SendWelcomesProto,
41 },
42 propose_group_context_extension_data, propose_member_update_data,
43 readd_installations_data::{
44 V1 as ReaddInstallationsV1, Version as ReaddInstallationsVersion,
45 },
46 send_message_data::{V1 as SendMessageV1, Version as SendMessageVersion},
47 update_admin_lists_data::{V1 as UpdateAdminListsV1, Version as UpdateAdminListsVersion},
48 update_group_membership_data::{
49 V1 as UpdateGroupMembershipV1, Version as UpdateGroupMembershipVersion,
50 },
51 update_metadata_data::{V1 as UpdateMetadataV1, Version as UpdateMetadataVersion},
52 update_permission_data::{
53 self, V1 as UpdatePermissionV1, Version as UpdatePermissionVersion,
54 },
55 },
56 xmtp::mls::message_contents::ComponentType,
57};
58
59mod queue;
60pub use queue::*;
61
62#[derive(Debug, Error)]
63pub enum IntentError {
64 #[error("conversion error: {0}")]
65 Conversion(#[from] xmtp_proto::ConversionError),
66 #[error("key package verification: {0}")]
67 KeyPackageVerification(#[from] KeyPackageVerificationError),
68 #[error("TLS Codec error: {0}")]
69 TlsError(#[from] TlsCodecError),
70 #[error(transparent)]
71 Storage(#[from] xmtp_db::StorageError),
72 #[error("missing update permission")]
73 MissingUpdatePermissionVersion,
74 #[error("missing payload")]
75 MissingPayload,
76 #[error("missing update admin version")]
77 MissingUpdateAdminVersion,
78 #[error("missing post commit action")]
79 MissingPostCommit,
80 #[error("unsupported permission version")]
81 UnsupportedPermissionVersion,
82 #[error("unknown permission update type")]
83 UnknownPermissionUpdateType,
84 #[error("unknown value for PermissionPolicyOption")]
85 UnknownPermissionPolicyOption,
86 #[error("unknown value for AdminListActionType")]
87 UnknownAdminListAction,
88 #[error("intent payload error: {0}")]
89 Generic(String),
90}
91
92impl From<prost::DecodeError> for IntentError {
93 fn from(error: prost::DecodeError) -> Self {
94 IntentError::Conversion(xmtp_proto::ConversionError::Decode(error))
95 }
96}
97
98#[derive(Debug, Clone)]
99pub struct SendMessageIntentData {
100 pub message: Vec<u8>,
101}
102
103impl SendMessageIntentData {
104 pub fn new(message: Vec<u8>) -> Self {
105 Self { message }
106 }
107
108 pub(crate) fn to_bytes(&self) -> Vec<u8> {
109 SendMessageData {
110 version: Some(SendMessageVersion::V1(SendMessageV1 {
111 payload_bytes: self.message.clone(),
112 })),
113 }
114 .encode_to_vec()
115 }
116
117 pub(crate) fn from_bytes(data: &[u8]) -> Result<Self, IntentError> {
118 let msg = SendMessageData::decode(data)?;
119 let payload_bytes = match msg.version {
120 Some(SendMessageVersion::V1(v1)) => v1.payload_bytes,
121 None => return Err(IntentError::MissingPayload),
122 };
123
124 Ok(Self::new(payload_bytes))
125 }
126}
127
128impl From<SendMessageIntentData> for Vec<u8> {
129 fn from(intent: SendMessageIntentData) -> Self {
130 intent.to_bytes()
131 }
132}
133
134#[derive(Debug, PartialEq, Eq, Clone)]
135pub enum AddressesOrInstallationIds {
136 AccountAddresses(Vec<String>),
137 InstallationIds(Vec<Vec<u8>>),
138}
139
140impl From<AddressesOrInstallationIds> for AddressesOrInstallationIdsProtoWrapper {
141 fn from(address_or_id: AddressesOrInstallationIds) -> Self {
142 match address_or_id {
143 AddressesOrInstallationIds::AccountAddresses(account_addresses) => {
144 AddressesOrInstallationIdsProtoWrapper {
145 addresses_or_installation_ids: Some(
146 AddressesOrInstallationIdsProto::AccountAddresses(AccountAddresses {
147 account_addresses,
148 }),
149 ),
150 }
151 }
152 AddressesOrInstallationIds::InstallationIds(installation_ids) => {
153 AddressesOrInstallationIdsProtoWrapper {
154 addresses_or_installation_ids: Some(
155 AddressesOrInstallationIdsProto::InstallationIds(InstallationIds {
156 installation_ids,
157 }),
158 ),
159 }
160 }
161 }
162 }
163}
164
165impl TryFrom<AddressesOrInstallationIdsProtoWrapper> for AddressesOrInstallationIds {
166 type Error = IntentError;
167
168 fn try_from(wrapper: AddressesOrInstallationIdsProtoWrapper) -> Result<Self, Self::Error> {
169 match wrapper.addresses_or_installation_ids {
170 Some(AddressesOrInstallationIdsProto::AccountAddresses(addrs)) => Ok(
171 AddressesOrInstallationIds::AccountAddresses(addrs.account_addresses),
172 ),
173 Some(AddressesOrInstallationIdsProto::InstallationIds(ids)) => Ok(
174 AddressesOrInstallationIds::InstallationIds(ids.installation_ids),
175 ),
176 _ => Err(IntentError::MissingPayload),
177 }
178 }
179}
180
181impl From<Vec<Address>> for AddressesOrInstallationIds {
182 fn from(addrs: Vec<Address>) -> Self {
183 AddressesOrInstallationIds::AccountAddresses(addrs)
184 }
185}
186
187impl From<Vec<Vec<u8>>> for AddressesOrInstallationIds {
188 fn from(installation_ids: Vec<Vec<u8>>) -> Self {
189 AddressesOrInstallationIds::InstallationIds(installation_ids)
190 }
191}
192
193#[derive(Debug, Clone)]
194pub struct UpdateMetadataIntentData {
195 pub field_name: String,
196 pub field_value: String,
197 pub expected_field_value: Option<String>,
201}
202
203impl UpdateMetadataIntentData {
204 pub fn new(field_name: String, field_value: String) -> Self {
205 Self {
206 field_name,
207 field_value,
208 expected_field_value: None,
209 }
210 }
211
212 pub fn new_guarded(
216 field_name: String,
217 field_value: String,
218 expected_field_value: String,
219 ) -> Self {
220 Self {
221 field_name,
222 field_value,
223 expected_field_value: Some(expected_field_value),
224 }
225 }
226
227 pub fn new_update_group_name(group_name: String) -> Self {
228 Self::new(MetadataField::GroupName.to_string(), group_name)
229 }
230
231 pub fn new_update_group_image_url_square(group_image_url_square: String) -> Self {
232 Self::new(
233 MetadataField::GroupImageUrlSquare.to_string(),
234 group_image_url_square,
235 )
236 }
237
238 pub fn new_update_group_description(group_description: String) -> Self {
239 Self::new(MetadataField::Description.to_string(), group_description)
240 }
241
242 pub fn new_update_app_data(app_data: String, expected_app_data: Option<String>) -> Self {
243 match expected_app_data {
244 Some(expected) => {
245 Self::new_guarded(MetadataField::AppData.to_string(), app_data, expected)
246 }
247 None => Self::new(MetadataField::AppData.to_string(), app_data),
248 }
249 }
250
251 pub fn new_update_conversation_message_disappear_from_ns(from_ns: i64) -> Self {
252 Self::new(
253 MetadataField::MessageDisappearFromNS.to_string(),
254 from_ns.to_string(),
255 )
256 }
257 pub fn new_update_conversation_message_disappear_in_ns(in_ns: i64) -> Self {
258 Self::new(
259 MetadataField::MessageDisappearInNS.to_string(),
260 in_ns.to_string(),
261 )
262 }
263
264 pub fn new_update_group_min_version_to_match_self(min_version: String) -> Self {
265 Self::new(
266 MetadataField::MinimumSupportedProtocolVersion.to_string(),
267 min_version,
268 )
269 }
270
271 pub fn new_update_commit_log_signer(commit_log_signer: xmtp_cryptography::Secret) -> Self {
272 Self::new(
273 MetadataField::CommitLogSigner.to_string(),
274 hex::encode(commit_log_signer.as_slice()),
275 )
276 }
277}
278
279impl From<UpdateMetadataIntentData> for Vec<u8> {
280 fn from(intent: UpdateMetadataIntentData) -> Self {
281 let mut buf = Vec::new();
282
283 UpdateMetadataData {
284 version: Some(UpdateMetadataVersion::V1(UpdateMetadataV1 {
285 field_name: intent.field_name.to_string(),
286 field_value: intent.field_value.clone(),
287 expected_field_value: intent.expected_field_value.clone(),
288 })),
289 }
290 .encode(&mut buf)
291 .expect("encode error");
292
293 buf
294 }
295}
296
297impl TryFrom<Vec<u8>> for UpdateMetadataIntentData {
298 type Error = IntentError;
299
300 fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
301 let msg = UpdateMetadataData::decode(Bytes::from(data))?;
302
303 let field_name = match msg.version {
304 Some(UpdateMetadataVersion::V1(ref v1)) => v1.field_name.clone(),
305 None => return Err(IntentError::MissingPayload),
306 };
307 let field_value = match msg.version {
308 Some(UpdateMetadataVersion::V1(ref v1)) => v1.field_value.clone(),
309 None => return Err(IntentError::MissingPayload),
310 };
311 let expected_field_value = match msg.version {
314 Some(UpdateMetadataVersion::V1(ref v1)) => v1.expected_field_value.clone(),
315 None => return Err(IntentError::MissingPayload),
316 };
317
318 Ok(match expected_field_value {
319 Some(expected) => Self::new_guarded(field_name, field_value, expected),
320 None => Self::new(field_name, field_value),
321 })
322 }
323}
324
325#[derive(Debug, Default, Clone)]
326pub struct UpdateGroupMembershipResult {
327 pub added_members: HashMap<String, u64>,
328 pub removed_members: Vec<String>,
329 pub failed_installations: Vec<Vec<u8>>,
330}
331
332impl UpdateGroupMembershipResult {
333 pub fn new(
334 added_members: HashMap<String, u64>,
335 removed_members: Vec<String>,
336 failed_installations: Vec<Vec<u8>>,
337 ) -> Self {
338 Self {
339 added_members,
340 removed_members,
341 failed_installations,
342 }
343 }
344}
345
346impl From<UpdateGroupMembershipIntentData> for UpdateGroupMembershipResult {
347 fn from(value: UpdateGroupMembershipIntentData) -> Self {
348 UpdateGroupMembershipResult::new(
349 value.membership_updates,
350 value.removed_members,
351 value.failed_installations,
352 )
353 }
354}
355
356#[derive(Debug, Clone)]
357pub(crate) struct UpdateGroupMembershipIntentData {
358 pub membership_updates: HashMap<String, u64>,
359 pub removed_members: Vec<String>,
360 pub failed_installations: Vec<Vec<u8>>,
361}
362
363impl UpdateGroupMembershipIntentData {
364 pub fn new(
365 membership_updates: HashMap<String, u64>,
366 removed_members: Vec<String>,
367 failed_installations: Vec<Vec<u8>>,
368 ) -> Self {
369 Self {
370 membership_updates,
371 removed_members,
372 failed_installations,
373 }
374 }
375
376 pub fn is_empty(&self) -> bool {
377 self.membership_updates.is_empty()
378 && self.removed_members.is_empty()
379 && self.failed_installations.is_empty()
380 }
381
382 pub fn apply_to_group_membership(&self, group_membership: &GroupMembership) -> GroupMembership {
383 tracing::info!("old group membership: {:?}", group_membership.members);
384 let mut new_membership = group_membership.clone();
385 for (inbox_id, sequence_id) in self.membership_updates.iter() {
386 new_membership.add(inbox_id.clone(), *sequence_id);
387 }
388
389 for inbox_id in self.removed_members.iter() {
390 new_membership.remove(inbox_id)
391 }
392
393 new_membership.failed_installations = new_membership
394 .failed_installations
395 .into_iter()
396 .chain(self.failed_installations.iter().cloned())
397 .collect::<HashSet<_>>()
398 .into_iter()
399 .collect();
400
401 tracing::info!("updated group membership: {:?}", new_membership.members);
402 new_membership
403 }
404}
405
406impl From<UpdateGroupMembershipIntentData> for Vec<u8> {
407 fn from(intent: UpdateGroupMembershipIntentData) -> Self {
408 let mut buf = Vec::new();
409
410 UpdateGroupMembershipData {
411 version: Some(UpdateGroupMembershipVersion::V1(UpdateGroupMembershipV1 {
412 membership_updates: intent.membership_updates,
413 removed_members: intent.removed_members,
414 failed_installations: intent.failed_installations,
415 })),
416 }
417 .encode(&mut buf)
418 .expect("encode error");
419
420 buf
421 }
422}
423
424impl TryFrom<Vec<u8>> for UpdateGroupMembershipIntentData {
425 type Error = IntentError;
426
427 fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
428 if let UpdateGroupMembershipData {
429 version: Some(UpdateGroupMembershipVersion::V1(v1)),
430 } = UpdateGroupMembershipData::decode(data.as_slice())?
431 {
432 Ok(Self::new(
433 v1.membership_updates,
434 v1.removed_members,
435 v1.failed_installations,
436 ))
437 } else {
438 Err(IntentError::MissingPayload)
439 }
440 }
441}
442
443impl<'a> TryFrom<&'a [u8]> for UpdateGroupMembershipIntentData {
444 type Error = IntentError;
445
446 fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
447 if let UpdateGroupMembershipData {
448 version: Some(UpdateGroupMembershipVersion::V1(v1)),
449 } = UpdateGroupMembershipData::decode(data)?
450 {
451 Ok(Self::new(
452 v1.membership_updates,
453 v1.removed_members,
454 v1.failed_installations,
455 ))
456 } else {
457 Err(IntentError::MissingPayload)
458 }
459 }
460}
461#[repr(i32)]
462#[derive(Debug, Clone, PartialEq)]
463pub enum AdminListActionType {
464 Add = 1, Remove = 2, AddSuper = 3, RemoveSuper = 4, }
469
470impl TryFrom<i32> for AdminListActionType {
471 type Error = IntentError;
472
473 fn try_from(value: i32) -> Result<Self, Self::Error> {
474 match value {
475 1 => Ok(AdminListActionType::Add),
476 2 => Ok(AdminListActionType::Remove),
477 3 => Ok(AdminListActionType::AddSuper),
478 4 => Ok(AdminListActionType::RemoveSuper),
479 _ => Err(IntentError::UnknownAdminListAction),
480 }
481 }
482}
483
484#[derive(Debug, Clone)]
485pub struct UpdateAdminListIntentData {
486 pub action_type: AdminListActionType,
487 pub inbox_id: String,
488}
489
490impl UpdateAdminListIntentData {
491 pub fn new(action_type: AdminListActionType, inbox_id: String) -> Self {
492 Self {
493 action_type,
494 inbox_id,
495 }
496 }
497}
498
499impl From<UpdateAdminListIntentData> for Vec<u8> {
500 fn from(intent: UpdateAdminListIntentData) -> Self {
501 let mut buf = Vec::new();
502 let action_type = intent.action_type as i32;
503
504 UpdateAdminListsData {
505 version: Some(UpdateAdminListsVersion::V1(UpdateAdminListsV1 {
506 admin_list_update_type: action_type,
507 inbox_id: intent.inbox_id,
508 })),
509 }
510 .encode(&mut buf)
511 .expect("encode error");
512
513 buf
514 }
515}
516
517impl TryFrom<Vec<u8>> for UpdateAdminListIntentData {
518 type Error = IntentError;
519
520 fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
521 let msg = UpdateAdminListsData::decode(Bytes::from(data))?;
522
523 let action_type: AdminListActionType = match msg.version {
524 Some(UpdateAdminListsVersion::V1(ref v1)) => {
525 AdminListActionType::try_from(v1.admin_list_update_type)?
526 }
527 None => return Err(IntentError::MissingUpdateAdminVersion),
528 };
529 let inbox_id = match msg.version {
530 Some(UpdateAdminListsVersion::V1(ref v1)) => v1.inbox_id.clone(),
531 None => return Err(IntentError::MissingUpdateAdminVersion),
532 };
533
534 Ok(Self::new(action_type, inbox_id))
535 }
536}
537
538#[repr(i32)]
539#[derive(Debug, Clone, PartialEq)]
540pub enum PermissionUpdateType {
541 AddMember = 1, RemoveMember = 2, AddAdmin = 3, RemoveAdmin = 4, UpdateMetadata = 5, }
547
548impl TryFrom<i32> for PermissionUpdateType {
549 type Error = IntentError;
550
551 fn try_from(value: i32) -> Result<Self, Self::Error> {
552 match value {
553 1 => Ok(PermissionUpdateType::AddMember),
554 2 => Ok(PermissionUpdateType::RemoveMember),
555 3 => Ok(PermissionUpdateType::AddAdmin),
556 4 => Ok(PermissionUpdateType::RemoveAdmin),
557 5 => Ok(PermissionUpdateType::UpdateMetadata),
558 _ => Err(IntentError::UnknownPermissionUpdateType),
559 }
560 }
561}
562
563#[repr(i32)]
564#[derive(Debug, Clone, PartialEq)]
565pub enum PermissionPolicyOption {
566 Allow = 1, Deny = 2, AdminOnly = 3, SuperAdminOnly = 4, }
571
572impl TryFrom<i32> for PermissionPolicyOption {
573 type Error = IntentError;
574
575 fn try_from(value: i32) -> Result<Self, Self::Error> {
576 match value {
577 1 => Ok(PermissionPolicyOption::Allow),
578 2 => Ok(PermissionPolicyOption::Deny),
579 3 => Ok(PermissionPolicyOption::AdminOnly),
580 4 => Ok(PermissionPolicyOption::SuperAdminOnly),
581 _ => Err(IntentError::UnknownPermissionPolicyOption),
582 }
583 }
584}
585
586impl From<PermissionPolicyOption> for MembershipPolicies {
587 fn from(value: PermissionPolicyOption) -> Self {
588 match value {
589 PermissionPolicyOption::Allow => MembershipPolicies::allow(),
590 PermissionPolicyOption::Deny => MembershipPolicies::deny(),
591 PermissionPolicyOption::AdminOnly => MembershipPolicies::allow_if_actor_admin(),
592 PermissionPolicyOption::SuperAdminOnly => {
593 MembershipPolicies::allow_if_actor_super_admin()
594 }
595 }
596 }
597}
598
599impl From<PermissionPolicyOption> for MetadataPolicies {
600 fn from(value: PermissionPolicyOption) -> Self {
601 match value {
602 PermissionPolicyOption::Allow => MetadataPolicies::allow(),
603 PermissionPolicyOption::Deny => MetadataPolicies::deny(),
604 PermissionPolicyOption::AdminOnly => MetadataPolicies::allow_if_actor_admin(),
605 PermissionPolicyOption::SuperAdminOnly => {
606 MetadataPolicies::allow_if_actor_super_admin()
607 }
608 }
609 }
610}
611
612impl From<PermissionPolicyOption> for PermissionsPolicies {
613 fn from(value: PermissionPolicyOption) -> Self {
614 match value {
615 PermissionPolicyOption::Allow => {
616 tracing::error!(
617 "PermissionPolicyOption::Allow is not allowed for PermissionsPolicies, set to super_admin only instead"
618 );
619 PermissionsPolicies::allow_if_actor_super_admin()
620 }
621 PermissionPolicyOption::Deny => PermissionsPolicies::deny(),
622 PermissionPolicyOption::AdminOnly => PermissionsPolicies::allow_if_actor_admin(),
623 PermissionPolicyOption::SuperAdminOnly => {
624 PermissionsPolicies::allow_if_actor_super_admin()
625 }
626 }
627 }
628}
629
630#[derive(Debug, Clone)]
631pub struct UpdatePermissionIntentData {
632 pub update_type: PermissionUpdateType,
633 pub policy_option: PermissionPolicyOption,
634 pub metadata_field_name: Option<String>,
635}
636
637impl UpdatePermissionIntentData {
638 pub fn new(
639 update_type: PermissionUpdateType,
640 policy_option: PermissionPolicyOption,
641 metadata_field_name: Option<String>,
642 ) -> Self {
643 Self {
644 update_type,
645 policy_option,
646 metadata_field_name,
647 }
648 }
649}
650
651impl From<UpdatePermissionIntentData> for Vec<u8> {
652 fn from(intent: UpdatePermissionIntentData) -> Self {
653 let mut buf = Vec::new();
654 let update_type = intent.update_type as i32;
655 let policy_option = intent.policy_option as i32;
656
657 UpdatePermissionData {
658 version: Some(UpdatePermissionVersion::V1(UpdatePermissionV1 {
659 permission_update_type: update_type,
660 permission_policy_option: policy_option,
661 metadata_field_name: intent.metadata_field_name,
662 })),
663 }
664 .encode(&mut buf)
665 .expect("encode error");
666
667 buf
668 }
669}
670
671impl TryFrom<Vec<u8>> for UpdatePermissionIntentData {
672 type Error = IntentError;
673
674 fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
675 let msg = UpdatePermissionData::decode(Bytes::from(data))?;
676 let Some(UpdatePermissionVersion::V1(update_permission_data::V1 {
677 permission_update_type,
678 permission_policy_option,
679 metadata_field_name,
680 })) = msg.version
681 else {
682 return Err(IntentError::UnsupportedPermissionVersion);
683 };
684 let update_type: PermissionUpdateType = permission_update_type.try_into()?;
685 let policy_option: PermissionPolicyOption = permission_policy_option.try_into()?;
686 Ok(Self::new(update_type, policy_option, metadata_field_name))
687 }
688}
689
690pub(crate) struct ReaddInstallationsIntentData {
691 pub readded_installations: Vec<Vec<u8>>,
692}
693
694impl ReaddInstallationsIntentData {
695 pub fn new(readded_installations: Vec<Vec<u8>>) -> Self {
696 Self {
697 readded_installations,
698 }
699 }
700}
701
702impl From<ReaddInstallationsIntentData> for Vec<u8> {
703 fn from(intent: ReaddInstallationsIntentData) -> Self {
704 let mut buf = Vec::new();
705 ReaddInstallationsData {
706 version: Some(ReaddInstallationsVersion::V1(ReaddInstallationsV1 {
707 readded_installations: intent.readded_installations,
708 })),
709 }
710 .encode(&mut buf)
711 .expect("encode error");
712
713 buf
714 }
715}
716
717impl TryFrom<Vec<u8>> for ReaddInstallationsIntentData {
718 type Error = IntentError;
719
720 fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
721 if let ReaddInstallationsData {
722 version: Some(ReaddInstallationsVersion::V1(v1)),
723 } = ReaddInstallationsData::decode(data.as_slice())?
724 {
725 Ok(Self::new(v1.readded_installations))
726 } else {
727 Err(IntentError::MissingPayload)
728 }
729 }
730}
731
732impl TryFrom<&[u8]> for ReaddInstallationsIntentData {
733 type Error = IntentError;
734
735 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
736 if let ReaddInstallationsData {
737 version: Some(ReaddInstallationsVersion::V1(v1)),
738 } = ReaddInstallationsData::decode(data)?
739 {
740 Ok(Self::new(v1.readded_installations))
741 } else {
742 Err(IntentError::MissingPayload)
743 }
744 }
745}
746
747#[derive(Debug, Clone)]
749pub(crate) struct ProposeMemberUpdateIntentData {
750 pub add_inbox_ids: Vec<String>,
751 pub remove_inbox_ids: Vec<String>,
752}
753
754impl ProposeMemberUpdateIntentData {
755 pub fn new(add_inbox_ids: Vec<String>, remove_inbox_ids: Vec<String>) -> Self {
756 Self {
757 add_inbox_ids,
758 remove_inbox_ids,
759 }
760 }
761}
762
763impl TryFrom<ProposeMemberUpdateIntentData> for Vec<u8> {
764 type Error = IntentError;
765 fn try_from(intent: ProposeMemberUpdateIntentData) -> Result<Self, Self::Error> {
766 let decode_inbox_ids =
767 |inbox_ids: Vec<String>, item: &'static str| -> Result<Vec<Vec<u8>>, IntentError> {
768 inbox_ids
769 .into_iter()
770 .map(|s| {
771 hex::decode(&s)
772 .map_err(|_| xmtp_proto::ConversionError::InvalidValue {
773 item,
774 expected: "hex encoded string",
775 got: s,
776 })
777 .map_err(Into::into)
778 })
779 .collect::<Result<Vec<_>, _>>()
780 };
781 let proposal = ProposeMemberUpdateData {
782 version: Some(propose_member_update_data::Version::V1(
783 propose_member_update_data::V1 {
784 add_inbox_ids: decode_inbox_ids(intent.add_inbox_ids, "add_inbox_ids")?,
785 remove_inbox_ids: decode_inbox_ids(
786 intent.remove_inbox_ids,
787 "remove_inbox_ids",
788 )?,
789 },
790 )),
791 }
792 .encode_to_vec();
793 Ok(proposal)
794 }
795}
796
797impl TryFrom<&[u8]> for ProposeMemberUpdateIntentData {
798 type Error = IntentError;
799
800 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
801 let proto = ProposeMemberUpdateData::decode(data)?;
802 let decode_inbox_ids = |inbox_ids: Vec<Vec<u8>>| -> Vec<String> {
803 inbox_ids
804 .into_iter()
805 .map(|b| hex::encode(&b))
806 .collect::<Vec<_>>()
807 };
808 match proto.version {
809 Some(propose_member_update_data::Version::V1(v1)) => {
810 let add_inbox_ids = decode_inbox_ids(v1.add_inbox_ids);
811 let remove_inbox_ids = decode_inbox_ids(v1.remove_inbox_ids);
812 Ok(Self::new(add_inbox_ids, remove_inbox_ids))
813 }
814 None => Err(IntentError::MissingPayload),
815 }
816 }
817}
818
819#[derive(Debug, Clone)]
821pub(crate) struct ProposeGroupContextExtensionsIntentData {
822 pub extensions_bytes: Vec<u8>,
824}
825
826impl ProposeGroupContextExtensionsIntentData {
827 pub fn new(extensions_bytes: Vec<u8>) -> Self {
828 Self { extensions_bytes }
829 }
830}
831
832impl From<ProposeGroupContextExtensionsIntentData> for Vec<u8> {
833 fn from(intent: ProposeGroupContextExtensionsIntentData) -> Self {
834 ProposeGroupContextExtensionData {
835 version: Some(propose_group_context_extension_data::Version::V1(
836 propose_group_context_extension_data::V1 {
837 group_context_extension: intent.extensions_bytes,
838 },
839 )),
840 }
841 .encode_to_vec()
842 }
843}
844
845impl TryFrom<&[u8]> for ProposeGroupContextExtensionsIntentData {
846 type Error = IntentError;
847
848 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
849 let proto = ProposeGroupContextExtensionData::decode(data)?;
850 match proto.version {
851 Some(propose_group_context_extension_data::Version::V1(v1)) => {
852 Ok(Self::new(v1.group_context_extension))
853 }
854 None => Err(IntentError::MissingPayload),
855 }
856 }
857}
858
859#[derive(Debug, Clone, Default)]
861pub(crate) struct CommitPendingProposalsIntentData {
862 }
864
865impl CommitPendingProposalsIntentData {
866 pub fn new() -> Self {
867 Self {}
868 }
869}
870
871impl From<CommitPendingProposalsIntentData> for Vec<u8> {
872 fn from(_intent: CommitPendingProposalsIntentData) -> Self {
873 CommitPendingProposalsData {
874 version: Some(commit_pending_proposals_data::Version::V1(
875 commit_pending_proposals_data::V1 {
876 proposal_hashes: vec![],
877 },
878 )),
879 }
880 .encode_to_vec()
881 }
882}
883
884impl TryFrom<&[u8]> for CommitPendingProposalsIntentData {
885 type Error = IntentError;
886
887 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
888 if data.is_empty() {
890 return Ok(Self::new());
891 }
892 let proto = CommitPendingProposalsData::decode(data)?;
893 match proto.version {
894 Some(commit_pending_proposals_data::Version::V1(_)) => Ok(Self::new()),
895 None => Err(IntentError::MissingPayload),
896 }
897 }
898}
899
900#[derive(Debug, Clone)]
901pub enum PostCommitAction {
902 SendWelcomes(SendWelcomesAction),
903}
904
905#[derive(Debug)]
906pub struct MembershipDiffWithKeyPackages {
907 pub new_installations: Vec<Installation>,
908 pub new_key_packages: Vec<KeyPackage>,
909 pub removed_installations: HashSet<Vec<u8>>,
910 pub failed_installations: Vec<Vec<u8>>,
911}
912
913impl MembershipDiffWithKeyPackages {
914 pub fn new(
915 new_installations: Vec<Installation>,
916 new_key_packages: Vec<KeyPackage>,
917 removed_installations: HashSet<Vec<u8>>,
918 failed_installations: Vec<Vec<u8>>,
919 ) -> MembershipDiffWithKeyPackages {
920 MembershipDiffWithKeyPackages {
921 new_installations,
922 new_key_packages,
923 removed_installations,
924 failed_installations,
925 }
926 }
927}
928
929#[derive(Debug, Clone)]
930pub struct Installation {
931 pub(crate) installation_key: Vec<u8>,
932 pub(crate) hpke_public_key: Vec<u8>,
933 pub(crate) welcome_wrapper_algorithm: WrapperAlgorithm,
934 pub(crate) welcome_pointee_encryption_aead_types: WelcomePointersExtension,
935}
936
937impl Installation {
938 pub fn from_verified_key_package(
939 key_package: &VerifiedKeyPackageV2,
940 ) -> Result<Self, IntentError> {
941 let wrapper_encryption = WrapperEncryptionExtension::advertised_by(&key_package.inner)?;
942
943 let welcome_pointee_encryption_aead_types = key_package
944 .inner
945 .extensions()
946 .unknown(WELCOME_POINTEE_ENCRYPTION_AEAD_TYPES_EXTENSION_ID)
947 .map(|ext| ext.0.as_slice().try_into())
948 .transpose()?;
949
950 Ok(Self {
951 installation_key: key_package.installation_id(),
952 hpke_public_key: wrapper_encryption.pub_key_bytes,
953 welcome_wrapper_algorithm: wrapper_encryption.algorithm,
954 welcome_pointee_encryption_aead_types: welcome_pointee_encryption_aead_types
955 .unwrap_or_else(WelcomePointersExtension::empty),
956 })
957 }
958}
959
960impl From<Installation> for InstallationProto {
961 fn from(installation: Installation) -> Self {
962 Self {
963 installation_key: installation.installation_key,
964 hpke_public_key: installation.hpke_public_key,
965 welcome_wrapper_algorithm: installation.welcome_wrapper_algorithm.into(),
966 welcome_pointee_encryption_aead_types: Some(
967 installation.welcome_pointee_encryption_aead_types.into(),
968 ),
969 }
970 }
971}
972
973impl TryFrom<InstallationProto> for Installation {
974 type Error = ConversionError;
975 fn try_from(installation: InstallationProto) -> Result<Self, Self::Error> {
976 Ok(Self {
977 installation_key: installation.installation_key,
978 hpke_public_key: installation.hpke_public_key,
979 welcome_wrapper_algorithm: installation.welcome_wrapper_algorithm.try_into()?,
980 welcome_pointee_encryption_aead_types: installation
981 .welcome_pointee_encryption_aead_types
982 .map(Into::into)
983 .unwrap_or_else(WelcomePointersExtension::empty),
984 })
985 }
986}
987
988#[derive(Debug, Clone)]
989pub struct SendWelcomesAction {
990 pub installations: Vec<Installation>,
991 pub welcome_message: Vec<u8>,
992}
993
994impl SendWelcomesAction {
995 pub fn new(installations: Vec<Installation>, welcome_message: Vec<u8>) -> Self {
996 Self {
997 installations,
998 welcome_message,
999 }
1000 }
1001
1002 pub(crate) fn to_bytes(&self) -> Vec<u8> {
1003 PostCommitActionProto {
1004 kind: Some(PostCommitActionKind::SendWelcomes(SendWelcomesProto {
1005 installations: self
1006 .installations
1007 .clone()
1008 .into_iter()
1009 .map(|i| i.into())
1010 .collect(),
1011 welcome_message: self.welcome_message.clone(),
1012 })),
1013 }
1014 .encode_to_vec()
1015 }
1016}
1017
1018impl PostCommitAction {
1019 pub(crate) fn to_bytes(&self) -> Vec<u8> {
1020 match self {
1021 PostCommitAction::SendWelcomes(action) => action.to_bytes(),
1022 }
1023 }
1024
1025 pub(crate) fn from_bytes(data: &[u8]) -> Result<Self, IntentError> {
1026 let decoded = PostCommitActionProto::decode(data)?
1027 .kind
1028 .ok_or(IntentError::MissingPostCommit)?;
1029 match decoded {
1030 PostCommitActionKind::SendWelcomes(proto) => {
1031 Ok(Self::SendWelcomes(SendWelcomesAction::new(
1032 proto
1033 .installations
1034 .into_iter()
1035 .map(|i| i.try_into())
1036 .collect::<Result<_, _>>()?,
1037 proto.welcome_message,
1038 )))
1039 }
1040 }
1041 }
1042
1043 pub(crate) fn from_welcome(
1044 welcome: MlsMessageOut,
1045 installations: Vec<Installation>,
1046 ) -> Result<Self, IntentError> {
1047 let welcome_bytes = welcome.tls_serialize_detached()?;
1048
1049 Ok(Self::SendWelcomes(SendWelcomesAction::new(
1050 installations,
1051 welcome_bytes,
1052 )))
1053 }
1054}
1055
1056impl TryFrom<Vec<u8>> for PostCommitAction {
1057 type Error = IntentError;
1058
1059 fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
1060 PostCommitAction::from_bytes(data.as_slice())
1061 }
1062}
1063
1064#[derive(Debug, Clone, PartialEq, Eq)]
1071pub enum AppDataUpdateIntentData {
1072 Payload { component_id: u16, payload: Vec<u8> },
1076 Fields(Vec<FieldWrite>),
1081}
1082
1083impl AppDataUpdateIntentData {
1084 pub fn new(component_id: u16, payload: Vec<u8>) -> Self {
1086 Self::Payload {
1087 component_id,
1088 payload,
1089 }
1090 }
1091}
1092
1093impl From<AppDataUpdateIntentData> for Vec<u8> {
1094 fn from(intent: AppDataUpdateIntentData) -> Self {
1095 use xmtp_proto::xmtp::mls::database::{
1096 AppDataUpdateData,
1097 app_data_update_data::{
1098 FieldWrite as FieldWriteProto, V1, V2, Version, field_write::Operation,
1099 },
1100 };
1101 let version = match intent {
1102 AppDataUpdateIntentData::Payload {
1103 component_id,
1104 payload,
1105 } => Version::V1(V1 {
1106 component_id: component_id.into(),
1107 payload,
1108 }),
1109 AppDataUpdateIntentData::Fields(writes) => Version::V2(V2 {
1110 writes: writes
1111 .into_iter()
1112 .map(|write| FieldWriteProto {
1113 component_id: write.component_id.as_u16().into(),
1114 component_type: write.component_type.into(),
1115 operation: Some(match write.operation {
1116 WriteOperation::Update(payload) => Operation::Update(payload),
1117 WriteOperation::Remove => Operation::Remove(true),
1118 WriteOperation::SetOwn(value) => Operation::SetOwn(value),
1119 WriteOperation::ClearOwn => Operation::ClearOwn(true),
1120 }),
1121 })
1122 .collect(),
1123 }),
1124 };
1125 AppDataUpdateData {
1126 version: Some(version),
1127 }
1128 .encode_to_vec()
1129 }
1130}
1131
1132impl TryFrom<Vec<u8>> for AppDataUpdateIntentData {
1133 type Error = IntentError;
1134
1135 fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
1136 Self::try_from(data.as_slice())
1137 }
1138}
1139
1140impl TryFrom<&[u8]> for AppDataUpdateIntentData {
1141 type Error = IntentError;
1142
1143 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
1144 use xmtp_proto::xmtp::mls::database::{
1145 AppDataUpdateData,
1146 app_data_update_data::{Version, field_write::Operation},
1147 };
1148 let invalid =
1149 |reason: String| IntentError::Generic(format!("AppDataUpdateIntentData {reason}"));
1150 let component_id = |id: u32| {
1151 u16::try_from(id).map_err(|_| invalid(format!("component_id {id} exceeds u16 range")))
1152 };
1153 match AppDataUpdateData::decode(data)?.version {
1154 Some(Version::V1(v1)) => Ok(Self::Payload {
1155 component_id: component_id(v1.component_id)?,
1156 payload: v1.payload,
1157 }),
1158 Some(Version::V2(v2)) => v2
1159 .writes
1160 .into_iter()
1161 .map(|write| {
1162 Ok(FieldWrite {
1163 component_id: ComponentId::new(component_id(write.component_id)?),
1164 component_type: ComponentType::try_from(write.component_type).map_err(
1165 |_| {
1166 invalid(format!(
1167 "component_type {} is unknown",
1168 write.component_type
1169 ))
1170 },
1171 )?,
1172 operation: match write.operation {
1173 Some(Operation::Update(payload)) => WriteOperation::Update(payload),
1174 Some(Operation::Remove(_)) => WriteOperation::Remove,
1175 Some(Operation::SetOwn(value)) => WriteOperation::SetOwn(value),
1176 Some(Operation::ClearOwn(_)) => WriteOperation::ClearOwn,
1177 None => return Err(invalid("field write has no operation".into())),
1178 },
1179 })
1180 })
1181 .collect::<Result<_, _>>()
1182 .map(Self::Fields),
1183 None => Err(invalid("missing version oneof variant".into())),
1184 }
1185 }
1186}
1187
1188#[cfg(test)]
1189mod app_data_update_intent_tests {
1190 use super::*;
1191
1192 #[test]
1193 fn round_trip_basic() {
1194 let intent = AppDataUpdateIntentData::new(0x800C, vec![0xAA, 0xBB, 0xCC]);
1195 let bytes: Vec<u8> = intent.clone().into();
1196 let restored = AppDataUpdateIntentData::try_from(bytes).unwrap();
1197 assert_eq!(restored, intent);
1198 }
1199
1200 #[test]
1201 fn empty_payload_round_trips() {
1202 let intent = AppDataUpdateIntentData::new(0x800C, vec![]);
1207 let bytes: Vec<u8> = intent.clone().into();
1208 let restored = AppDataUpdateIntentData::try_from(bytes).unwrap();
1209 assert_eq!(restored, intent);
1210 }
1211
1212 #[test]
1213 fn missing_version_variant_surfaces_error() {
1214 use prost::Message;
1218 use xmtp_proto::xmtp::mls::database::AppDataUpdateData;
1219 let bytes = AppDataUpdateData { version: None }.encode_to_vec();
1220 let err = AppDataUpdateIntentData::try_from(bytes).unwrap_err();
1221 match err {
1222 IntentError::Generic(msg) => assert!(msg.contains("missing version")),
1223 _ => panic!("expected Generic error, got {err:?}"),
1224 }
1225 }
1226
1227 #[test]
1228 fn component_id_overflow_surfaces_error() {
1229 use prost::Message;
1236 use xmtp_proto::xmtp::mls::database::{
1237 AppDataUpdateData, app_data_update_data::V1 as AppDataUpdateDataV1,
1238 app_data_update_data::Version as AppDataUpdateVersion,
1239 };
1240 let bytes = AppDataUpdateData {
1241 version: Some(AppDataUpdateVersion::V1(AppDataUpdateDataV1 {
1242 component_id: u16::MAX as u32 + 1,
1243 payload: vec![],
1244 })),
1245 }
1246 .encode_to_vec();
1247 let err = AppDataUpdateIntentData::try_from(bytes).unwrap_err();
1248 match err {
1249 IntentError::Generic(msg) => assert!(msg.contains("exceeds u16")),
1250 _ => panic!("expected Generic error, got {err:?}"),
1251 }
1252 }
1253
1254 #[xmtp_common::test]
1258 fn field_writes_round_trip() {
1259 let write = |id, component_type, operation| FieldWrite {
1260 component_id: ComponentId::new(id),
1261 component_type,
1262 operation,
1263 };
1264 let intent = AppDataUpdateIntentData::Fields(vec![
1265 write(
1266 0x8001,
1267 ComponentType::String,
1268 WriteOperation::Update(b"a".to_vec()),
1269 ),
1270 write(0x8002, ComponentType::Bytes, WriteOperation::Remove),
1271 write(
1272 0x8003,
1273 ComponentType::TlsMapInboxIdString,
1274 WriteOperation::SetOwn(b"b".to_vec()),
1275 ),
1276 write(
1277 0x8004,
1278 ComponentType::TlsMapInboxIdBytes,
1279 WriteOperation::ClearOwn,
1280 ),
1281 ]);
1282 let bytes: Vec<u8> = intent.clone().into();
1283 assert_eq!(AppDataUpdateIntentData::try_from(bytes).unwrap(), intent);
1284 }
1285
1286 #[xmtp_common::test]
1289 fn unreadable_field_writes_fail_closed() {
1290 use xmtp_proto::xmtp::mls::database::{
1291 AppDataUpdateData,
1292 app_data_update_data::{
1293 FieldWrite as FieldWriteProto, V2, Version, field_write::Operation,
1294 },
1295 };
1296 let decode = |component_type, operation| {
1297 AppDataUpdateIntentData::try_from(
1298 AppDataUpdateData {
1299 version: Some(Version::V2(V2 {
1300 writes: vec![FieldWriteProto {
1301 component_id: 0x8001,
1302 component_type,
1303 operation,
1304 }],
1305 })),
1306 }
1307 .encode_to_vec(),
1308 )
1309 };
1310 assert!(decode(ComponentType::Bytes.into(), Some(Operation::Remove(true))).is_ok());
1311 assert!(decode(99, Some(Operation::Remove(true))).is_err());
1312 assert!(decode(ComponentType::Bytes.into(), None).is_err());
1313 }
1314
1315 #[test]
1316 fn malformed_proto_bytes_surface_decode_error() {
1317 let bytes = vec![0xFF; 16];
1320 assert!(AppDataUpdateIntentData::try_from(bytes).is_err());
1321 }
1322}
1323
1324#[cfg(test)]
1325pub(crate) mod tests {
1326 use crate::context::XmtpSharedContext;
1327 use crate::groups::send_message_opts::SendMessageOpts;
1328 use openmls::prelude::{ProcessedMessageContent, ProtocolMessage};
1329 use xmtp_cryptography::utils::generate_local_wallet;
1330 use xmtp_db::XmtpOpenMlsProviderRef;
1331
1332 use crate::{builder::ClientBuilder, utils::TestMlsGroup};
1333
1334 use super::*;
1335
1336 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1337 #[cfg_attr(not(target_arch = "wasm32"), test)]
1338 fn test_serialize_send_message() {
1339 let message = vec![1, 2, 3];
1340 let intent = SendMessageIntentData::new(message.clone());
1341 let as_bytes: Vec<u8> = intent.into();
1342 let restored_intent = SendMessageIntentData::from_bytes(as_bytes.as_slice()).unwrap();
1343
1344 assert_eq!(restored_intent.message, message);
1345 }
1346
1347 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1348 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
1349 async fn test_serialize_update_membership() {
1350 let mut membership_updates = HashMap::new();
1351 membership_updates.insert("foo".to_string(), 123);
1352
1353 let intent = UpdateGroupMembershipIntentData::new(
1354 membership_updates,
1355 vec!["bar".to_string()],
1356 vec![vec![1, 2, 3]],
1357 );
1358
1359 let as_bytes: Vec<u8> = intent.clone().into();
1360 let restored_intent: UpdateGroupMembershipIntentData = as_bytes.try_into().unwrap();
1361
1362 assert_eq!(
1363 intent.membership_updates,
1364 restored_intent.membership_updates
1365 );
1366
1367 assert_eq!(intent.removed_members, restored_intent.removed_members);
1368
1369 assert_eq!(
1370 intent.failed_installations,
1371 restored_intent.failed_installations
1372 );
1373 }
1374
1375 #[xmtp_common::test]
1376 async fn test_serialize_update_metadata() {
1377 let intent = UpdateMetadataIntentData::new_update_group_name("group name".to_string());
1378 let as_bytes: Vec<u8> = intent.clone().into();
1379 let restored_intent: UpdateMetadataIntentData =
1380 UpdateMetadataIntentData::try_from(as_bytes).unwrap();
1381
1382 assert_eq!(intent.field_value, restored_intent.field_value);
1383 }
1384
1385 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1386 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
1387 async fn test_serialize_readd_installations() {
1388 let readded_installations = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
1389
1390 let intent = ReaddInstallationsIntentData::new(readded_installations.clone());
1391
1392 let as_bytes: Vec<u8> = intent.into();
1393 let restored_intent: ReaddInstallationsIntentData = as_bytes.try_into().unwrap();
1394
1395 assert_eq!(readded_installations, restored_intent.readded_installations);
1396 }
1397
1398 #[xmtp_common::test]
1399 async fn test_key_rotation_before_first_message() {
1400 let client_a = ClientBuilder::new_test_client_vanilla(&generate_local_wallet()).await;
1401 let client_b = ClientBuilder::new_test_client_vanilla(&generate_local_wallet()).await;
1402
1403 let group_a = client_a.create_group(None, None).expect("create group");
1405 group_a.add_members(&[client_b.inbox_id()]).await.unwrap();
1406 group_a
1407 .send_message(b"First message from A", SendMessageOpts::default())
1408 .await
1409 .unwrap();
1410
1411 verify_num_payloads_in_group(&group_a, 4).await;
1414
1415 let groups_b = client_b.sync_welcomes().await.unwrap();
1417 assert_eq!(groups_b.len(), 1);
1418 let group_b = groups_b[0].clone();
1419 group_b
1420 .send_message(b"First message from B", SendMessageOpts::default())
1421 .await
1422 .expect("send message");
1423
1424 let payloads_a = verify_num_payloads_in_group(&group_a, 6).await;
1427 let payloads_b = verify_num_payloads_in_group(&group_b, 6).await;
1428
1429 for i in 0..payloads_a.len() {
1431 assert_eq!(payloads_a[i].payload_hash, payloads_b[i].payload_hash);
1432 }
1433 verify_commit_updates_leaf_node(&group_a, &payloads_a[4]);
1434
1435 group_b
1437 .send_message(b"Second message from B", SendMessageOpts::default())
1438 .await
1439 .expect("send message");
1440 group_a
1441 .send_message(b"Second message from A", SendMessageOpts::default())
1442 .await
1443 .expect("send message");
1444
1445 verify_num_payloads_in_group(&group_a, 8).await;
1447 verify_num_payloads_in_group(&group_b, 8).await;
1448 }
1449
1450 async fn verify_num_payloads_in_group(
1451 group: &TestMlsGroup,
1452 num_messages: usize,
1453 ) -> Vec<xmtp_proto::types::GroupMessage> {
1454 let messages = group
1455 .context
1456 .api()
1457 .query_group_messages(group.group_id)
1458 .await
1459 .unwrap();
1460 assert_eq!(messages.len(), num_messages);
1461 messages
1462 }
1463
1464 fn verify_commit_updates_leaf_node(
1465 group: &TestMlsGroup,
1466 message: &xmtp_proto::types::GroupMessage,
1467 ) {
1468 let mls_message = message.message.clone();
1469 let mls_message = match mls_message {
1470 ProtocolMessage::PrivateMessage(mls_message) => mls_message,
1471 _ => panic!("error mls_message"),
1472 };
1473
1474 let storage = group.context.mls_storage();
1475 let decrypted_message = group
1476 .load_mls_group_with_lock(storage, |mut mls_group| {
1477 Ok(mls_group
1478 .process_message(&XmtpOpenMlsProviderRef::new(storage), mls_message.clone())
1479 .unwrap())
1480 })
1481 .unwrap();
1482
1483 let staged_commit = match decrypted_message.into_content() {
1484 ProcessedMessageContent::StagedCommitMessage(staged_commit) => *staged_commit,
1485 _ => panic!("error staged_commit"),
1486 };
1487
1488 let path_update_leaf_node = staged_commit.update_path_leaf_node();
1490 assert!(path_update_leaf_node.is_some());
1491 }
1492}