1use std::{
33 borrow::Cow,
34 collections::{BTreeMap, BTreeSet},
35};
36
37use openmls::{extensions::AppDataDictionary, messages::proposals::AppDataUpdateOperation};
38use tls_codec::{Deserialize, Serialize, Size, VLBytes};
39use xmtp_configuration::ApplicationComponentDefinition;
40use xmtp_proto::xmtp::mls::message_contents::{ComponentPermissions, ComponentType};
41
42use crate::{
43 app_data::{
44 component_id::ComponentId,
45 component_registry::ComponentRegistry,
46 component_source::{ComponentSourceError, apply_app_data_update_payload, component_type},
47 },
48 inbox_id::InboxId,
49 tls_map::{TlsMap, TlsMapDelta},
50 tls_set::{TlsKeyHash, TlsSet, TlsSetDelta},
51};
52
53#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub struct MetadataFieldRef {
62 pub component_id: ComponentId,
63 pub name: Option<Cow<'static, str>>,
64}
65
66impl MetadataFieldRef {
67 pub const GROUP_NAME: Self = Self::well_known(ComponentId::GROUP_NAME);
68 pub const GROUP_DESCRIPTION: Self = Self::well_known(ComponentId::GROUP_DESCRIPTION);
69 pub const GROUP_IMAGE_URL: Self = Self::well_known(ComponentId::GROUP_IMAGE_URL);
70 pub const GROUP_IMAGE: Self = Self::well_known(ComponentId::GROUP_IMAGE);
71 pub const APP_DATA: Self = Self::well_known(ComponentId::APP_DATA);
72 pub const USER_DISPLAY_NAME: Self = Self::well_known(ComponentId::USER_DISPLAY_NAME);
73 pub const MESSAGE_DISAPPEAR_FROM_NS: Self =
74 Self::well_known(ComponentId::MESSAGE_DISAPPEAR_FROM_NS);
75 pub const MESSAGE_DISAPPEAR_IN_NS: Self =
76 Self::well_known(ComponentId::MESSAGE_DISAPPEAR_IN_NS);
77
78 pub const PUBLIC_WELL_KNOWN: [Self; 8] = [
83 Self::GROUP_NAME,
84 Self::GROUP_DESCRIPTION,
85 Self::GROUP_IMAGE_URL,
86 Self::GROUP_IMAGE,
87 Self::APP_DATA,
88 Self::USER_DISPLAY_NAME,
89 Self::MESSAGE_DISAPPEAR_FROM_NS,
90 Self::MESSAGE_DISAPPEAR_IN_NS,
91 ];
92
93 pub const fn new(component_id: ComponentId) -> Self {
95 Self {
96 component_id,
97 name: None,
98 }
99 }
100
101 const fn well_known(id: ComponentId) -> Self {
104 let names = ComponentId::WELL_KNOWN_NAMES;
105 let mut i = 0;
106 while names[i].0.as_u16() != id.as_u16() {
107 i += 1;
108 }
109 Self {
110 component_id: id,
111 name: Some(Cow::Borrowed(names[i].1)),
112 }
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum MetadataScalarType {
119 Bytes,
120 String,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum MetadataKeyType {
126 Bytes,
127 InboxId,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum MetadataComponentType {
133 Bytes,
134 String,
135 Map {
136 key_type: MetadataKeyType,
137 value_type: MetadataScalarType,
138 },
139 Set {
140 key_type: MetadataKeyType,
141 },
142 Unknown {
145 tag: i32,
146 },
147}
148
149impl MetadataComponentType {
150 pub fn from_tag(tag: i32) -> Self {
152 use MetadataKeyType as K;
153 use MetadataScalarType as S;
154 match ComponentType::try_from(tag) {
155 Ok(ComponentType::Bytes) => Self::Bytes,
156 Ok(ComponentType::String) => Self::String,
157 Ok(ComponentType::TlsMapBytesBytes) => Self::map(K::Bytes, S::Bytes),
158 Ok(ComponentType::TlsMapInboxIdBytes) => Self::map(K::InboxId, S::Bytes),
159 Ok(ComponentType::TlsMapInboxIdString) => Self::map(K::InboxId, S::String),
160 Ok(ComponentType::TlsSetBytes) => Self::Set { key_type: K::Bytes },
161 Ok(ComponentType::TlsSetInboxId) => Self::Set {
162 key_type: K::InboxId,
163 },
164 Ok(ComponentType::Unspecified) | Err(_) => Self::Unknown { tag },
165 }
166 }
167
168 pub fn tag(self) -> Option<ComponentType> {
171 use MetadataKeyType as K;
172 use MetadataScalarType as S;
173 Some(match self {
174 Self::Bytes => ComponentType::Bytes,
175 Self::String => ComponentType::String,
176 Self::Map {
177 key_type: K::Bytes,
178 value_type: S::Bytes,
179 } => ComponentType::TlsMapBytesBytes,
180 Self::Map {
181 key_type: K::InboxId,
182 value_type: S::Bytes,
183 } => ComponentType::TlsMapInboxIdBytes,
184 Self::Map {
185 key_type: K::InboxId,
186 value_type: S::String,
187 } => ComponentType::TlsMapInboxIdString,
188 Self::Set { key_type: K::Bytes } => ComponentType::TlsSetBytes,
189 Self::Set {
190 key_type: K::InboxId,
191 } => ComponentType::TlsSetInboxId,
192 Self::Map {
193 key_type: K::Bytes,
194 value_type: S::String,
195 }
196 | Self::Unknown { .. } => return None,
197 })
198 }
199
200 const fn map(key_type: MetadataKeyType, value_type: MetadataScalarType) -> Self {
201 Self::Map {
202 key_type,
203 value_type,
204 }
205 }
206}
207
208#[derive(Debug, Clone, PartialEq)]
210pub struct MetadataFieldDescriptor {
211 pub field: MetadataFieldRef,
212 pub component_type: MetadataComponentType,
213 pub permissions: ComponentPermissions,
215}
216
217impl MetadataFieldDescriptor {
218 pub fn is_user_field(&self) -> bool {
221 matches!(
222 self.component_type,
223 MetadataComponentType::Map {
224 key_type: MetadataKeyType::InboxId,
225 ..
226 }
227 )
228 }
229
230 fn user_value_type(&self) -> Option<MetadataScalarType> {
232 match self.component_type {
233 MetadataComponentType::Map {
234 key_type: MetadataKeyType::InboxId,
235 value_type,
236 } => Some(value_type),
237 _ => None,
238 }
239 }
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
244pub enum FieldValue {
245 Bytes(Vec<u8>),
246 String(String),
247}
248
249#[derive(Debug, Clone, PartialEq, Eq)]
251pub enum FieldKey {
252 Bytes(Vec<u8>),
253 InboxId(InboxId),
254}
255
256#[derive(Debug, Clone, PartialEq, Eq)]
258pub enum MetadataValue {
259 Scalar(FieldValue),
260 Map(Vec<MapEntry>),
262 Set(Vec<FieldKey>),
264}
265
266#[derive(Debug, Clone, PartialEq, Eq)]
267pub struct MapEntry {
268 pub key: FieldKey,
269 pub value: FieldValue,
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
274pub struct MetadataFieldValue {
275 pub field: MetadataFieldRef,
276 pub value: Option<MetadataValue>,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
281pub struct UserFieldValue {
282 pub field: MetadataFieldRef,
283 pub value: FieldValue,
284}
285
286#[derive(Debug, Clone, PartialEq, Eq)]
288pub struct UserFieldUpdate {
289 pub field: MetadataFieldRef,
290 pub value: Option<FieldValue>,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq)]
295pub enum MapMutation {
296 Insert(FieldKey, FieldValue),
297 Update(FieldKey, FieldValue),
298 Delete(FieldKey),
299}
300
301#[derive(Debug, Clone, PartialEq, Eq)]
303pub enum SetMutation {
304 Insert(FieldKey),
305 Delete(FieldKey),
306 DeleteByHash([u8; 32]),
308}
309
310#[derive(Debug, Clone, PartialEq, Eq)]
314pub enum ComponentMutation {
315 Replace(FieldValue),
317 Remove,
319 MapDelta(Vec<MapMutation>),
320 SetDelta(Vec<SetMutation>),
321}
322
323#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct FieldWrite {
327 pub component_id: ComponentId,
328 pub component_type: ComponentType,
329 pub operation: WriteOperation,
330}
331
332#[derive(Debug, Clone, PartialEq, Eq)]
333pub enum WriteOperation {
334 Update(Vec<u8>),
336 Remove,
338 SetOwn(Vec<u8>),
341 ClearOwn,
343}
344
345#[derive(Debug, thiserror::Error)]
346pub enum FieldError {
347 #[error("no metadata field {0} in this group")]
349 UnknownField(ComponentId),
350 #[error("metadata field {0} is not a user field")]
351 NotUserField(ComponentId),
352 #[error("metadata field {0} appears more than once")]
353 DuplicateField(ComponentId),
354 #[error("metadata field {component_id} has unsupported type tag {tag}")]
355 UnsupportedType { component_id: ComponentId, tag: i32 },
356 #[error("value does not match the type of metadata field {0}")]
358 TypeMismatch(ComponentId),
359 #[error("metadata field {component_id} changed type from {expected:?} to {actual:?}")]
361 TypeChanged {
362 component_id: ComponentId,
363 expected: ComponentType,
364 actual: MetadataComponentType,
365 },
366 #[error("the group's policy denies this write to metadata field {0}")]
368 Denied(ComponentId),
369 #[error(transparent)]
370 Component(#[from] ComponentSourceError),
371}
372
373impl From<tls_codec::Error> for FieldError {
374 fn from(error: tls_codec::Error) -> Self {
375 ComponentSourceError::from(error).into()
376 }
377}
378
379pub struct FieldSnapshot<'a> {
381 dictionary: Option<&'a AppDataDictionary>,
382 registry: ComponentRegistry,
383 fields: Vec<MetadataFieldDescriptor>,
384}
385
386impl<'a> FieldSnapshot<'a> {
387 pub fn new(
395 dictionary: Option<&'a AppDataDictionary>,
396 catalogue: &[ApplicationComponentDefinition],
397 ) -> Result<Self, FieldError> {
398 let registry =
399 match dictionary.and_then(|d| d.get(&ComponentId::COMPONENT_REGISTRY.as_u16())) {
400 Some(bytes) => ComponentRegistry::from_bytes(bytes).map_err(|e| {
401 ComponentSourceError::MalformedComponentValue {
402 component_id: ComponentId::COMPONENT_REGISTRY,
403 reason: format!("registry decode: {e}"),
404 }
405 })?,
406 None => ComponentRegistry::new(),
407 };
408 let fields = registry
409 .iter()
410 .filter_map(Result::ok)
411 .filter_map(|(id, meta)| {
412 let (name, component_type) = if id.is_app_range() {
413 let name = catalogue
414 .iter()
415 .find(|d| d.component_id == id.as_u16())
416 .filter(|d| d.validate().is_ok())
417 .map(|d| Cow::Owned(d.name.clone()));
418 (name, MetadataComponentType::from_tag(meta.component_type))
419 } else {
420 let known = MetadataFieldRef::PUBLIC_WELL_KNOWN
421 .into_iter()
422 .find(|f| f.component_id == id)?;
423 (known.name, component_type(id)?.into())
424 };
425 Some(MetadataFieldDescriptor {
426 field: MetadataFieldRef {
427 component_id: id,
428 name,
429 },
430 component_type,
431 permissions: meta.permissions?,
432 })
433 })
434 .collect();
435 Ok(Self {
436 dictionary,
437 registry,
438 fields,
439 })
440 }
441
442 pub fn fields(&self) -> &[MetadataFieldDescriptor] {
444 &self.fields
445 }
446
447 pub fn field(&self, name: &str) -> Option<&MetadataFieldDescriptor> {
451 self.fields
452 .iter()
453 .find(|f| f.field.name.as_deref() == Some(name))
454 }
455
456 pub fn registry(&self) -> &ComponentRegistry {
458 &self.registry
459 }
460
461 pub fn resolve(
463 &self,
464 field: &MetadataFieldRef,
465 ) -> Result<&MetadataFieldDescriptor, FieldError> {
466 self.resolve_id(field.component_id)
467 }
468
469 fn resolve_id(&self, id: ComponentId) -> Result<&MetadataFieldDescriptor, FieldError> {
470 self.fields
471 .iter()
472 .find(|f| f.field.component_id == id)
473 .ok_or(FieldError::UnknownField(id))
474 }
475
476 pub fn value(&self, field: &MetadataFieldRef) -> Result<Option<MetadataValue>, FieldError> {
479 let descriptor = self.resolve(field)?;
480 let id = descriptor.field.component_id;
481 self.bytes(id)
482 .map(|bytes| decode(id, descriptor.component_type, bytes))
483 .transpose()
484 }
485
486 pub fn values(
490 &self,
491 fields: &[MetadataFieldRef],
492 ) -> Result<Vec<MetadataFieldValue>, FieldError> {
493 fields
494 .iter()
495 .map(|field| {
496 Ok(MetadataFieldValue {
497 field: self.resolve(field)?.field.clone(),
498 value: self.value(field)?,
499 })
500 })
501 .collect()
502 }
503
504 pub fn map_value(
507 &self,
508 field: &MetadataFieldRef,
509 key: &FieldKey,
510 ) -> Result<Option<FieldValue>, FieldError> {
511 let descriptor = self.resolve(field)?;
512 let id = descriptor.field.component_id;
513 match (descriptor.component_type, key) {
514 (MetadataComponentType::Unknown { tag }, _) => {
515 return Err(FieldError::UnsupportedType {
516 component_id: id,
517 tag,
518 });
519 }
520 (
521 MetadataComponentType::Map {
522 key_type: MetadataKeyType::Bytes,
523 ..
524 },
525 FieldKey::Bytes(_),
526 )
527 | (
528 MetadataComponentType::Map {
529 key_type: MetadataKeyType::InboxId,
530 ..
531 },
532 FieldKey::InboxId(_),
533 ) => {}
534 _ => return Err(FieldError::TypeMismatch(id)),
535 }
536 let Some(MetadataValue::Map(entries)) = self.value(field)? else {
537 return Ok(None);
538 };
539 Ok(entries
540 .into_iter()
541 .find(|entry| entry.key == *key)
542 .map(|entry| entry.value))
543 }
544
545 pub fn user_data(
554 &self,
555 fields: Option<&[MetadataFieldRef]>,
556 inbox_ids: Option<&[InboxId]>,
557 ) -> Result<BTreeMap<InboxId, Vec<UserFieldValue>>, FieldError> {
558 if let Some(id) =
559 fields.and_then(|refs| first_duplicate(refs.iter().map(|f| f.component_id)))
560 {
561 return Err(FieldError::DuplicateField(id));
562 }
563 let fields = match fields {
564 None => self.fields.iter().filter(|f| f.is_user_field()).collect(),
565 Some(refs) => refs
566 .iter()
567 .map(|field| {
568 let descriptor = self.resolve(field)?;
569 if descriptor.is_user_field() {
570 Ok(descriptor)
571 } else {
572 Err(FieldError::NotUserField(field.component_id))
573 }
574 })
575 .collect::<Result<Vec<_>, _>>()?,
576 };
577 let mut data: BTreeMap<InboxId, Vec<UserFieldValue>> = match inbox_ids {
578 Some(ids) => ids.iter().map(|id| (*id, Vec::new())).collect(),
579 None => self.default_inboxes()?.map(|id| (id, Vec::new())).collect(),
580 };
581 for descriptor in fields {
582 let entries = match self.value(&descriptor.field) {
586 Ok(Some(MetadataValue::Map(entries))) => entries,
587 Ok(_) => continue,
588 Err(error) => {
589 tracing::warn!(field = %descriptor.field.component_id, %error, "skipping undecodable user field");
590 continue;
591 }
592 };
593 for MapEntry { key, value } in entries {
594 if let FieldKey::InboxId(inbox) = key
595 && let Some(values) = data.get_mut(&inbox)
596 {
597 values.push(UserFieldValue {
598 field: descriptor.field.clone(),
599 value,
600 });
601 }
602 }
603 }
604 Ok(data)
605 }
606
607 fn default_inboxes(&self) -> Result<impl Iterator<Item = InboxId>, FieldError> {
608 let inboxes: Vec<InboxId> = match self.bytes(ComponentId::DM_MEMBERS) {
609 Some(bytes) => TlsSet::<InboxId>::tls_deserialize_exact(bytes)
610 .map_err(|e| malformed(ComponentId::DM_MEMBERS, e))?
611 .into_iter()
612 .collect(),
613 None => self
614 .bytes(ComponentId::GROUP_MEMBERSHIP)
615 .map(TlsMap::<InboxId, VLBytes>::tls_deserialize_exact)
616 .transpose()
617 .map_err(|e| malformed(ComponentId::GROUP_MEMBERSHIP, e))?
618 .into_iter()
619 .flat_map(|map| map.into_iter().map(|(inbox, _)| inbox))
620 .collect(),
621 };
622 Ok(inboxes.into_iter())
623 }
624
625 pub fn field_write(
628 &self,
629 field: &MetadataFieldRef,
630 mutation: &ComponentMutation,
631 ) -> Result<FieldWrite, FieldError> {
632 use MetadataComponentType as T;
633 use MetadataKeyType as K;
634 let descriptor = self.resolve(field)?;
635 let id = descriptor.field.component_id;
636 let ty = descriptor.component_type;
637 let component_type = wire_type(id, ty)?;
638 let payload = match (ty, mutation) {
639 (_, ComponentMutation::Remove) => {
640 return Ok(FieldWrite {
641 component_id: id,
642 component_type,
643 operation: WriteOperation::Remove,
644 });
645 }
646 (T::Bytes, ComponentMutation::Replace(value)) => {
647 scalar(MetadataScalarType::Bytes, value).map(|b| Ok(b.to_vec()))
648 }
649 (T::String, ComponentMutation::Replace(value)) => {
650 scalar(MetadataScalarType::String, value).map(|b| Ok(b.to_vec()))
651 }
652 (
653 T::Map {
654 key_type: K::Bytes,
655 value_type,
656 },
657 ComponentMutation::MapDelta(mutations),
658 ) => map_delta(mutations, value_type, bytes_key).map(|d| d.tls_serialize_detached()),
659 (
660 T::Map {
661 key_type: K::InboxId,
662 value_type,
663 },
664 ComponentMutation::MapDelta(mutations),
665 ) => map_delta(mutations, value_type, inbox_key).map(|d| d.tls_serialize_detached()),
666 (T::Set { key_type: K::Bytes }, ComponentMutation::SetDelta(mutations)) => {
667 set_delta(mutations, bytes_key).map(|d| d.tls_serialize_detached())
668 }
669 (
670 T::Set {
671 key_type: K::InboxId,
672 },
673 ComponentMutation::SetDelta(mutations),
674 ) => set_delta(mutations, inbox_key).map(|d| d.tls_serialize_detached()),
675 _ => None,
676 }
677 .ok_or(FieldError::TypeMismatch(id))??;
678 Ok(FieldWrite {
679 component_id: id,
680 component_type,
681 operation: WriteOperation::Update(payload),
682 })
683 }
684
685 pub fn user_data_writes(
688 &self,
689 values: &[UserFieldUpdate],
690 ) -> Result<Vec<FieldWrite>, FieldError> {
691 if let Some(id) = first_duplicate(values.iter().map(|v| v.field.component_id)) {
692 return Err(FieldError::DuplicateField(id));
693 }
694 values
695 .iter()
696 .map(|UserFieldUpdate { field, value }| {
697 let descriptor = self.resolve(field)?;
698 let id = descriptor.field.component_id;
699 let value_type = descriptor
700 .user_value_type()
701 .ok_or(FieldError::NotUserField(id))?;
702 let operation = match value {
703 Some(value) => WriteOperation::SetOwn(
704 scalar(value_type, value)
705 .ok_or(FieldError::TypeMismatch(id))?
706 .to_vec(),
707 ),
708 None => WriteOperation::ClearOwn,
709 };
710 Ok(FieldWrite {
711 component_id: id,
712 component_type: wire_type(id, descriptor.component_type)?,
713 operation,
714 })
715 })
716 .collect()
717 }
718
719 pub fn resolve_writes(
732 &self,
733 values: Option<&AppDataDictionary>,
734 own: InboxId,
735 writes: &[FieldWrite],
736 ) -> Result<Vec<(ComponentId, AppDataUpdateOperation)>, FieldError> {
737 self.operations(values, own, writes, false)
738 }
739
740 pub fn write_operations(
745 &self,
746 values: Option<&AppDataDictionary>,
747 own: InboxId,
748 writes: &[FieldWrite],
749 ) -> Result<Vec<(ComponentId, AppDataUpdateOperation)>, FieldError> {
750 self.operations(values, own, writes, true)
751 }
752
753 fn operations(
754 &self,
755 values: Option<&AppDataDictionary>,
756 own: InboxId,
757 writes: &[FieldWrite],
758 keep_unchanged: bool,
759 ) -> Result<Vec<(ComponentId, AppDataUpdateOperation)>, FieldError> {
760 if let Some(id) = first_duplicate(writes.iter().map(|w| w.component_id)) {
761 return Err(FieldError::DuplicateField(id));
762 }
763 writes
764 .iter()
765 .filter_map(|write| {
766 self.resolve_write(values, own, write, keep_unchanged)
767 .map(|op| op.map(|op| (write.component_id, op)))
768 .transpose()
769 })
770 .collect()
771 }
772
773 fn resolve_write(
774 &self,
775 values: Option<&AppDataDictionary>,
776 own: InboxId,
777 write: &FieldWrite,
778 keep_unchanged: bool,
779 ) -> Result<Option<AppDataUpdateOperation>, FieldError> {
780 let id = write.component_id;
781 let descriptor = self.resolve_id(id)?;
782 let actual = descriptor.component_type;
783 if actual.tag() != Some(write.component_type) {
784 return Err(FieldError::TypeChanged {
785 component_id: id,
786 expected: write.component_type,
787 actual,
788 });
789 }
790 let own_key = matches!(
791 write.operation,
792 WriteOperation::SetOwn(_) | WriteOperation::ClearOwn
793 );
794 if own_key && !descriptor.is_user_field() {
795 return Err(FieldError::TypeMismatch(id));
796 }
797 let current = values.and_then(|d| d.get(&id.as_u16()));
798 let owned = || -> Result<bool, FieldError> {
799 Ok(current
800 .map(TlsMap::<InboxId, VLBytes>::tls_deserialize_exact)
801 .transpose()
802 .map_err(|e| malformed(id, e))?
803 .is_some_and(|map| map.contains_key(&own)))
804 };
805 let delta = TlsMapDelta::<InboxId, VLBytes>::new();
806 let payload = match &write.operation {
807 WriteOperation::Remove => return Ok(current.map(|_| AppDataUpdateOperation::Remove)),
808 WriteOperation::Update(payload) => payload.clone(),
809 WriteOperation::SetOwn(value) if owned()? => delta
810 .update(own, value.as_slice().into())
811 .tls_serialize_detached()?,
812 WriteOperation::SetOwn(value) => delta
813 .insert(own, value.as_slice().into())
814 .tls_serialize_detached()?,
815 WriteOperation::ClearOwn if owned()? => delta.delete(own).tls_serialize_detached()?,
816 WriteOperation::ClearOwn => return Ok(None),
817 };
818 let next = apply_app_data_update_payload(id, &payload, current, &self.registry)?;
819 Ok((keep_unchanged || current != Some(next.as_slice()))
820 .then(|| AppDataUpdateOperation::Update(payload.into())))
821 }
822
823 fn bytes(&self, id: ComponentId) -> Option<&'a [u8]> {
824 self.dictionary.and_then(|d| d.get(&id.as_u16()))
825 }
826}
827
828impl From<ComponentType> for MetadataComponentType {
829 fn from(ty: ComponentType) -> Self {
830 Self::from_tag(ty as i32)
831 }
832}
833
834fn wire_type(id: ComponentId, ty: MetadataComponentType) -> Result<ComponentType, FieldError> {
836 ty.tag().ok_or(FieldError::UnsupportedType {
837 component_id: id,
838 tag: match ty {
839 MetadataComponentType::Unknown { tag } => tag,
840 _ => ComponentType::Unspecified as i32,
841 },
842 })
843}
844
845fn decode(
846 id: ComponentId,
847 ty: MetadataComponentType,
848 bytes: &[u8],
849) -> Result<MetadataValue, FieldError> {
850 use MetadataComponentType as T;
851 use MetadataKeyType as K;
852 let value = |ty, bytes: VLBytes| field_value(id, ty, bytes.into());
853 let tls = |e| malformed(id, e);
854 Ok(match ty {
855 T::Bytes => {
856 MetadataValue::Scalar(field_value(id, MetadataScalarType::Bytes, bytes.into())?)
857 }
858 T::String => {
859 MetadataValue::Scalar(field_value(id, MetadataScalarType::String, bytes.into())?)
860 }
861 T::Map {
862 key_type: K::Bytes,
863 value_type,
864 } => MetadataValue::Map(
865 TlsMap::<VLBytes, VLBytes>::tls_deserialize_exact(bytes)
866 .map_err(tls)?
867 .into_iter()
868 .map(|(key, v)| {
869 Ok(MapEntry {
870 key: FieldKey::Bytes(key.into()),
871 value: value(value_type, v)?,
872 })
873 })
874 .collect::<Result<_, FieldError>>()?,
875 ),
876 T::Map {
877 key_type: K::InboxId,
878 value_type,
879 } => MetadataValue::Map(
880 TlsMap::<InboxId, VLBytes>::tls_deserialize_exact(bytes)
881 .map_err(tls)?
882 .into_iter()
883 .map(|(key, v)| {
884 Ok(MapEntry {
885 key: FieldKey::InboxId(key),
886 value: value(value_type, v)?,
887 })
888 })
889 .collect::<Result<_, FieldError>>()?,
890 ),
891 T::Set { key_type: K::Bytes } => MetadataValue::Set(
892 TlsSet::<VLBytes>::tls_deserialize_exact(bytes)
893 .map_err(tls)?
894 .into_iter()
895 .map(|key| FieldKey::Bytes(key.into()))
896 .collect(),
897 ),
898 T::Set {
899 key_type: K::InboxId,
900 } => MetadataValue::Set(
901 TlsSet::<InboxId>::tls_deserialize_exact(bytes)
902 .map_err(tls)?
903 .into_iter()
904 .map(FieldKey::InboxId)
905 .collect(),
906 ),
907 T::Unknown { tag } => {
908 return Err(FieldError::UnsupportedType {
909 component_id: id,
910 tag,
911 });
912 }
913 })
914}
915
916fn field_value(
917 id: ComponentId,
918 ty: MetadataScalarType,
919 bytes: Vec<u8>,
920) -> Result<FieldValue, FieldError> {
921 Ok(match ty {
922 MetadataScalarType::Bytes => FieldValue::Bytes(bytes),
923 MetadataScalarType::String => {
924 FieldValue::String(String::from_utf8(bytes).map_err(|e| {
925 ComponentSourceError::MalformedComponentValue {
926 component_id: id,
927 reason: format!("invalid UTF-8: {e}"),
928 }
929 })?)
930 }
931 })
932}
933
934fn malformed(id: ComponentId, error: tls_codec::Error) -> FieldError {
935 ComponentSourceError::MalformedComponentValue {
936 component_id: id,
937 reason: format!("TLS decode: {error}"),
938 }
939 .into()
940}
941
942fn scalar(ty: MetadataScalarType, value: &FieldValue) -> Option<&[u8]> {
944 match (ty, value) {
945 (MetadataScalarType::Bytes, FieldValue::Bytes(bytes)) => Some(bytes),
946 (MetadataScalarType::String, FieldValue::String(s)) => Some(s.as_bytes()),
947 _ => None,
948 }
949}
950
951fn bytes_key(key: &FieldKey) -> Option<VLBytes> {
952 match key {
953 FieldKey::Bytes(bytes) => Some(bytes.as_slice().into()),
954 FieldKey::InboxId(_) => None,
955 }
956}
957
958fn inbox_key(key: &FieldKey) -> Option<InboxId> {
959 match key {
960 FieldKey::InboxId(inbox) => Some(*inbox),
961 FieldKey::Bytes(_) => None,
962 }
963}
964
965fn map_delta<K>(
967 mutations: &[MapMutation],
968 value_type: MetadataScalarType,
969 key: fn(&FieldKey) -> Option<K>,
970) -> Option<TlsMapDelta<K, VLBytes>> {
971 mutations
972 .iter()
973 .try_fold(TlsMapDelta::new(), |delta, mutation| {
974 Some(match mutation {
975 MapMutation::Insert(k, v) => delta.insert(key(k)?, scalar(value_type, v)?.into()),
976 MapMutation::Update(k, v) => delta.update(key(k)?, scalar(value_type, v)?.into()),
977 MapMutation::Delete(k) => delta.delete(key(k)?),
978 })
979 })
980}
981
982fn set_delta<K: Serialize + Size>(
984 mutations: &[SetMutation],
985 key: fn(&FieldKey) -> Option<K>,
986) -> Option<TlsSetDelta<K>> {
987 mutations
988 .iter()
989 .try_fold(TlsSetDelta::new(), |delta, mutation| {
990 Some(match mutation {
991 SetMutation::Insert(k) => delta.insert(key(k)?),
992 SetMutation::Delete(k) => delta.remove(key(k)?),
993 SetMutation::DeleteByHash(hash) => {
994 delta.remove_by_hash(TlsKeyHash::from_bytes(*hash))
995 }
996 })
997 })
998}
999
1000fn first_duplicate(ids: impl IntoIterator<Item = ComponentId>) -> Option<ComponentId> {
1001 let mut seen = BTreeSet::new();
1002 ids.into_iter().find(|id| !seen.insert(*id))
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007 use prost::Message;
1008 use xmtp_configuration::{
1009 ComponentPermissions as CataloguePermissions, MetadataPolicy as CataloguePolicy,
1010 };
1011 use xmtp_proto::xmtp::mls::message_contents::{
1012 ComponentMetadata, MetadataPolicy,
1013 metadata_policy::{Kind, MetadataBasePolicy},
1014 };
1015
1016 use super::*;
1017
1018 const STATUS: ComponentId = ComponentId::new(0xC001);
1019 const SCORES: ComponentId = ComponentId::new(0xC002);
1020 const FUTURE: ComponentId = ComponentId::new(0xC003);
1021 const BROKEN: ComponentId = ComponentId::new(0xC004);
1022 const TAGS: ComponentId = ComponentId::new(0xC005);
1023 const LINKS: ComponentId = ComponentId::new(0xC006);
1024 const PARTIAL: ComponentId = ComponentId::new(0xC007);
1025
1026 fn inbox(tag: u8) -> InboxId {
1027 InboxId::from_bytes([tag; 32])
1028 }
1029
1030 fn policy(base: MetadataBasePolicy) -> Option<MetadataPolicy> {
1031 Some(MetadataPolicy {
1032 kind: Some(Kind::Base(base as i32)),
1033 })
1034 }
1035
1036 fn permissions(base: MetadataBasePolicy) -> ComponentPermissions {
1037 ComponentPermissions {
1038 insert_policy: policy(base),
1039 update_policy: policy(base),
1040 delete_policy: policy(base),
1041 }
1042 }
1043
1044 fn entry(tag: i32) -> Vec<u8> {
1046 ComponentMetadata {
1047 permissions: Some(permissions(MetadataBasePolicy::Allow)),
1048 component_type: tag,
1049 external_committer_permissions: None,
1050 }
1051 .encode_to_vec()
1052 }
1053
1054 fn inbox_map(entries: &[(InboxId, &[u8])]) -> Vec<u8> {
1055 let mut map = TlsMap::<InboxId, VLBytes>::new();
1056 for (key, value) in entries {
1057 map.set(*key, value.to_vec().into());
1058 }
1059 map.tls_serialize_detached().unwrap()
1060 }
1061
1062 fn inbox_set(inboxes: &[InboxId]) -> Vec<u8> {
1063 TlsSet::from_iter(inboxes.iter().copied())
1064 .tls_serialize_detached()
1065 .unwrap()
1066 }
1067
1068 fn dictionary(
1071 registry: &[(ComponentId, Vec<u8>)],
1072 values: &[(ComponentId, Vec<u8>)],
1073 ) -> AppDataDictionary {
1074 let mut entries = TlsMap::<ComponentId, VLBytes>::new();
1075 for (id, raw) in registry {
1076 entries.set(*id, raw.clone().into());
1077 }
1078 let mut dictionary = AppDataDictionary::new();
1079 dictionary.insert(
1080 ComponentId::COMPONENT_REGISTRY.as_u16(),
1081 entries.tls_serialize_detached().unwrap(),
1082 );
1083 for (id, value) in values {
1084 dictionary.insert(id.as_u16(), value.clone());
1085 }
1086 dictionary
1087 }
1088
1089 fn tag(ty: ComponentType) -> i32 {
1090 ty as i32
1091 }
1092
1093 fn group() -> AppDataDictionary {
1095 dictionary(
1096 &[
1097 (
1098 ComponentId::GROUP_MEMBERSHIP,
1099 entry(tag(ComponentType::TlsMapInboxIdBytes)),
1100 ),
1101 (ComponentId::GROUP_NAME, entry(tag(ComponentType::String))),
1102 (
1103 ComponentId::GROUP_DESCRIPTION,
1104 entry(tag(ComponentType::String)),
1105 ),
1106 (
1107 ComponentId::MIN_SUPPORTED_PROTOCOL_VERSION,
1108 entry(tag(ComponentType::String)),
1109 ),
1110 (
1112 ComponentId::USER_DISPLAY_NAME,
1113 entry(tag(ComponentType::Bytes)),
1114 ),
1115 (STATUS, entry(tag(ComponentType::String))),
1116 (SCORES, entry(tag(ComponentType::TlsMapInboxIdBytes))),
1117 (FUTURE, entry(99)),
1118 (BROKEN, vec![0xFF, 0xFF]),
1119 (TAGS, entry(tag(ComponentType::TlsSetInboxId))),
1120 (LINKS, entry(tag(ComponentType::TlsMapBytesBytes))),
1121 (
1123 PARTIAL,
1124 ComponentMetadata {
1125 permissions: Some(ComponentPermissions {
1126 delete_policy: None,
1127 ..permissions(MetadataBasePolicy::Allow)
1128 }),
1129 component_type: tag(ComponentType::String),
1130 external_committer_permissions: None,
1131 }
1132 .encode_to_vec(),
1133 ),
1134 ],
1135 &[
1136 (
1137 ComponentId::GROUP_MEMBERSHIP,
1138 inbox_map(&[(inbox(0xA), b""), (inbox(0xB), b"")]),
1139 ),
1140 (ComponentId::GROUP_NAME, b"Team".to_vec()),
1141 (
1142 ComponentId::USER_DISPLAY_NAME,
1143 inbox_map(&[(inbox(0xA), b"Alix"), (inbox(0xC), b"Carol")]),
1144 ),
1145 (SCORES, inbox_map(&[(inbox(0xA), &[7])])),
1146 (FUTURE, vec![9]),
1147 (BROKEN, vec![1, 2, 3]),
1148 (TAGS, inbox_set(&[inbox(0xB)])),
1149 ],
1150 )
1151 }
1152
1153 fn catalogue() -> Vec<ApplicationComponentDefinition> {
1158 let allow = Some(CataloguePolicy::Base(MetadataBasePolicy::Allow as i32));
1159 let definition = |id: ComponentId, name: &str| ApplicationComponentDefinition {
1160 component_id: id.as_u16(),
1161 name: name.into(),
1162 component_type: tag(ComponentType::Bytes),
1163 permissions: CataloguePermissions {
1164 insert: allow.clone(),
1165 update: allow.clone(),
1166 delete: allow.clone(),
1167 },
1168 in_groups: true,
1169 in_dms: true,
1170 };
1171 vec![
1172 definition(STATUS, "GROUP_NAME"),
1173 definition(SCORES, ""),
1174 definition(SCORES, "scores"),
1175 ]
1176 }
1177
1178 fn snapshot(dictionary: &AppDataDictionary) -> FieldSnapshot<'_> {
1179 FieldSnapshot::new(Some(dictionary), &catalogue()).unwrap()
1180 }
1181
1182 fn named(id: ComponentId, name: &'static str) -> MetadataFieldRef {
1183 MetadataFieldRef {
1184 component_id: id,
1185 name: Some(Cow::Borrowed(name)),
1186 }
1187 }
1188
1189 fn string(s: &str) -> FieldValue {
1190 FieldValue::String(s.into())
1191 }
1192
1193 #[xmtp_common::test(unwrap_try = true)]
1201 fn fields_describe_the_committed_registry() {
1202 use MetadataComponentType as T;
1203 let dictionary = group();
1204 let fields = snapshot(&dictionary);
1205 let described: Vec<_> = fields
1206 .fields()
1207 .iter()
1208 .map(|f| (f.field.clone(), f.component_type, f.is_user_field()))
1209 .collect();
1210 let string_map = T::Map {
1211 key_type: MetadataKeyType::InboxId,
1212 value_type: MetadataScalarType::String,
1213 };
1214 let bytes_map = T::Map {
1215 key_type: MetadataKeyType::InboxId,
1216 value_type: MetadataScalarType::Bytes,
1217 };
1218 assert_eq!(
1219 described,
1220 vec![
1221 (MetadataFieldRef::GROUP_NAME, T::String, false),
1222 (MetadataFieldRef::GROUP_DESCRIPTION, T::String, false),
1223 (MetadataFieldRef::USER_DISPLAY_NAME, string_map, true),
1224 (named(STATUS, "GROUP_NAME"), T::String, false),
1225 (MetadataFieldRef::new(SCORES), bytes_map, true),
1226 (MetadataFieldRef::new(FUTURE), T::Unknown { tag: 99 }, false),
1227 (
1228 MetadataFieldRef::new(TAGS),
1229 T::Set {
1230 key_type: MetadataKeyType::InboxId
1231 },
1232 false
1233 ),
1234 (
1235 MetadataFieldRef::new(LINKS),
1236 T::Map {
1237 key_type: MetadataKeyType::Bytes,
1238 value_type: MetadataScalarType::Bytes
1239 },
1240 false
1241 ),
1242 ]
1243 );
1244 assert!(
1245 fields
1246 .fields()
1247 .iter()
1248 .all(|f| f.permissions == permissions(MetadataBasePolicy::Allow))
1249 );
1250 assert!(
1251 FieldSnapshot::new(None, &catalogue())
1252 .unwrap()
1253 .fields()
1254 .is_empty()
1255 );
1256 }
1257
1258 #[xmtp_common::test(unwrap_try = true)]
1262 fn permissions_come_from_the_registry() {
1263 let mut raw =
1264 ComponentMetadata::decode(entry(tag(ComponentType::String)).as_slice()).unwrap();
1265 raw.permissions = Some(permissions(MetadataBasePolicy::AllowIfAdmin));
1266 let dictionary = dictionary(&[(STATUS, raw.encode_to_vec())], &[]);
1267 let fields = snapshot(&dictionary);
1268 assert_eq!(
1269 fields.fields()[0].permissions,
1270 permissions(MetadataBasePolicy::AllowIfAdmin)
1271 );
1272 }
1273
1274 #[xmtp_common::test(unwrap_try = true)]
1278 fn name_lookup_prefers_the_well_known_field() {
1279 let dictionary = group();
1280 let fields = snapshot(&dictionary);
1281 assert_eq!(
1282 fields.field("GROUP_NAME").map(|f| &f.field),
1283 Some(&MetadataFieldRef::GROUP_NAME)
1284 );
1285 assert_eq!(
1286 fields.field("USER_DISPLAY_NAME").map(|f| &f.field),
1287 Some(&MetadataFieldRef::USER_DISPLAY_NAME)
1288 );
1289 assert!(fields.field("MIN_SUPPORTED_PROTOCOL_VERSION").is_none());
1290 assert!(fields.field("GROUP_IMAGE").is_none());
1291 assert!(fields.field("nothing").is_none());
1292 }
1293
1294 #[xmtp_common::test(unwrap_try = true)]
1298 fn refs_resolve_by_id() {
1299 let dictionary = group();
1300 let fields = snapshot(&dictionary);
1301 let team = Some(MetadataValue::Scalar(string("Team")));
1302 for field in [
1303 MetadataFieldRef::GROUP_NAME,
1304 MetadataFieldRef::new(ComponentId::GROUP_NAME),
1305 named(ComponentId::GROUP_NAME, "STATUS"),
1306 ] {
1307 assert_eq!(fields.value(&field).unwrap(), team);
1308 assert_eq!(
1309 fields.values(std::slice::from_ref(&field)).unwrap(),
1310 vec![MetadataFieldValue {
1311 field: MetadataFieldRef::GROUP_NAME,
1312 value: team.clone(),
1313 }]
1314 );
1315 }
1316 assert_eq!(fields.value(&named(STATUS, "GROUP_NAME")).unwrap(), None);
1319 }
1320
1321 #[xmtp_common::test(unwrap_try = true)]
1326 fn values_are_typed_and_in_request_order() {
1327 let dictionary = group();
1328 let fields = snapshot(&dictionary);
1329 let batch = fields
1330 .values(&[
1331 MetadataFieldRef::new(SCORES),
1332 MetadataFieldRef::GROUP_DESCRIPTION,
1333 MetadataFieldRef::new(TAGS),
1334 MetadataFieldRef::USER_DISPLAY_NAME,
1335 ])
1336 .unwrap();
1337 let values: Vec<_> = batch
1338 .into_iter()
1339 .map(|v| (v.field.component_id, v.value))
1340 .collect();
1341 assert_eq!(
1342 values,
1343 vec![
1344 (
1345 SCORES,
1346 Some(MetadataValue::Map(vec![MapEntry {
1347 key: FieldKey::InboxId(inbox(0xA)),
1348 value: FieldValue::Bytes(vec![7]),
1349 }]))
1350 ),
1351 (ComponentId::GROUP_DESCRIPTION, None),
1352 (
1353 TAGS,
1354 Some(MetadataValue::Set(vec![FieldKey::InboxId(inbox(0xB))]))
1355 ),
1356 (
1357 ComponentId::USER_DISPLAY_NAME,
1358 Some(MetadataValue::Map(vec![
1359 MapEntry {
1360 key: FieldKey::InboxId(inbox(0xA)),
1361 value: string("Alix"),
1362 },
1363 MapEntry {
1364 key: FieldKey::InboxId(inbox(0xC)),
1365 value: string("Carol"),
1366 },
1367 ]))
1368 ),
1369 ]
1370 );
1371 assert!(fields.values(&[]).unwrap().is_empty());
1372 for (field, id) in [
1373 (MetadataFieldRef::new(BROKEN), BROKEN),
1374 (MetadataFieldRef::GROUP_IMAGE, ComponentId::GROUP_IMAGE),
1375 (
1376 MetadataFieldRef::new(ComponentId::MIN_SUPPORTED_PROTOCOL_VERSION),
1377 ComponentId::MIN_SUPPORTED_PROTOCOL_VERSION,
1378 ),
1379 ] {
1380 assert!(matches!(
1381 fields.values(&[MetadataFieldRef::GROUP_NAME, field]),
1382 Err(FieldError::UnknownField(unknown)) if unknown == id
1383 ));
1384 }
1385 assert!(matches!(
1386 fields.value(&MetadataFieldRef::new(FUTURE)),
1387 Err(FieldError::UnsupportedType {
1388 component_id: FUTURE,
1389 tag: 99
1390 })
1391 ));
1392 }
1393
1394 #[xmtp_common::test(unwrap_try = true)]
1398 fn map_value_reads_one_key() {
1399 let dictionary = group();
1400 let fields = snapshot(&dictionary);
1401 let names = MetadataFieldRef::USER_DISPLAY_NAME;
1402 assert_eq!(
1403 fields
1404 .map_value(&names, &FieldKey::InboxId(inbox(0xC)))
1405 .unwrap(),
1406 Some(string("Carol"))
1407 );
1408 assert_eq!(
1409 fields
1410 .map_value(&names, &FieldKey::InboxId(inbox(0xB)))
1411 .unwrap(),
1412 None
1413 );
1414 assert_eq!(
1415 fields
1416 .map_value(&MetadataFieldRef::new(LINKS), &FieldKey::Bytes(vec![1]))
1417 .unwrap(),
1418 None
1419 );
1420 for (field, key) in [
1421 (names.clone(), FieldKey::Bytes(vec![0xA; 32])),
1422 (MetadataFieldRef::new(LINKS), FieldKey::InboxId(inbox(0xA))),
1423 (MetadataFieldRef::GROUP_NAME, FieldKey::Bytes(vec![])),
1424 (MetadataFieldRef::new(TAGS), FieldKey::InboxId(inbox(0xB))),
1425 ] {
1426 assert!(matches!(
1427 fields.map_value(&field, &key),
1428 Err(FieldError::TypeMismatch(id)) if id == field.component_id
1429 ));
1430 }
1431 assert!(matches!(
1432 fields.map_value(&MetadataFieldRef::new(FUTURE), &FieldKey::Bytes(vec![])),
1433 Err(FieldError::UnsupportedType { .. })
1434 ));
1435 }
1436
1437 #[xmtp_common::test(unwrap_try = true)]
1443 fn user_data_selects_fields_and_inboxes() {
1444 let dictionary = group();
1445 let fields = snapshot(&dictionary);
1446 let value = |field: &MetadataFieldRef, value| UserFieldValue {
1447 field: field.clone(),
1448 value,
1449 };
1450 let scores = MetadataFieldRef::new(SCORES);
1451 let names = MetadataFieldRef::USER_DISPLAY_NAME;
1452 assert_eq!(
1453 fields.user_data(None, None).unwrap(),
1454 BTreeMap::from([
1455 (
1456 inbox(0xA),
1457 vec![
1458 value(&names, string("Alix")),
1459 value(&scores, FieldValue::Bytes(vec![7])),
1460 ]
1461 ),
1462 (inbox(0xB), vec![]),
1463 ])
1464 );
1465 assert_eq!(
1466 fields
1467 .user_data(
1468 Some(std::slice::from_ref(&scores)),
1469 Some(&[inbox(0xC), inbox(0xA)])
1470 )
1471 .unwrap(),
1472 BTreeMap::from([
1473 (inbox(0xA), vec![value(&scores, FieldValue::Bytes(vec![7]))]),
1474 (inbox(0xC), vec![]),
1475 ])
1476 );
1477 assert_eq!(
1478 fields.user_data(None, Some(&[inbox(0xC)])).unwrap(),
1479 BTreeMap::from([(inbox(0xC), vec![value(&names, string("Carol"))])])
1480 );
1481 assert_eq!(
1482 fields.user_data(Some(&[]), None).unwrap(),
1483 BTreeMap::from([(inbox(0xA), vec![]), (inbox(0xB), vec![])])
1484 );
1485 assert!(fields.user_data(None, Some(&[])).unwrap().is_empty());
1486 assert!(matches!(
1487 fields.user_data(Some(&[MetadataFieldRef::GROUP_NAME]), None),
1488 Err(FieldError::NotUserField(ComponentId::GROUP_NAME))
1489 ));
1490 assert!(matches!(
1491 fields.user_data(Some(&[MetadataFieldRef::new(LINKS)]), None),
1492 Err(FieldError::NotUserField(LINKS))
1493 ));
1494 assert!(matches!(
1495 fields.user_data(Some(&[MetadataFieldRef::new(BROKEN)]), None),
1496 Err(FieldError::UnknownField(BROKEN))
1497 ));
1498 assert!(matches!(
1499 fields.user_data(Some(&[scores.clone(), named(SCORES, "scores")]), None),
1500 Err(FieldError::DuplicateField(SCORES))
1501 ));
1502 }
1503
1504 #[xmtp_common::test(unwrap_try = true)]
1510 fn user_data_skips_undecodable_fields() {
1511 let dictionary = dictionary(
1512 &[
1513 (
1514 ComponentId::GROUP_MEMBERSHIP,
1515 entry(tag(ComponentType::TlsMapInboxIdBytes)),
1516 ),
1517 (
1518 ComponentId::USER_DISPLAY_NAME,
1519 entry(tag(ComponentType::TlsMapInboxIdString)),
1520 ),
1521 (STATUS, entry(tag(ComponentType::TlsMapInboxIdString))),
1522 ],
1523 &[
1524 (
1525 ComponentId::GROUP_MEMBERSHIP,
1526 inbox_map(&[(inbox(0xA), &[1])]),
1527 ),
1528 (
1529 ComponentId::USER_DISPLAY_NAME,
1530 inbox_map(&[(inbox(0xA), b"Alix")]),
1531 ),
1532 (STATUS, inbox_map(&[(inbox(0xA), &[0xFF])])),
1533 ],
1534 );
1535 let fields = snapshot(&dictionary);
1536 let status = named(STATUS, "GROUP_NAME");
1537 let names = UserFieldValue {
1538 field: MetadataFieldRef::USER_DISPLAY_NAME,
1539 value: string("Alix"),
1540 };
1541 assert_eq!(
1542 fields.user_data(None, None)?,
1543 BTreeMap::from([(inbox(0xA), vec![names])])
1544 );
1545 assert_eq!(
1546 fields.user_data(Some(std::slice::from_ref(&status)), None)?,
1547 BTreeMap::from([(inbox(0xA), vec![])])
1548 );
1549 assert!(fields.value(&status).is_err());
1550 }
1551
1552 #[xmtp_common::test(unwrap_try = true)]
1556 fn user_data_defaults_to_the_dm_pair() {
1557 let dictionary = dictionary(
1558 &[(
1559 ComponentId::USER_DISPLAY_NAME,
1560 entry(tag(ComponentType::TlsMapInboxIdString)),
1561 )],
1562 &[
1563 (
1564 ComponentId::DM_MEMBERS,
1565 inbox_set(&[inbox(0xA), inbox(0xD)]),
1566 ),
1567 (
1568 ComponentId::GROUP_MEMBERSHIP,
1569 inbox_map(&[(inbox(0xA), b"")]),
1570 ),
1571 ],
1572 );
1573 let data = snapshot(&dictionary).user_data(None, None).unwrap();
1574 assert_eq!(
1575 data.into_keys().collect::<Vec<_>>(),
1576 vec![inbox(0xA), inbox(0xD)]
1577 );
1578 }
1579
1580 fn update(write: FieldWrite) -> Vec<u8> {
1581 match write.operation {
1582 WriteOperation::Update(payload) => payload,
1583 other => panic!("expected an update, got {other:?}"),
1584 }
1585 }
1586
1587 #[xmtp_common::test(unwrap_try = true)]
1593 fn field_writes_follow_the_committed_type() {
1594 use ComponentMutation as M;
1595 let dictionary = group();
1596 let fields = snapshot(&dictionary);
1597 let status = MetadataFieldRef::new(STATUS);
1598 let write = fields
1599 .field_write(&status, &M::Replace(string("away")))
1600 .unwrap();
1601 assert_eq!(write.component_type, ComponentType::String);
1602 assert_eq!(update(write), b"away");
1603
1604 let insert = M::MapDelta(vec![MapMutation::Insert(
1605 FieldKey::InboxId(inbox(0xB)),
1606 string("Bo"),
1607 )]);
1608 let write = fields
1609 .field_write(&MetadataFieldRef::USER_DISPLAY_NAME, &insert)
1610 .unwrap();
1611 assert_eq!(write.component_type, ComponentType::TlsMapInboxIdString);
1612 assert_eq!(
1613 update(write),
1614 TlsMapDelta::<InboxId, VLBytes>::new()
1615 .insert(inbox(0xB), b"Bo".as_slice().into())
1616 .tls_serialize_detached()
1617 .unwrap()
1618 );
1619
1620 let set = M::SetDelta(vec![
1621 SetMutation::Insert(FieldKey::InboxId(inbox(0xA))),
1622 SetMutation::DeleteByHash([1; 32]),
1623 ]);
1624 let write = fields
1625 .field_write(&MetadataFieldRef::new(TAGS), &set)
1626 .unwrap();
1627 assert_eq!(
1628 update(write),
1629 TlsSetDelta::<InboxId>::new()
1630 .insert(inbox(0xA))
1631 .remove_by_hash(TlsKeyHash::from_bytes([1; 32]))
1632 .tls_serialize_detached()
1633 .unwrap()
1634 );
1635 assert_eq!(
1636 fields.field_write(&status, &M::Remove).unwrap().operation,
1637 WriteOperation::Remove
1638 );
1639
1640 let bytes_name = M::MapDelta(vec![MapMutation::Update(
1641 FieldKey::InboxId(inbox(0xA)),
1642 FieldValue::Bytes(b"Alix".to_vec()),
1643 )]);
1644 for (field, mutation) in [
1645 (
1646 status.clone(),
1647 M::Replace(FieldValue::Bytes(b"away".to_vec())),
1648 ),
1649 (status.clone(), M::MapDelta(vec![])),
1650 (MetadataFieldRef::USER_DISPLAY_NAME, bytes_name),
1651 (
1652 MetadataFieldRef::new(SCORES),
1653 M::MapDelta(vec![MapMutation::Delete(FieldKey::Bytes(vec![0xA; 32]))]),
1654 ),
1655 (
1656 MetadataFieldRef::new(TAGS),
1657 M::SetDelta(vec![SetMutation::Delete(FieldKey::Bytes(vec![]))]),
1658 ),
1659 (MetadataFieldRef::new(LINKS), M::SetDelta(vec![])),
1660 ] {
1661 assert!(matches!(
1662 fields.field_write(&field, &mutation),
1663 Err(FieldError::TypeMismatch(id)) if id == field.component_id
1664 ));
1665 }
1666 for mutation in [M::Remove, M::Replace(FieldValue::Bytes(vec![]))] {
1667 assert!(matches!(
1668 fields.field_write(&MetadataFieldRef::new(FUTURE), &mutation),
1669 Err(FieldError::UnsupportedType {
1670 component_id: FUTURE,
1671 tag: 99
1672 })
1673 ));
1674 }
1675 assert!(matches!(
1676 fields.field_write(&MetadataFieldRef::GROUP_IMAGE, &M::Remove),
1677 Err(FieldError::UnknownField(ComponentId::GROUP_IMAGE))
1678 ));
1679 }
1680
1681 #[xmtp_common::test(unwrap_try = true)]
1685 fn user_data_writes_refuse_bad_updates() {
1686 let dictionary = group();
1687 let fields = snapshot(&dictionary);
1688 let set = |field: MetadataFieldRef, value| UserFieldUpdate { field, value };
1689 assert_eq!(
1690 fields
1691 .user_data_writes(&[
1692 set(MetadataFieldRef::USER_DISPLAY_NAME, Some(string("Bo"))),
1693 set(MetadataFieldRef::new(SCORES), None),
1694 ])
1695 .unwrap(),
1696 vec![
1697 FieldWrite {
1698 component_id: ComponentId::USER_DISPLAY_NAME,
1699 component_type: ComponentType::TlsMapInboxIdString,
1700 operation: WriteOperation::SetOwn(b"Bo".to_vec()),
1701 },
1702 FieldWrite {
1703 component_id: SCORES,
1704 component_type: ComponentType::TlsMapInboxIdBytes,
1705 operation: WriteOperation::ClearOwn,
1706 },
1707 ]
1708 );
1709 assert!(fields.user_data_writes(&[]).unwrap().is_empty());
1710 assert!(matches!(
1711 fields.user_data_writes(&[
1712 set(MetadataFieldRef::USER_DISPLAY_NAME, Some(string("Bo"))),
1713 set(named(ComponentId::USER_DISPLAY_NAME, "nickname"), None),
1714 ]),
1715 Err(FieldError::DuplicateField(ComponentId::USER_DISPLAY_NAME))
1716 ));
1717 assert!(matches!(
1718 fields.user_data_writes(&[set(MetadataFieldRef::GROUP_NAME, Some(string("x")))]),
1719 Err(FieldError::NotUserField(ComponentId::GROUP_NAME))
1720 ));
1721 assert!(matches!(
1722 fields.user_data_writes(&[set(MetadataFieldRef::new(SCORES), Some(string("7")))]),
1723 Err(FieldError::TypeMismatch(SCORES))
1724 ));
1725 assert!(matches!(
1726 fields.user_data_writes(&[set(
1727 MetadataFieldRef::USER_DISPLAY_NAME,
1728 Some(FieldValue::Bytes(b"Bo".to_vec()))
1729 )]),
1730 Err(FieldError::TypeMismatch(ComponentId::USER_DISPLAY_NAME))
1731 ));
1732 assert!(matches!(
1733 fields.user_data_writes(&[set(MetadataFieldRef::new(BROKEN), None)]),
1734 Err(FieldError::UnknownField(BROKEN))
1735 ));
1736 }
1737
1738 fn own_write(id: ComponentId, ty: ComponentType, operation: WriteOperation) -> FieldWrite {
1739 FieldWrite {
1740 component_id: id,
1741 component_type: ty,
1742 operation,
1743 }
1744 }
1745
1746 fn map_delta(delta: TlsMapDelta<InboxId, VLBytes>) -> AppDataUpdateOperation {
1747 AppDataUpdateOperation::Update(delta.tls_serialize_detached().unwrap().into())
1748 }
1749
1750 #[xmtp_common::test(unwrap_try = true)]
1755 fn own_writes_resolve_against_current_values() {
1756 let dictionary = group();
1757 let fields = snapshot(&dictionary);
1758 let names = |operation| {
1759 own_write(
1760 ComponentId::USER_DISPLAY_NAME,
1761 ComponentType::TlsMapInboxIdString,
1762 operation,
1763 )
1764 };
1765 let scores = |operation| own_write(SCORES, ComponentType::TlsMapInboxIdBytes, operation);
1766 let resolve = |own, writes: &[FieldWrite]| {
1767 fields
1768 .resolve_writes(Some(&dictionary), own, writes)
1769 .unwrap()
1770 };
1771 let id = ComponentId::USER_DISPLAY_NAME;
1772 assert_eq!(
1773 resolve(inbox(0xA), &[names(WriteOperation::SetOwn(b"Al".to_vec()))]),
1774 vec![(
1775 id,
1776 map_delta(TlsMapDelta::new().update(inbox(0xA), b"Al".as_slice().into()))
1777 )]
1778 );
1779 assert_eq!(
1780 resolve(inbox(0xB), &[names(WriteOperation::SetOwn(b"Bo".to_vec()))]),
1781 vec![(
1782 id,
1783 map_delta(TlsMapDelta::new().insert(inbox(0xB), b"Bo".as_slice().into()))
1784 )]
1785 );
1786 assert_eq!(
1787 resolve(
1788 inbox(0xA),
1789 &[
1790 names(WriteOperation::ClearOwn),
1791 scores(WriteOperation::ClearOwn)
1792 ]
1793 ),
1794 vec![
1795 (id, map_delta(TlsMapDelta::new().delete(inbox(0xA)))),
1796 (SCORES, map_delta(TlsMapDelta::new().delete(inbox(0xA)))),
1797 ]
1798 );
1799 assert!(
1800 resolve(
1801 inbox(0xB),
1802 &[
1803 names(WriteOperation::ClearOwn),
1804 scores(WriteOperation::ClearOwn)
1805 ]
1806 )
1807 .is_empty()
1808 );
1809 assert!(resolve(inbox(0xB), &[]).is_empty());
1810 assert_eq!(
1812 fields
1813 .resolve_writes(
1814 None,
1815 inbox(0xA),
1816 &[names(WriteOperation::SetOwn(b"A".to_vec()))]
1817 )
1818 .unwrap(),
1819 vec![(
1820 id,
1821 map_delta(TlsMapDelta::new().insert(inbox(0xA), b"A".as_slice().into()))
1822 )]
1823 );
1824 assert!(matches!(
1825 fields.resolve_writes(
1826 Some(&dictionary),
1827 inbox(0xA),
1828 &[
1829 names(WriteOperation::ClearOwn),
1830 names(WriteOperation::ClearOwn)
1831 ]
1832 ),
1833 Err(FieldError::DuplicateField(ComponentId::USER_DISPLAY_NAME))
1834 ));
1835 }
1836
1837 #[xmtp_common::test(unwrap_try = true)]
1842 fn writes_are_checked_against_current_values() {
1843 let dictionary = group();
1844 let fields = snapshot(&dictionary);
1845 let status = |operation| own_write(STATUS, ComponentType::String, operation);
1846 let resolve =
1847 |writes: &[FieldWrite]| fields.resolve_writes(Some(&dictionary), inbox(0xA), writes);
1848 assert!(
1849 resolve(&[status(WriteOperation::Remove)])
1850 .unwrap()
1851 .is_empty()
1852 );
1853 assert_eq!(
1854 resolve(&[own_write(
1855 SCORES,
1856 ComponentType::TlsMapInboxIdBytes,
1857 WriteOperation::Remove
1858 )])
1859 .unwrap(),
1860 vec![(SCORES, AppDataUpdateOperation::Remove)]
1861 );
1862 assert!(matches!(
1863 resolve(&[status(WriteOperation::Update(vec![b'x'; 8193]))]),
1864 Err(FieldError::Component(_))
1865 ));
1866 assert!(matches!(
1867 resolve(&[status(WriteOperation::Update(vec![0xC3, 0x28]))]),
1868 Err(FieldError::Component(_))
1869 ));
1870 let insert_present = TlsMapDelta::<InboxId, VLBytes>::new()
1871 .insert(inbox(0xA), b"again".as_slice().into())
1872 .tls_serialize_detached()
1873 .unwrap();
1874 assert!(matches!(
1875 resolve(&[own_write(
1876 ComponentId::USER_DISPLAY_NAME,
1877 ComponentType::TlsMapInboxIdString,
1878 WriteOperation::Update(insert_present)
1879 )]),
1880 Err(FieldError::Component(_))
1881 ));
1882 }
1883
1884 #[xmtp_common::test(unwrap_try = true)]
1890 fn unchanged_writes_are_dropped() {
1891 let dictionary = group();
1892 let fields = snapshot(&dictionary);
1893 let resolve = |writes: &[FieldWrite]| {
1894 fields
1895 .resolve_writes(Some(&dictionary), inbox(0xA), writes)
1896 .unwrap()
1897 };
1898 let name = |value: &[u8]| {
1899 own_write(
1900 ComponentId::GROUP_NAME,
1901 ComponentType::String,
1902 WriteOperation::Update(value.to_vec()),
1903 )
1904 };
1905 let empty_links = TlsMapDelta::<VLBytes, VLBytes>::new().tls_serialize_detached()?;
1906 let empty_scores = TlsMapDelta::<InboxId, VLBytes>::new().tls_serialize_detached()?;
1907 assert!(
1908 resolve(&[
1909 name(b"Team"),
1910 own_write(
1911 ComponentId::USER_DISPLAY_NAME,
1912 ComponentType::TlsMapInboxIdString,
1913 WriteOperation::SetOwn(b"Alix".to_vec()),
1914 ),
1915 own_write(
1916 SCORES,
1917 ComponentType::TlsMapInboxIdBytes,
1918 WriteOperation::Update(empty_scores),
1919 ),
1920 ])
1921 .is_empty()
1922 );
1923 assert_eq!(
1924 resolve(&[
1925 name(b"Teams"),
1926 own_write(
1927 LINKS,
1928 ComponentType::TlsMapBytesBytes,
1929 WriteOperation::Update(empty_links.clone()),
1930 ),
1931 ]),
1932 vec![
1933 (
1934 ComponentId::GROUP_NAME,
1935 AppDataUpdateOperation::Update(b"Teams".to_vec().into())
1936 ),
1937 (LINKS, AppDataUpdateOperation::Update(empty_links.into())),
1938 ]
1939 );
1940 assert_eq!(
1941 fields.write_operations(
1942 Some(&dictionary),
1943 inbox(0xA),
1944 &[
1945 name(b"Team"),
1946 own_write(
1947 ComponentId::USER_DISPLAY_NAME,
1948 ComponentType::TlsMapInboxIdString,
1949 WriteOperation::SetOwn(b"Alix".to_vec()),
1950 ),
1951 ]
1952 )?,
1953 vec![
1954 (
1955 ComponentId::GROUP_NAME,
1956 AppDataUpdateOperation::Update(b"Team".to_vec().into())
1957 ),
1958 (
1959 ComponentId::USER_DISPLAY_NAME,
1960 map_delta(TlsMapDelta::new().update(inbox(0xA), b"Alix".as_slice().into()))
1961 ),
1962 ]
1963 );
1964 }
1965
1966 #[xmtp_common::test(unwrap_try = true)]
1971 fn own_writes_need_a_user_field() {
1972 let dictionary = group();
1973 let fields = snapshot(&dictionary);
1974 for (id, ty) in [
1975 (STATUS, ComponentType::String),
1976 (LINKS, ComponentType::TlsMapBytesBytes),
1977 ] {
1978 for operation in [
1979 WriteOperation::SetOwn(b"x".to_vec()),
1980 WriteOperation::ClearOwn,
1981 ] {
1982 assert!(matches!(
1983 fields.resolve_writes(
1984 Some(&dictionary),
1985 inbox(0xA),
1986 &[own_write(id, ty, operation)]
1987 ),
1988 Err(FieldError::TypeMismatch(got)) if got == id
1989 ));
1990 }
1991 }
1992 }
1993
1994 #[xmtp_common::test(unwrap_try = true)]
1998 fn stale_writes_are_refused() {
1999 let encoded = snapshot(&group())
2000 .field_write(
2001 &MetadataFieldRef::new(STATUS),
2002 &ComponentMutation::Replace(string("x")),
2003 )
2004 .unwrap();
2005 let retyped = dictionary(&[(STATUS, entry(tag(ComponentType::Bytes)))], &[]);
2006 assert!(matches!(
2007 snapshot(&retyped).resolve_writes(
2008 Some(&retyped),
2009 inbox(0xA),
2010 std::slice::from_ref(&encoded)
2011 ),
2012 Err(FieldError::TypeChanged {
2013 component_id: STATUS,
2014 expected: ComponentType::String,
2015 actual: MetadataComponentType::Bytes,
2016 })
2017 ));
2018 let removed = dictionary(&[], &[]);
2019 assert!(matches!(
2020 snapshot(&removed).resolve_writes(Some(&removed), inbox(0xA), &[encoded]),
2021 Err(FieldError::UnknownField(STATUS))
2022 ));
2023 }
2024
2025 #[xmtp_common::test(unwrap_try = true)]
2030 fn uninterpreted_entries_are_left_alone() {
2031 let dictionary = group();
2032 let before = dictionary.clone();
2033 let fields = snapshot(&dictionary);
2034 let writes = fields
2035 .user_data_writes(&[UserFieldUpdate {
2036 field: MetadataFieldRef::USER_DISPLAY_NAME,
2037 value: Some(string("Bo")),
2038 }])
2039 .unwrap();
2040 let updates = fields
2041 .resolve_writes(Some(&dictionary), inbox(0xB), &writes)
2042 .unwrap();
2043 assert_eq!(
2044 updates.iter().map(|(id, _)| *id).collect::<Vec<_>>(),
2045 vec![ComponentId::USER_DISPLAY_NAME]
2046 );
2047 let _ = fields.values(&[MetadataFieldRef::new(FUTURE)]);
2048 assert_eq!(dictionary, before);
2049 assert_eq!(dictionary.get(&BROKEN.as_u16()), Some([1, 2, 3].as_slice()));
2050 }
2051
2052 #[xmtp_common::test(unwrap_try = true)]
2055 fn type_tags_round_trip() {
2056 for tag in 1..=7 {
2057 let shape = MetadataComponentType::from_tag(tag);
2058 assert_eq!(shape.tag().map(|t| t as i32), Some(tag));
2059 }
2060 for tag in [0, 8, -1] {
2061 assert_eq!(
2062 MetadataComponentType::from_tag(tag),
2063 MetadataComponentType::Unknown { tag }
2064 );
2065 assert_eq!(MetadataComponentType::from_tag(tag).tag(), None);
2066 }
2067 }
2068}