Skip to main content

xmtp_mls_common/
tls_set.rs

1#![deny(missing_docs)]
2
3//! A deterministic, sorted set with TLS codec serialization.
4//!
5//! [`TlsSet`] is backed by a [`TlsMap<K, ()>`](crate::tls_map::TlsMap), inheriting
6//! deterministic serialization, O(log n) lookup, and O(n) insert/remove.
7//!
8//! Since `()` serializes as zero bytes in TLS codec, the wire format is
9//! effectively `vlen(total_byte_length) || key[0] || key[1] || ...`.
10
11use std::io::{Read, Write};
12
13use tls_codec::{Deserialize, Serialize, Size};
14
15use crate::tls_map::{TlsMap, TlsMapDelta, TlsMapError, TlsMapMutation};
16
17/// A SHA-256 hash of a key's TLS-serialized form, used as the lookup token
18/// for [`TlsSetMutation::RemoveByHash`].
19///
20/// This is a newtype around `[u8; 32]` to prevent accidental mixing with
21/// other 32-byte SHA-256 hashes elsewhere in the codebase (commit hashes,
22/// installation IDs, etc.).
23///
24/// The only safe way to construct one is via [`TlsKeyHash::of`], which
25/// guarantees the hash always uses the canonical TLS serialization of the
26/// key. Raw byte construction is reserved for wire-format deserialization.
27#[derive(Clone, Copy, PartialEq, Eq, Hash)]
28pub struct TlsKeyHash([u8; 32]);
29
30impl TlsKeyHash {
31    /// Compute the hash of a key by SHA-256-ing its TLS-serialized form.
32    /// This is the only public way to construct a `TlsKeyHash`, ensuring
33    /// every hash on the wire was produced by the canonical encoding.
34    #[inline]
35    pub fn of<K: Serialize + Size>(key: &K) -> Result<Self, tls_codec::Error> {
36        let bytes = key.tls_serialize_detached()?;
37        Ok(Self(xmtp_common::sha256_array(&bytes)))
38    }
39
40    /// Construct from raw bytes. Used by TLS deserialization to reconstruct
41    /// a hash that was produced by an earlier `TlsKeyHash::of` on the sender.
42    #[inline]
43    pub(crate) const fn from_bytes(bytes: [u8; 32]) -> Self {
44        Self(bytes)
45    }
46
47    /// Borrow the underlying bytes.
48    #[inline]
49    pub const fn as_bytes(&self) -> &[u8; 32] {
50        &self.0
51    }
52}
53
54impl std::fmt::Debug for TlsKeyHash {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        f.write_str("TlsKeyHash(")?;
57        for byte in &self.0 {
58            write!(f, "{byte:02x}")?;
59        }
60        f.write_str(")")
61    }
62}
63
64/// Error type for [`TlsSet`] operations.
65#[derive(Debug, thiserror::Error)]
66pub enum TlsSetError {
67    /// Two or more keys in the set produce the same SHA-256 digest.
68    ///
69    /// Returned by [`TlsSet::apply_delta`] when building the hash index for
70    /// `RemoveByHash` lookups. SHA-256 collisions are cryptographically
71    /// infeasible to find by chance, so in practice this signals either:
72    /// - A bug in `Serialize` for `K` producing identical bytes for distinct
73    ///   logically-different keys (e.g., a non-deterministic encoding), or
74    /// - A deliberate adversarial attempt to seed the set with crafted entries.
75    ///
76    /// We surface it as a defense-in-depth guarantee: `RemoveByHash` resolves
77    /// to exactly one key, never silently picking one of several candidates.
78    #[error("duplicate hash in set")]
79    DuplicateHash,
80    /// An underlying [`TlsMap`] error.
81    #[error(transparent)]
82    Map(#[from] TlsMapError),
83    /// A TLS codec serialization error (from hashing a key).
84    #[error("tls codec error: {0}")]
85    Codec(#[from] tls_codec::Error),
86}
87
88/// A sorted set with deterministic TLS codec serialization.
89///
90/// # Examples
91///
92/// ```
93/// use xmtp_mls_common::tls_set::TlsSet;
94/// use tls_codec::{Serialize, Deserialize};
95///
96/// let mut set = TlsSet::<u16>::new();
97/// set.insert(3).unwrap();
98/// set.insert(1).unwrap();
99/// set.insert(2).unwrap();
100///
101/// let bytes = set.tls_serialize_detached().unwrap();
102/// let deserialized = TlsSet::<u16>::tls_deserialize_exact(&bytes).unwrap();
103/// assert_eq!(set, deserialized);
104/// ```
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct TlsSet<K> {
107    inner: TlsMap<K, ()>,
108}
109
110impl<K> Default for TlsSet<K> {
111    #[inline]
112    fn default() -> Self {
113        Self {
114            inner: TlsMap::default(),
115        }
116    }
117}
118
119impl<K: Ord + Eq> TlsSet<K> {
120    /// Create an empty set.
121    #[inline]
122    pub fn new() -> Self {
123        Self::default()
124    }
125
126    /// Create a set from an iterator of keys. Duplicates are silently ignored.
127    ///
128    /// ```
129    /// use xmtp_mls_common::tls_set::TlsSet;
130    ///
131    /// let set = TlsSet::from_keys([3, 1, 2, 1]);
132    /// assert_eq!(set.len(), 3);
133    /// ```
134    #[inline]
135    pub fn from_keys(iter: impl IntoIterator<Item = K>) -> Self {
136        Self {
137            inner: TlsMap::from_pairs(iter.into_iter().map(|k| (k, ()))),
138        }
139    }
140
141    /// Insert a key. Returns an error if the key already exists.
142    ///
143    /// ```
144    /// use xmtp_mls_common::tls_set::TlsSet;
145    ///
146    /// let mut set = TlsSet::<u8>::new();
147    /// assert!(set.insert(1).is_ok());
148    /// assert!(set.insert(1).is_err()); // duplicate
149    /// ```
150    #[inline]
151    pub fn insert(&mut self, key: K) -> Result<(), TlsSetError> {
152        Ok(self.inner.insert(key, ())?)
153    }
154
155    /// Remove a key. Returns an error if the key doesn't exist.
156    ///
157    /// ```
158    /// use xmtp_mls_common::tls_set::TlsSet;
159    ///
160    /// let mut set = TlsSet::<u8>::new();
161    /// set.insert(1).unwrap();
162    /// assert!(set.remove(&1).is_ok());
163    /// assert!(set.remove(&1).is_err()); // already removed
164    /// ```
165    #[inline]
166    pub fn remove(&mut self, key: &K) -> Result<(), TlsSetError> {
167        self.inner.remove(key).map(|_| ())?;
168        Ok(())
169    }
170
171    /// Returns true if the set contains the key.
172    #[inline]
173    pub fn contains(&self, key: &K) -> bool {
174        self.inner.contains_key(key)
175    }
176
177    /// Returns the number of elements in the set.
178    #[inline]
179    pub fn len(&self) -> usize {
180        self.inner.len()
181    }
182
183    /// Returns true if the set is empty.
184    #[inline]
185    pub fn is_empty(&self) -> bool {
186        self.inner.is_empty()
187    }
188
189    /// Iterate over keys in sorted order.
190    ///
191    /// ```
192    /// use xmtp_mls_common::tls_set::TlsSet;
193    ///
194    /// let set = TlsSet::from_keys([3, 1, 2]);
195    /// let keys: Vec<_> = set.iter().collect();
196    /// assert_eq!(keys, vec![&1, &2, &3]);
197    /// ```
198    #[inline]
199    pub fn iter(&self) -> impl Iterator<Item = &K> {
200        self.inner.keys()
201    }
202}
203
204impl<K: Ord + Eq + Clone + Serialize + Size> TlsSet<K> {
205    /// Apply a delta atomically. If any mutation fails, the set is unchanged.
206    ///
207    /// If the delta contains any `RemoveByHash` mutations, a hash index of all
208    /// existing keys is built once (O(n)) and used for lookups (O(1) each),
209    /// avoiding O(n*m) behavior. Returns [`TlsSetError::DuplicateHash`] if two
210    /// existing keys produce the same SHA-256 hash.
211    ///
212    /// # Performance
213    ///
214    /// - **Time:** O(n + m) when the delta contains any `RemoveByHash`
215    ///   (one O(n) index build, then O(1) per lookup), or O(m) otherwise,
216    ///   where `n` is the current set size and `m` is the mutation count.
217    /// - **Memory:** O(m) — all mutations are resolved into a temporary
218    ///   `Vec<TlsMapMutation>` before being applied. This allocation is
219    ///   required to preserve atomicity: resolution must complete before
220    ///   any actual mutation happens, so a partial delta cannot leave the
221    ///   set in an inconsistent state. When `RemoveByHash` is present, an
222    ///   additional O(n) `HashMap` is allocated for the hash index, holding
223    ///   borrowed key references (no key cloning during index build).
224    ///
225    /// ```
226    /// use xmtp_mls_common::tls_set::{TlsSet, TlsSetDelta};
227    ///
228    /// let mut set = TlsSet::<u8>::new();
229    /// set.insert(1).unwrap();
230    ///
231    /// let delta = TlsSetDelta::new().insert(2).remove(1);
232    /// set.apply_delta(delta).unwrap();
233    /// assert!(set.contains(&2));
234    /// assert!(!set.contains(&1));
235    /// ```
236    // implements: META-013
237    pub fn apply_delta(&mut self, delta: TlsSetDelta<K>) -> Result<(), TlsSetError> {
238        let has_hash_removal = delta
239            .mutations
240            .iter()
241            .any(|m| matches!(m, TlsSetMutation::RemoveByHash(_)));
242
243        // Build hash → &K index only when needed — O(n) once instead of
244        // O(n) per removal. Stores borrowed references, no key cloning.
245        let hash_index: Option<std::collections::HashMap<TlsKeyHash, &K>> = if has_hash_removal {
246            let mut idx = std::collections::HashMap::with_capacity(self.inner.len());
247            for key in self.inner.keys() {
248                if idx.insert(TlsKeyHash::of(key)?, key).is_some() {
249                    return Err(TlsSetError::DuplicateHash);
250                }
251            }
252            Some(idx)
253        } else {
254            None
255        };
256
257        let resolved: Vec<TlsMapMutation<K, ()>> = delta
258            .mutations
259            .into_iter()
260            .map(|m| -> Result<TlsMapMutation<K, ()>, TlsSetError> {
261                match m {
262                    TlsSetMutation::Insert(key) => Ok(TlsMapMutation::Insert { key, value: () }),
263                    TlsSetMutation::Remove(key) => Ok(TlsMapMutation::Delete { key }),
264                    TlsSetMutation::RemoveByHash(target_hash) => {
265                        let idx = hash_index
266                            .as_ref()
267                            .expect("hash index must be built for RemoveByHash");
268                        let key = idx.get(&target_hash).ok_or(TlsMapError::KeyNotFound)?;
269                        Ok(TlsMapMutation::Delete {
270                            key: (*key).clone(),
271                        })
272                    }
273                }
274            })
275            .collect::<Result<Vec<_>, _>>()?;
276        let map_delta = TlsMapDelta {
277            mutations: resolved,
278        };
279        self.inner.apply_delta(map_delta)?;
280        Ok(())
281    }
282}
283
284// -- TLS codec --
285
286impl<K: Size> Size for TlsSet<K> {
287    #[inline]
288    fn tls_serialized_len(&self) -> usize {
289        self.inner.tls_serialized_len()
290    }
291}
292
293impl<K: Serialize + Size + std::fmt::Debug> Serialize for TlsSet<K> {
294    #[inline]
295    fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
296        self.inner.tls_serialize(writer)
297    }
298}
299
300impl<K> Deserialize for TlsSet<K>
301where
302    K: Deserialize + Size + Ord + Eq,
303{
304    #[inline]
305    fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
306    where
307        Self: Sized,
308    {
309        let inner = TlsMap::tls_deserialize(bytes)?;
310        Ok(Self { inner })
311    }
312}
313
314impl<K: Ord + Eq> FromIterator<K> for TlsSet<K> {
315    #[inline]
316    fn from_iter<T: IntoIterator<Item = K>>(iter: T) -> Self {
317        Self::from_keys(iter)
318    }
319}
320
321impl<K> IntoIterator for TlsSet<K> {
322    type Item = K;
323    type IntoIter = std::iter::Map<<TlsMap<K, ()> as IntoIterator>::IntoIter, fn((K, ())) -> K>;
324
325    #[inline]
326    fn into_iter(self) -> Self::IntoIter {
327        self.inner.into_iter().map(|(k, _)| k)
328    }
329}
330
331// ============================================================================
332// TlsSetDelta — atomic batch mutations for TlsSet
333// ============================================================================
334
335/// A mutation to apply to a [`TlsSet`].
336///
337/// Wire format: `u8(tag) || payload` where:
338/// - tag 0 = Insert: `K(tls)`
339/// - tag 1 = Remove: `K(tls)`
340/// - tag 2 = RemoveByHash: `[u8; 32]` (SHA-256 of the key's TLS serialization)
341#[derive(Debug, Clone, PartialEq, Eq)]
342pub enum TlsSetMutation<K> {
343    /// Insert a key. Fails if the key already exists.
344    Insert(K),
345    /// Remove a key by value. Fails if the key doesn't exist.
346    Remove(K),
347    /// Remove a key by the SHA-256 hash of its TLS serialization.
348    /// Avoids sending large keys over the wire for removal.
349    /// Fails if no key with a matching hash exists.
350    RemoveByHash(TlsKeyHash),
351}
352
353/// A batch of mutations to apply atomically to a [`TlsSet`].
354#[derive(Debug, Clone, PartialEq, Eq)]
355pub struct TlsSetDelta<K> {
356    /// The ordered list of mutations to apply.
357    pub mutations: Vec<TlsSetMutation<K>>,
358}
359
360impl<K> TlsSetDelta<K> {
361    /// Create an empty delta.
362    #[inline]
363    pub fn new() -> Self {
364        Self {
365            mutations: Vec::new(),
366        }
367    }
368
369    /// Append an insert mutation.
370    #[inline]
371    pub fn insert(mut self, key: K) -> Self {
372        self.mutations.push(TlsSetMutation::Insert(key));
373        self
374    }
375
376    /// Append a remove mutation.
377    #[inline]
378    pub fn remove(mut self, key: K) -> Self {
379        self.mutations.push(TlsSetMutation::Remove(key));
380        self
381    }
382
383    /// Append a remove-by-hash mutation.
384    #[inline]
385    pub fn remove_by_hash(mut self, hash: TlsKeyHash) -> Self {
386        self.mutations.push(TlsSetMutation::RemoveByHash(hash));
387        self
388    }
389}
390
391impl<K> Default for TlsSetDelta<K> {
392    #[inline]
393    fn default() -> Self {
394        Self::new()
395    }
396}
397
398// -- TLS codec for delta types --
399
400impl<K: Size> Size for TlsSetMutation<K> {
401    #[inline]
402    fn tls_serialized_len(&self) -> usize {
403        1 + match self {
404            Self::Insert(key) | Self::Remove(key) => key.tls_serialized_len(),
405            Self::RemoveByHash(_) => 32,
406        }
407    }
408}
409
410impl<K: Serialize + Size> Serialize for TlsSetMutation<K> {
411    #[inline]
412    fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
413        match self {
414            Self::Insert(key) => {
415                let mut written = 0_u8.tls_serialize(writer)?;
416                written += key.tls_serialize(writer)?;
417                Ok(written)
418            }
419            Self::Remove(key) => {
420                let mut written = 1_u8.tls_serialize(writer)?;
421                written += key.tls_serialize(writer)?;
422                Ok(written)
423            }
424            Self::RemoveByHash(hash) => {
425                let written = 2_u8.tls_serialize(writer)?;
426                let bytes = hash.as_bytes();
427                writer
428                    .write_all(bytes)
429                    .map_err(|e| tls_codec::Error::EncodingError(e.to_string()))?;
430                Ok(written + bytes.len())
431            }
432        }
433    }
434}
435
436impl<K: Deserialize + Size> Deserialize for TlsSetMutation<K> {
437    #[inline]
438    fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
439    where
440        Self: Sized,
441    {
442        let tag = u8::tls_deserialize(bytes)?;
443        match tag {
444            0 => Ok(Self::Insert(K::tls_deserialize(bytes)?)),
445            1 => Ok(Self::Remove(K::tls_deserialize(bytes)?)),
446            2 => {
447                let mut buf = [0_u8; 32];
448                bytes
449                    .read_exact(&mut buf)
450                    .map_err(|e| tls_codec::Error::DecodingError(e.to_string()))?;
451                Ok(Self::RemoveByHash(TlsKeyHash::from_bytes(buf)))
452            }
453            _ => Err(tls_codec::Error::DecodingError(format!(
454                "unknown TlsSetMutation tag: {tag}"
455            ))),
456        }
457    }
458}
459
460impl<K: Size> Size for TlsSetDelta<K> {
461    #[inline]
462    fn tls_serialized_len(&self) -> usize {
463        self.mutations.tls_serialized_len()
464    }
465}
466
467impl<K: Serialize + Size + std::fmt::Debug> Serialize for TlsSetDelta<K> {
468    #[inline]
469    fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
470        self.mutations.tls_serialize(writer)
471    }
472}
473
474impl<K: Deserialize + Size> Deserialize for TlsSetDelta<K> {
475    #[inline]
476    fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
477    where
478        Self: Sized,
479    {
480        let mutations = Vec::<TlsSetMutation<K>>::tls_deserialize(bytes)?;
481        Ok(Self { mutations })
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488    use tls_codec::{Deserialize, Serialize};
489
490    #[xmtp_common::test]
491    fn test_insert_and_contains() {
492        let mut set = TlsSet::<u16>::new();
493        assert!(set.remove(&42).is_err());
494        set.insert(42).unwrap();
495        assert!(set.contains(&42));
496        assert!(!set.contains(&99));
497        assert!(set.insert(42).is_err());
498        set.remove(&42).unwrap();
499        assert!(!set.contains(&42));
500        assert!(set.is_empty());
501        assert!(set.remove(&42).is_err());
502
503        let set = TlsSet::from_keys([1_u8, 2, 3, 1, 2]);
504        assert_eq!(set.len(), 3);
505        assert!(set.contains(&1));
506    }
507
508    #[xmtp_common::test]
509    fn test_iter_sorted() {
510        let set = TlsSet::from_keys([3_u8, 1, 2]);
511        let keys: Vec<_> = set.iter().copied().collect();
512        assert_eq!(keys, vec![1, 2, 3]);
513        assert_eq!(set.clone().into_iter().collect::<Vec<_>>(), vec![1, 2, 3]);
514        let collected: TlsSet<u8> = [3, 1, 2].into_iter().collect();
515        assert_eq!(collected.len(), 3);
516        assert!(collected.contains(&1));
517        assert_eq!(collected.iter().copied().collect::<Vec<_>>(), keys);
518    }
519
520    #[xmtp_common::test]
521    fn test_tls_round_trip() {
522        let set = TlsSet::from_keys([10_u16, 20, 30]);
523        let bytes = set.tls_serialize_detached().unwrap();
524        let restored = TlsSet::<u16>::tls_deserialize_exact(&bytes).unwrap();
525        assert_eq!(set, restored);
526        let empty = TlsSet::<u8>::new();
527        let bytes = empty.tls_serialize_detached().unwrap();
528        let restored = TlsSet::<u8>::tls_deserialize_exact(&bytes).unwrap();
529        assert_eq!(empty, restored);
530        assert!(restored.is_empty());
531    }
532
533    #[xmtp_common::test]
534    fn test_apply_delta() {
535        let mut set = TlsSet::from_keys([1_u8, 2, 3]);
536        let delta = TlsSetDelta::new().insert(4).remove(2);
537        set.apply_delta(delta).unwrap();
538        assert!(set.contains(&1));
539        assert!(!set.contains(&2));
540        assert!(set.contains(&3));
541        assert!(set.contains(&4));
542    }
543
544    #[xmtp_common::test]
545    // verifies: META-013
546    fn test_apply_delta_rollback_on_failure() {
547        let mut set = TlsSet::from_keys([1_u8, 2]);
548        // Try to add 3 then add 1 (duplicate) — should rollback
549        let delta = TlsSetDelta::new().insert(3).insert(1);
550        assert!(set.apply_delta(delta).is_err());
551        // Set should be unchanged
552        assert_eq!(set.len(), 2);
553        assert!(!set.contains(&3));
554    }
555
556    #[xmtp_common::test]
557    // verifies: META-013
558    fn test_remove_by_hash() {
559        let mut set = TlsSet::from_keys([10_u16, 20, 30]);
560        let hash = TlsKeyHash::of(&20_u16).unwrap();
561        let delta = TlsSetDelta::new().remove_by_hash(hash);
562        set.apply_delta(delta).unwrap();
563        assert!(set.contains(&10));
564        assert!(!set.contains(&20));
565        assert!(set.contains(&30));
566    }
567
568    #[xmtp_common::test]
569    fn test_remove_by_hash_not_found() {
570        let mut set = TlsSet::from_keys([10_u16, 20]);
571        let hash = TlsKeyHash::of(&99_u16).unwrap(); // not in set
572        let delta = TlsSetDelta::new().remove_by_hash(hash);
573        assert!(set.apply_delta(delta).is_err());
574    }
575
576    #[xmtp_common::test]
577    fn test_remove_by_hash_matches_remove_by_value() {
578        let mut set_a = TlsSet::from_keys([1_u16, 2, 3]);
579        let mut set_b = set_a.clone();
580
581        // Remove by value
582        let delta_a = TlsSetDelta::new().remove(2);
583        set_a.apply_delta(delta_a).unwrap();
584
585        // Remove by hash
586        let hash = TlsKeyHash::of(&2_u16).unwrap();
587        let delta_b = TlsSetDelta::new().remove_by_hash(hash);
588        set_b.apply_delta(delta_b).unwrap();
589
590        assert_eq!(set_a, set_b);
591    }
592
593    #[xmtp_common::test]
594    fn test_delta_tls_round_trip() {
595        let delta = TlsSetDelta::<u16>::new().insert(10).remove(20).insert(30);
596        let bytes = delta.tls_serialize_detached().unwrap();
597        let restored = TlsSetDelta::<u16>::tls_deserialize_exact(&bytes).unwrap();
598        assert_eq!(delta, restored);
599    }
600
601    #[xmtp_common::test]
602    fn test_remove_by_hash_mutation_tls_round_trip() {
603        let hash = TlsKeyHash::of(&42_u16).unwrap();
604        let mutation = TlsSetMutation::<u16>::RemoveByHash(hash);
605        let bytes = mutation.tls_serialize_detached().unwrap();
606        let restored = TlsSetMutation::<u16>::tls_deserialize_exact(&bytes).unwrap();
607        assert_eq!(mutation, restored);
608    }
609
610    #[xmtp_common::test]
611    fn test_mutation_tls_round_trip() {
612        let add = TlsSetMutation::Insert(42_u16);
613        let bytes = add.tls_serialize_detached().unwrap();
614        let restored = TlsSetMutation::<u16>::tls_deserialize_exact(&bytes).unwrap();
615        assert_eq!(add, restored);
616
617        let remove = TlsSetMutation::Remove(99_u16);
618        let bytes = remove.tls_serialize_detached().unwrap();
619        let restored = TlsSetMutation::<u16>::tls_deserialize_exact(&bytes).unwrap();
620        assert_eq!(remove, restored);
621    }
622
623    #[xmtp_common::test]
624    fn test_deserialize_unknown_tag() {
625        // Tag 3 is not a valid TlsSetMutation variant (only 0/1/2 are defined).
626        let bytes = [3_u8, 0, 42];
627        let result = TlsSetMutation::<u16>::tls_deserialize_exact(bytes);
628        assert!(matches!(result, Err(tls_codec::Error::DecodingError(_))));
629    }
630
631    /// A key type with a deliberately buggy `Serialize` impl that produces
632    /// identical bytes for distinct logical values. Used to exercise the
633    /// duplicate-hash detection path in `apply_delta`, since SHA-256 collisions
634    /// cannot be found by chance with well-formed inputs.
635    #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
636    struct CollidingKey(u16);
637
638    impl Size for CollidingKey {
639        fn tls_serialized_len(&self) -> usize {
640            1
641        }
642    }
643
644    impl Serialize for CollidingKey {
645        fn tls_serialize<W: std::io::Write>(
646            &self,
647            writer: &mut W,
648        ) -> Result<usize, tls_codec::Error> {
649            // Buggy: always serialize as the same byte regardless of value.
650            0_u8.tls_serialize(writer)
651        }
652    }
653
654    impl Deserialize for CollidingKey {
655        fn tls_deserialize<R: std::io::Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
656        where
657            Self: Sized,
658        {
659            let _ = u8::tls_deserialize(bytes)?;
660            Ok(CollidingKey(0))
661        }
662    }
663
664    #[xmtp_common::test]
665    // verifies: META-013
666    fn test_apply_delta_duplicate_hash() {
667        let mut set = TlsSet::<CollidingKey>::new();
668        // Distinct logical keys but identical TLS serialization → identical hashes.
669        set.insert(CollidingKey(1)).unwrap();
670        set.insert(CollidingKey(2)).unwrap();
671
672        let any_hash = TlsKeyHash::of(&CollidingKey(0)).unwrap();
673        let delta = TlsSetDelta::new().remove_by_hash(any_hash);
674        let result = set.apply_delta(delta);
675        assert!(matches!(result, Err(TlsSetError::DuplicateHash)));
676    }
677
678    #[xmtp_common::test]
679    // verifies: META-013
680    fn test_apply_delta_remove_by_hash_not_found_in_index() {
681        // Build the hash index successfully (no collisions), then look up a
682        // hash that doesn't match any key. This exercises the
683        // `idx.get(...).ok_or(KeyNotFound)?` path inside the resolve loop.
684        let mut set = TlsSet::<u16>::new();
685        set.insert(10).unwrap();
686        set.insert(20).unwrap();
687
688        let missing_hash = TlsKeyHash::of(&999_u16).unwrap();
689        let delta = TlsSetDelta::new().remove_by_hash(missing_hash);
690        let result = set.apply_delta(delta);
691        assert!(matches!(
692            result,
693            Err(TlsSetError::Map(TlsMapError::KeyNotFound))
694        ));
695        // Set is unchanged.
696        assert!(set.contains(&10));
697        assert!(set.contains(&20));
698    }
699}