1use openmls::messages::proposals::AppDataUpdateOperation;
23use tls_codec::{Deserialize, Serialize, VLBytes};
24use xmtp_proto::xmtp::mls::message_contents::ComponentType;
25
26use crate::{
27 app_data::{
28 component_id::ComponentId,
29 component_registry::{ComponentOp, ComponentRegistry, ComponentRegistryError},
30 typed::{Component, ComponentTypedError, ExpandedComponentChange},
31 },
32 inbox_id::InboxId,
33 tls_map::{TlsMap, TlsMapDelta, TlsMapMutation},
34};
35
36pub(crate) fn apply_tls_map_delta<K>(
51 payload: &[u8],
52 prior: Option<&[u8]>,
53) -> Result<Vec<u8>, ComponentTypedError>
54where
55 K: tls_codec::Serialize
56 + tls_codec::Deserialize
57 + tls_codec::Size
58 + Ord
59 + Eq
60 + Clone
61 + std::fmt::Debug,
62{
63 let delta = TlsMapDelta::<K, VLBytes>::tls_deserialize_exact(payload)?;
64 let mut map: TlsMap<K, VLBytes> = match prior {
65 Some(bytes) => TlsMap::<K, VLBytes>::tls_deserialize_exact(bytes)?,
66 None => TlsMap::new(),
67 };
68 map.apply_delta(delta)?;
69 Ok(map.tls_serialize_detached()?)
70}
71
72pub(crate) fn expand_tls_map_changes<K>(
100 op: &AppDataUpdateOperation,
101 _prior: Option<&[u8]>,
102) -> Result<Vec<ExpandedComponentChange>, ComponentTypedError>
103where
104 K: tls_codec::Serialize + tls_codec::Deserialize + tls_codec::Size,
105{
106 match op {
107 AppDataUpdateOperation::Remove => Ok(vec![ExpandedComponentChange {
108 op: ComponentOp::Delete,
109 value: None,
110 }]),
111 AppDataUpdateOperation::Update(payload) => {
112 let delta = TlsMapDelta::<K, VLBytes>::tls_deserialize_exact(payload.as_slice())?;
113 expand_decoded_tls_map_delta(delta)
114 }
115 }
116}
117
118fn expand_decoded_tls_map_delta<K>(
123 delta: TlsMapDelta<K, VLBytes>,
124) -> Result<Vec<ExpandedComponentChange>, ComponentTypedError>
125where
126 K: tls_codec::Serialize + tls_codec::Size,
127{
128 let mut out = Vec::with_capacity(delta.mutations.len());
129 for mutation in delta.mutations {
130 match mutation {
131 TlsMapMutation::Insert { value, .. } => out.push(ExpandedComponentChange {
132 op: ComponentOp::Insert,
133 value: Some(value.as_slice().to_vec()),
134 }),
135 TlsMapMutation::Update { value, .. } => out.push(ExpandedComponentChange {
136 op: ComponentOp::Update,
137 value: Some(value.as_slice().to_vec()),
138 }),
139 TlsMapMutation::Delete { key } => {
140 let key_bytes = key.tls_serialize_detached()?;
141 out.push(ExpandedComponentChange {
142 op: ComponentOp::Delete,
143 value: Some(key_bytes),
144 });
145 }
146 }
147 }
148 Ok(out)
149}
150
151pub struct GroupMembershipComponent;
163
164impl Component for GroupMembershipComponent {
165 const ID: ComponentId = ComponentId::GROUP_MEMBERSHIP;
166 const COMPONENT_TYPE: ComponentType = ComponentType::TlsMapInboxIdBytes;
167 type Value = TlsMap<InboxId, VLBytes>;
168 type Mutation = TlsMapDelta<InboxId, VLBytes>;
173
174 fn decode_value(bytes: &[u8]) -> Result<Self::Value, ComponentTypedError> {
175 TlsMap::<InboxId, VLBytes>::tls_deserialize_exact(bytes).map_err(Into::into)
176 }
177
178 fn encode_value(value: &Self::Value) -> Result<Vec<u8>, ComponentTypedError> {
179 value.tls_serialize_detached().map_err(Into::into)
180 }
181
182 fn encode_mutation(mutation: &Self::Mutation) -> Result<Vec<u8>, ComponentTypedError> {
183 mutation.tls_serialize_detached().map_err(Into::into)
184 }
185
186 fn apply_update_payload(
187 payload: &[u8],
188 prior: Option<&[u8]>,
189 ) -> Result<Vec<u8>, ComponentTypedError> {
190 apply_tls_map_delta::<InboxId>(payload, prior)
191 }
192
193 fn expand_to_changes(
194 op: &AppDataUpdateOperation,
195 prior: Option<&[u8]>,
196 ) -> Result<Vec<ExpandedComponentChange>, ComponentTypedError> {
197 expand_tls_map_changes::<InboxId>(op, prior)
198 }
199}
200
201pub struct ComponentRegistryComponent;
221
222fn validate_registry_delta(
258 delta: &TlsMapDelta<ComponentId, VLBytes>,
259) -> Result<(), ComponentTypedError> {
260 for mutation in &delta.mutations {
261 match mutation {
262 TlsMapMutation::Insert { key, value } => {
263 ComponentRegistry::validate_entry(*key, value.as_slice())?;
264 }
265 TlsMapMutation::Update { key, value } => {
266 ComponentRegistry::validate_entry(*key, value.as_slice())?;
267 if key.is_immutable() {
268 return Err(ComponentRegistryError::ImmutableComponent(*key).into());
269 }
270 }
271 TlsMapMutation::Delete { key } => {
272 if !key.is_in_component_space() {
273 return Err(ComponentRegistryError::InvalidComponentId(*key).into());
274 }
275 if key.is_reserved() {
276 return Err(ComponentRegistryError::ReservedRange(*key).into());
277 }
278 if key.is_hardcoded() {
279 return Err(ComponentRegistryError::HardcodedComponent(*key).into());
280 }
281 if key.is_immutable() {
282 return Err(ComponentRegistryError::ImmutableComponent(*key).into());
283 }
284 }
285 }
286 }
287 Ok(())
288}
289
290impl Component for ComponentRegistryComponent {
291 const ID: ComponentId = ComponentId::COMPONENT_REGISTRY;
292 const COMPONENT_TYPE: ComponentType = ComponentType::TlsMapBytesBytes;
293 type Value = TlsMap<ComponentId, VLBytes>;
294 type Mutation = TlsMapDelta<ComponentId, VLBytes>;
298
299 fn decode_value(bytes: &[u8]) -> Result<Self::Value, ComponentTypedError> {
300 TlsMap::<ComponentId, VLBytes>::tls_deserialize_exact(bytes).map_err(Into::into)
301 }
302
303 fn encode_value(value: &Self::Value) -> Result<Vec<u8>, ComponentTypedError> {
304 value.tls_serialize_detached().map_err(Into::into)
305 }
306
307 fn encode_mutation(mutation: &Self::Mutation) -> Result<Vec<u8>, ComponentTypedError> {
308 mutation.tls_serialize_detached().map_err(Into::into)
309 }
310
311 fn apply_update_payload(
312 payload: &[u8],
313 prior: Option<&[u8]>,
314 ) -> Result<Vec<u8>, ComponentTypedError> {
315 let delta = TlsMapDelta::<ComponentId, VLBytes>::tls_deserialize_exact(payload)?;
316 validate_registry_delta(&delta)?;
317 let mut map = match prior {
318 Some(bytes) => TlsMap::<ComponentId, VLBytes>::tls_deserialize_exact(bytes)?,
319 None => TlsMap::new(),
320 };
321 map.apply_delta(delta)?;
322 Ok(map.tls_serialize_detached()?)
323 }
324
325 fn expand_to_changes(
326 op: &AppDataUpdateOperation,
327 _prior: Option<&[u8]>,
328 ) -> Result<Vec<ExpandedComponentChange>, ComponentTypedError> {
329 match op {
330 AppDataUpdateOperation::Remove => Err(ComponentRegistryError::HardcodedComponent(
339 ComponentId::COMPONENT_REGISTRY,
340 )
341 .into()),
342 AppDataUpdateOperation::Update(payload) => {
343 let delta =
344 TlsMapDelta::<ComponentId, VLBytes>::tls_deserialize_exact(payload.as_slice())?;
345 validate_registry_delta(&delta)?;
346 expand_decoded_tls_map_delta(delta)
347 }
348 }
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate::app_data::component_permissions::component_permissions;
356 use prost::Message;
357 use xmtp_proto::xmtp::mls::message_contents::{
358 MetadataPolicy as MetadataPolicyProto,
359 metadata_policy::{Kind as MetadataPolicyKind, MetadataBasePolicy},
360 };
361
362 fn fixture_inbox_id(seed: u8) -> InboxId {
363 let mut bytes = [0u8; 32];
364 bytes[0] = seed;
365 InboxId::from_bytes(bytes)
366 }
367
368 fn valid_meta_bytes() -> Vec<u8> {
372 let allow = MetadataPolicyProto {
373 kind: Some(MetadataPolicyKind::Base(MetadataBasePolicy::Allow as i32)),
374 };
375 crate::app_data::component_registry::new_component_metadata(
376 component_permissions()
377 .insert(allow.clone())
378 .update(allow.clone())
379 .delete(allow)
380 .call(),
381 ComponentType::Bytes,
382 )
383 .encode_to_vec()
384 }
385
386 #[xmtp_common::test(unwrap_try = true)]
387 fn group_membership_round_trip_value() {
388 let mut map: TlsMap<InboxId, VLBytes> = TlsMap::new();
389 map.insert(fixture_inbox_id(1), VLBytes::new(b"member1-blob".to_vec()))
390 .unwrap();
391 let bytes = GroupMembershipComponent::encode_value(&map).unwrap();
392 let decoded = GroupMembershipComponent::decode_value(&bytes).unwrap();
393 assert_eq!(decoded.len(), 1);
394 assert_eq!(
395 decoded.get(&fixture_inbox_id(1)).unwrap().as_slice(),
396 b"member1-blob"
397 );
398 }
399
400 #[xmtp_common::test(unwrap_try = true)]
401 fn group_membership_apply_insert_against_empty() {
402 let delta = TlsMapDelta::<InboxId, VLBytes>::new()
403 .insert(fixture_inbox_id(2), VLBytes::new(b"v".to_vec()));
404 let payload = GroupMembershipComponent::encode_mutation(&delta).unwrap();
405 let new_bytes = GroupMembershipComponent::apply_update_payload(&payload, None).unwrap();
406 let new = GroupMembershipComponent::decode_value(&new_bytes).unwrap();
407 assert_eq!(new.len(), 1);
408 assert_eq!(new.get(&fixture_inbox_id(2)).unwrap().as_slice(), b"v");
409 }
410
411 #[xmtp_common::test(unwrap_try = true)]
412 fn group_membership_apply_update_against_existing() {
413 let mut prior_map: TlsMap<InboxId, VLBytes> = TlsMap::new();
414 prior_map
415 .insert(fixture_inbox_id(3), VLBytes::new(b"old".to_vec()))
416 .unwrap();
417 let prior_bytes = GroupMembershipComponent::encode_value(&prior_map).unwrap();
418
419 let delta = TlsMapDelta::<InboxId, VLBytes>::new()
420 .update(fixture_inbox_id(3), VLBytes::new(b"new".to_vec()));
421 let payload = GroupMembershipComponent::encode_mutation(&delta).unwrap();
422 let new_bytes =
423 GroupMembershipComponent::apply_update_payload(&payload, Some(&prior_bytes)).unwrap();
424 let new = GroupMembershipComponent::decode_value(&new_bytes).unwrap();
425 assert_eq!(new.get(&fixture_inbox_id(3)).unwrap().as_slice(), b"new");
426 }
427
428 #[xmtp_common::test(unwrap_try = true)]
429 fn group_membership_apply_batched_delta_atomically() {
430 let mut prior_map: TlsMap<InboxId, VLBytes> = TlsMap::new();
435 prior_map
436 .insert(fixture_inbox_id(1), VLBytes::new(b"seq=5".to_vec()))
437 .unwrap();
438 prior_map
439 .insert(fixture_inbox_id(3), VLBytes::new(b"to-remove".to_vec()))
440 .unwrap();
441 let prior_bytes = GroupMembershipComponent::encode_value(&prior_map).unwrap();
442
443 let delta = TlsMapDelta::<InboxId, VLBytes>::new()
444 .update(fixture_inbox_id(1), VLBytes::new(b"seq=7".to_vec()))
445 .insert(fixture_inbox_id(2), VLBytes::new(b"seq=1".to_vec()))
446 .delete(fixture_inbox_id(3));
447 let payload = GroupMembershipComponent::encode_mutation(&delta).unwrap();
448 let new_bytes =
449 GroupMembershipComponent::apply_update_payload(&payload, Some(&prior_bytes)).unwrap();
450 let new = GroupMembershipComponent::decode_value(&new_bytes).unwrap();
451
452 assert_eq!(new.len(), 2);
453 assert_eq!(new.get(&fixture_inbox_id(1)).unwrap().as_slice(), b"seq=7");
454 assert_eq!(new.get(&fixture_inbox_id(2)).unwrap().as_slice(), b"seq=1");
455 assert!(new.get(&fixture_inbox_id(3)).is_none());
456 }
457
458 #[xmtp_common::test(unwrap_try = true)]
459 fn group_membership_expand_insert_carries_value_bytes() {
460 let delta = TlsMapDelta::<InboxId, VLBytes>::new()
461 .insert(fixture_inbox_id(4), VLBytes::new(b"new-member".to_vec()));
462 let payload = GroupMembershipComponent::encode_mutation(&delta).unwrap();
463 let op = AppDataUpdateOperation::Update(payload.into());
464 let changes = GroupMembershipComponent::expand_to_changes(&op, None).unwrap();
465 assert_eq!(changes.len(), 1);
466 assert_eq!(changes[0].op, ComponentOp::Insert);
467 assert_eq!(changes[0].value.as_deref(), Some(&b"new-member"[..]));
468 }
469
470 #[xmtp_common::test(unwrap_try = true)]
471 fn group_membership_expand_delete_carries_key_bytes() {
472 let delta = TlsMapDelta::<InboxId, VLBytes>::new().delete(fixture_inbox_id(5));
473 let payload = GroupMembershipComponent::encode_mutation(&delta).unwrap();
474 let op = AppDataUpdateOperation::Update(payload.into());
475 let changes = GroupMembershipComponent::expand_to_changes(&op, None).unwrap();
476 assert_eq!(changes.len(), 1);
477 assert_eq!(changes[0].op, ComponentOp::Delete);
478 assert!(changes[0].value.is_some());
480 }
481
482 #[xmtp_common::test(unwrap_try = true)]
483 fn group_membership_expand_batched_yields_one_per_mutation() {
484 let delta = TlsMapDelta::<InboxId, VLBytes>::new()
485 .update(fixture_inbox_id(1), VLBytes::new(b"v1".to_vec()))
486 .insert(fixture_inbox_id(2), VLBytes::new(b"v2".to_vec()))
487 .delete(fixture_inbox_id(3));
488 let payload = GroupMembershipComponent::encode_mutation(&delta).unwrap();
489 let op = AppDataUpdateOperation::Update(payload.into());
490 let changes = GroupMembershipComponent::expand_to_changes(&op, None).unwrap();
491 assert_eq!(changes.len(), 3);
492 assert_eq!(changes[0].op, ComponentOp::Update);
493 assert_eq!(changes[1].op, ComponentOp::Insert);
494 assert_eq!(changes[2].op, ComponentOp::Delete);
495 }
496
497 #[xmtp_common::test(unwrap_try = true)]
498 fn component_registry_round_trip() {
499 let mut map: TlsMap<ComponentId, VLBytes> = TlsMap::new();
500 map.insert(
501 ComponentId::ADMIN_LIST,
502 VLBytes::new(b"meta-bytes".to_vec()),
503 )
504 .unwrap();
505 let bytes = ComponentRegistryComponent::encode_value(&map).unwrap();
506 let decoded = ComponentRegistryComponent::decode_value(&bytes).unwrap();
507 assert_eq!(decoded.len(), 1);
508 assert_eq!(
509 decoded.get(&ComponentId::ADMIN_LIST).unwrap().as_slice(),
510 b"meta-bytes"
511 );
512 }
513
514 #[xmtp_common::test(unwrap_try = true)]
515 fn component_registry_apply_update_replaces_metadata() {
516 let old_meta = valid_meta_bytes();
517 let mut new_meta = valid_meta_bytes();
518 new_meta = {
521 let mut decoded = xmtp_proto::xmtp::mls::message_contents::ComponentMetadata::decode(
522 new_meta.as_slice(),
523 )
524 .unwrap();
525 decoded.component_type = ComponentType::String as i32;
526 decoded.encode_to_vec()
527 };
528
529 let mut prior_map: TlsMap<ComponentId, VLBytes> = TlsMap::new();
530 prior_map
531 .insert(ComponentId::GROUP_NAME, VLBytes::new(old_meta))
532 .unwrap();
533 let prior_bytes = ComponentRegistryComponent::encode_value(&prior_map).unwrap();
534
535 let delta = TlsMapDelta::<ComponentId, VLBytes>::new()
536 .update(ComponentId::GROUP_NAME, VLBytes::new(new_meta.clone()));
537 let payload = ComponentRegistryComponent::encode_mutation(&delta).unwrap();
538 let new_bytes =
539 ComponentRegistryComponent::apply_update_payload(&payload, Some(&prior_bytes)).unwrap();
540 let new = ComponentRegistryComponent::decode_value(&new_bytes).unwrap();
541 assert_eq!(
542 new.get(&ComponentId::GROUP_NAME).unwrap().as_slice(),
543 new_meta.as_slice()
544 );
545 }
546
547 #[xmtp_common::test(unwrap_try = true)]
548 fn component_registry_apply_batched_delta_atomically() {
549 let delta = TlsMapDelta::<ComponentId, VLBytes>::new()
551 .insert(ComponentId::new(0xC100), VLBytes::new(valid_meta_bytes()))
552 .insert(ComponentId::new(0xC101), VLBytes::new(valid_meta_bytes()));
553 let payload = ComponentRegistryComponent::encode_mutation(&delta).unwrap();
554 let new_bytes = ComponentRegistryComponent::apply_update_payload(&payload, None).unwrap();
555 let new = ComponentRegistryComponent::decode_value(&new_bytes).unwrap();
556 assert_eq!(new.len(), 2);
557 assert!(new.get(&ComponentId::new(0xC100)).is_some());
558 assert!(new.get(&ComponentId::new(0xC101)).is_some());
559 }
560
561 fn assert_registry_delta_rejected_everywhere(delta: TlsMapDelta<ComponentId, VLBytes>) {
568 let payload = ComponentRegistryComponent::encode_mutation(&delta).unwrap();
569 let apply_err = ComponentRegistryComponent::apply_update_payload(&payload, None);
570 assert!(
571 matches!(apply_err, Err(ComponentTypedError::RegistryMutation(_))),
572 "apply must reject, got {apply_err:?}"
573 );
574 let op = AppDataUpdateOperation::Update(payload.into());
575 let expand_err = ComponentRegistryComponent::expand_to_changes(&op, None);
576 assert!(
577 matches!(expand_err, Err(ComponentTypedError::RegistryMutation(_))),
578 "expand must reject, got {expand_err:?}"
579 );
580 }
581
582 #[xmtp_common::test(unwrap_try = true)]
583 fn registry_delta_rejects_reserved_range_insert() {
584 assert_registry_delta_rejected_everywhere(
588 TlsMapDelta::<ComponentId, VLBytes>::new()
589 .insert(ComponentId::new(0xFF00), VLBytes::new(valid_meta_bytes())),
590 );
591 }
592
593 #[xmtp_common::test(unwrap_try = true)]
594 fn registry_delta_rejects_hardcoded_insert() {
595 assert_registry_delta_rejected_everywhere(
596 TlsMapDelta::<ComponentId, VLBytes>::new().insert(
597 ComponentId::SUPER_ADMIN_LIST,
598 VLBytes::new(valid_meta_bytes()),
599 ),
600 );
601 }
602
603 #[xmtp_common::test(unwrap_try = true)]
604 fn registry_delta_rejects_out_of_space_insert() {
605 assert_registry_delta_rejected_everywhere(
606 TlsMapDelta::<ComponentId, VLBytes>::new()
607 .insert(ComponentId::new(0x0001), VLBytes::new(valid_meta_bytes())),
608 );
609 }
610
611 #[xmtp_common::test(unwrap_try = true)]
612 fn registry_delta_rejects_undecodable_metadata_insert() {
613 assert_registry_delta_rejected_everywhere(
614 TlsMapDelta::<ComponentId, VLBytes>::new().insert(
615 ComponentId::new(0xC100),
616 VLBytes::new(vec![0xFF, 0xFF, 0xFF, 0xFF]),
617 ),
618 );
619 }
620
621 #[xmtp_common::test(unwrap_try = true)]
622 fn registry_delta_rejects_immutable_update_and_delete() {
623 assert_registry_delta_rejected_everywhere(
627 TlsMapDelta::<ComponentId, VLBytes>::new()
628 .update(ComponentId::new(0xBE00), VLBytes::new(valid_meta_bytes())),
629 );
630 assert_registry_delta_rejected_everywhere(
631 TlsMapDelta::<ComponentId, VLBytes>::new().delete(ComponentId::new(0xBE00)),
632 );
633 }
634
635 #[xmtp_common::test(unwrap_try = true)]
636 fn registry_delta_rejects_hardcoded_delete() {
637 assert_registry_delta_rejected_everywhere(
638 TlsMapDelta::<ComponentId, VLBytes>::new().delete(ComponentId::SUPER_ADMIN_LIST),
639 );
640 }
641
642 #[xmtp_common::test(unwrap_try = true)]
643 fn registry_component_rejects_whole_component_remove() {
644 let err =
648 ComponentRegistryComponent::expand_to_changes(&AppDataUpdateOperation::Remove, None);
649 assert!(
650 matches!(err, Err(ComponentTypedError::RegistryMutation(_))),
651 "Remove of the registry component must be rejected, got {err:?}"
652 );
653 }
654
655 #[xmtp_common::test(unwrap_try = true)]
656 fn registry_delta_valid_mutations_expand_and_apply() {
657 let delta = TlsMapDelta::<ComponentId, VLBytes>::new()
661 .insert(ComponentId::new(0xC200), VLBytes::new(valid_meta_bytes()));
662 let payload = ComponentRegistryComponent::encode_mutation(&delta).unwrap();
663
664 let op = AppDataUpdateOperation::Update(payload.clone().into());
665 let changes = ComponentRegistryComponent::expand_to_changes(&op, None).unwrap();
666 assert_eq!(changes.len(), 1);
667 assert_eq!(changes[0].op, ComponentOp::Insert);
668
669 let new_bytes = ComponentRegistryComponent::apply_update_payload(&payload, None).unwrap();
670 let new = ComponentRegistryComponent::decode_value(&new_bytes).unwrap();
671 assert!(new.get(&ComponentId::new(0xC200)).is_some());
672 }
673
674 #[xmtp_common::test(unwrap_try = true)]
675 fn apply_rejects_non_delta_payload() {
676 let mut map: TlsMap<InboxId, VLBytes> = TlsMap::new();
681 map.insert(fixture_inbox_id(1), VLBytes::new(b"v1".to_vec()))
682 .unwrap();
683 let raw_map_bytes = map.tls_serialize_detached().unwrap();
684 let err = GroupMembershipComponent::apply_update_payload(&raw_map_bytes, None).unwrap_err();
685 assert!(
686 matches!(err, ComponentTypedError::TlsCodec(_)),
687 "expected TlsCodec decode error for non-delta payload, got {err:?}"
688 );
689 }
690
691 #[xmtp_common::test(unwrap_try = true)]
692 fn component_types_are_correct() {
693 assert_eq!(
694 GroupMembershipComponent::COMPONENT_TYPE,
695 ComponentType::TlsMapInboxIdBytes
696 );
697 assert_eq!(
698 ComponentRegistryComponent::COMPONENT_TYPE,
699 ComponentType::TlsMapBytesBytes
700 );
701 }
702}