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