Skip to main content

xmtp_mls_common/
tls_map.rs

1#![deny(missing_docs)]
2
3//! A deterministic, sorted key-value map with TLS codec serialization.
4//!
5//! [`TlsMap`] maintains entries in sorted key order so that serialization is
6//! byte-identical across implementations. [`TlsMapDelta`] provides an atomic
7//! mutation batch (insert / update / delete) that can be serialized independently
8//! and applied to a map with automatic rollback on failure.
9//!
10//! # Complexity
11//!
12//! Backed by a sorted `Vec`, so lookup is **O(log n)** via binary search, while
13//! insert, update, and remove are **O(n)** due to element shifting. This is a
14//! deliberate trade-off for deterministic serialization and small map sizes
15//! typical in MLS group state. Do not use this as a general-purpose map for
16//! large datasets โ€” use [`std::collections::BTreeMap`] or [`std::collections::HashMap`]
17//! instead.
18//!
19//! In testing, a Vec and a BTreeMap were used to compare performance.
20//! The Vec was faster even for large maps at up to 50k entries for all
21//! operations except for insert and remove which were only half as fast.
22//! The Vec implementation is more memory efficient and significantly
23//! faster at deserialization.
24
25use std::io::{Read, Write};
26
27use tls_codec::{Deserialize, Serialize, Size};
28
29/// Error type for [`TlsMap`] operations.
30#[derive(Debug, thiserror::Error)]
31pub enum TlsMapError {
32    /// Attempted to insert a key that already exists.
33    #[error("key already exists")]
34    KeyExists,
35    /// Attempted to update or remove a key that does not exist.
36    #[error("key not found")]
37    KeyNotFound,
38    /// A TLS codec serialization or deserialization error.
39    #[error("tls codec error: {0}")]
40    Codec(#[from] tls_codec::Error),
41}
42
43/// A single key-value entry in a [`TlsMap`].
44///
45/// Wire format: `K(tls) || V(tls)` โ€” key followed by value with no delimiter.
46#[derive(Clone, PartialEq, Eq)]
47pub struct TlsMapEntry<K, V> {
48    /// The entry's key.
49    pub key: K,
50    /// The entry's value.
51    pub value: V,
52}
53
54impl<K: std::fmt::Debug, V: std::fmt::Debug> std::fmt::Debug for TlsMapEntry<K, V> {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        write!(f, "{{ key: {:?}, value: {:?} }}", self.key, self.value)
57    }
58}
59
60impl<K: Size, V: Size> Size for TlsMapEntry<K, V> {
61    #[inline]
62    fn tls_serialized_len(&self) -> usize {
63        self.key.tls_serialized_len() + self.value.tls_serialized_len()
64    }
65}
66
67impl<K: Serialize, V: Serialize> Serialize for TlsMapEntry<K, V> {
68    #[inline]
69    fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
70        let key_size = self.key.tls_serialize(writer)?;
71        let value_size = self.value.tls_serialize(writer)?;
72        Ok(key_size + value_size)
73    }
74}
75
76impl<K: Deserialize + Size, V: Deserialize + Size> Deserialize for TlsMapEntry<K, V> {
77    #[inline]
78    fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
79    where
80        Self: Sized,
81    {
82        let key = K::tls_deserialize(bytes)?;
83        let value = V::tls_deserialize(bytes)?;
84        Ok(Self { key, value })
85    }
86}
87
88/// A sorted key-value map with deterministic TLS codec serialization.
89///
90/// Entries are maintained in sorted order by key, ensuring byte-identical
91/// serialization across all implementations. Keys must be unique.
92///
93/// Wire format: `vlen(total_byte_length) || entry[0] || entry[1] || ...`
94/// where each entry is `K(tls) || V(tls)` and `vlen` is the QUIC
95/// variable-length encoding (RFC 9000 ยง16).
96///
97/// # Examples
98///
99/// ```
100/// use xmtp_mls_common::tls_map::TlsMap;
101/// use tls_codec::{Serialize, Deserialize};
102///
103/// let mut map = TlsMap::<u16, u16>::new();
104/// map.insert(3, 30).unwrap();
105/// map.insert(1, 10).unwrap();
106/// map.insert(2, 20).unwrap();
107///
108/// // Serialization is deterministic regardless of insertion order
109/// let bytes = map.tls_serialize_detached().unwrap();
110/// let deserialized = TlsMap::<u16, u16>::tls_deserialize_exact(&bytes).unwrap();
111/// assert_eq!(map, deserialized);
112/// ```
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct TlsMap<K, V> {
115    entries: Vec<TlsMapEntry<K, V>>,
116}
117
118impl<K, V> Default for TlsMap<K, V> {
119    #[inline]
120    fn default() -> Self {
121        Self {
122            entries: Vec::new(),
123        }
124    }
125}
126
127impl<K, V> TlsMap<K, V>
128where
129    K: Ord + Eq,
130{
131    /// Create an empty map.
132    ///
133    /// ```
134    /// use xmtp_mls_common::tls_map::TlsMap;
135    ///
136    /// let map = TlsMap::<u8, u16>::new();
137    /// assert!(map.is_empty());
138    /// ```
139    #[inline]
140    pub fn new() -> Self {
141        Self::default()
142    }
143
144    /// Create a map from an iterator of key-value pairs.
145    ///
146    /// Entries are sorted by key. If duplicate keys exist, the last value wins.
147    ///
148    /// ```
149    /// use xmtp_mls_common::tls_map::TlsMap;
150    ///
151    /// let map = TlsMap::from_pairs([(2, "b"), (1, "a"), (2, "c")]);
152    /// assert_eq!(map.get(&1), Some(&"a"));
153    /// assert_eq!(map.get(&2), Some(&"c")); // last value wins
154    /// assert_eq!(map.len(), 2);
155    /// ```
156    pub fn from_pairs(iter: impl IntoIterator<Item = (K, V)>) -> Self {
157        let entries = iter
158            .into_iter()
159            .collect::<std::collections::BTreeMap<K, V>>()
160            .into_iter()
161            .map(|(key, value)| TlsMapEntry { key, value })
162            .collect();
163        Self { entries }
164    }
165
166    /// Insert a key-value pair. Returns an error if the key already exists.
167    ///
168    /// ```
169    /// use xmtp_mls_common::tls_map::TlsMap;
170    ///
171    /// let mut map = TlsMap::<u8, u8>::new();
172    /// assert!(map.insert(1, 10).is_ok());
173    /// assert!(map.insert(1, 20).is_err()); // duplicate key
174    /// ```
175    #[inline]
176    pub fn insert(&mut self, key: K, value: V) -> Result<(), TlsMapError> {
177        match self.entries.binary_search_by(|e| e.key.cmp(&key)) {
178            Ok(_) => Err(TlsMapError::KeyExists),
179            Err(idx) => {
180                self.entries.insert(idx, TlsMapEntry { key, value });
181                Ok(())
182            }
183        }
184    }
185
186    /// Update an existing key's value. Returns an error if the key doesn't exist.
187    ///
188    /// ```
189    /// use xmtp_mls_common::tls_map::TlsMap;
190    ///
191    /// let mut map = TlsMap::<u8, u8>::new();
192    /// map.insert(1, 10).unwrap();
193    /// map.update(1, 20).unwrap();
194    /// assert_eq!(map.get(&1), Some(&20));
195    /// assert!(map.update(99, 0).is_err()); // key not found
196    /// ```
197    #[inline]
198    pub fn update(&mut self, key: K, value: V) -> Result<(), TlsMapError> {
199        match self.entries.binary_search_by(|e| e.key.cmp(&key)) {
200            Ok(idx) => {
201                self.entries[idx].value = value;
202                Ok(())
203            }
204            Err(_) => Err(TlsMapError::KeyNotFound),
205        }
206    }
207
208    /// Insert or update a key-value pair. Always succeeds.
209    ///
210    /// ```
211    /// use xmtp_mls_common::tls_map::TlsMap;
212    ///
213    /// let mut map = TlsMap::<u8, u8>::new();
214    /// map.set(1, 10);
215    /// map.set(1, 20); // overwrites
216    /// assert_eq!(map.get(&1), Some(&20));
217    /// assert_eq!(map.len(), 1);
218    /// ```
219    #[inline]
220    pub fn set(&mut self, key: K, value: V) {
221        match self.entries.binary_search_by(|e| e.key.cmp(&key)) {
222            Ok(idx) => self.entries[idx].value = value,
223            Err(idx) => self.entries.insert(idx, TlsMapEntry { key, value }),
224        }
225    }
226
227    /// Remove a key. Returns the removed value, or an error if the key doesn't exist.
228    ///
229    /// ```
230    /// use xmtp_mls_common::tls_map::TlsMap;
231    ///
232    /// let mut map = TlsMap::<u8, u8>::new();
233    /// map.insert(1, 10).unwrap();
234    /// assert_eq!(map.remove(&1).unwrap(), 10);
235    /// assert!(map.remove(&1).is_err()); // already removed
236    /// ```
237    #[inline]
238    pub fn remove(&mut self, key: &K) -> Result<V, TlsMapError> {
239        match self.entries.binary_search_by(|e| e.key.cmp(key)) {
240            Ok(idx) => Ok(self.entries.remove(idx).value),
241            Err(_) => Err(TlsMapError::KeyNotFound),
242        }
243    }
244
245    /// Get a value by key. Returns `None` if the key is not present.
246    ///
247    /// ```
248    /// use xmtp_mls_common::tls_map::TlsMap;
249    ///
250    /// let mut map = TlsMap::<u8, u8>::new();
251    /// map.insert(1, 10).unwrap();
252    /// assert_eq!(map.get(&1), Some(&10));
253    /// assert_eq!(map.get(&2), None);
254    /// ```
255    #[inline]
256    pub fn get(&self, key: &K) -> Option<&V> {
257        self.entries
258            .binary_search_by(|e| e.key.cmp(key))
259            .ok()
260            .map(|idx| &self.entries[idx].value)
261    }
262
263    /// Get a mutable reference to a value by key. Returns `None` if the key is not present.
264    ///
265    /// ```
266    /// use xmtp_mls_common::tls_map::TlsMap;
267    ///
268    /// let mut map = TlsMap::<u8, u8>::new();
269    /// map.insert(1, 10).unwrap();
270    /// *map.get_mut(&1).unwrap() = 20;
271    /// assert_eq!(map.get(&1), Some(&20));
272    /// ```
273    #[inline]
274    pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
275        self.entries
276            .binary_search_by(|e| e.key.cmp(key))
277            .ok()
278            .map(|idx| &mut self.entries[idx].value)
279    }
280
281    /// Check if a key exists in the map.
282    #[inline]
283    pub fn contains_key(&self, key: &K) -> bool {
284        self.entries.binary_search_by(|e| e.key.cmp(key)).is_ok()
285    }
286
287    /// Returns the number of entries in the map.
288    #[inline]
289    pub fn len(&self) -> usize {
290        self.entries.len()
291    }
292
293    /// Returns true if the map contains no entries.
294    #[inline]
295    pub fn is_empty(&self) -> bool {
296        self.entries.is_empty()
297    }
298
299    /// Iterate over key-value pairs in key-sorted order.
300    ///
301    /// ```
302    /// use xmtp_mls_common::tls_map::TlsMap;
303    ///
304    /// let map = TlsMap::from_pairs([(2, "b"), (1, "a")]);
305    /// let pairs: Vec<_> = map.iter().collect();
306    /// assert_eq!(pairs, vec![(&1, &"a"), (&2, &"b")]);
307    /// ```
308    #[inline]
309    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
310        self.entries.iter().map(|e| (&e.key, &e.value))
311    }
312
313    /// Iterate over keys in sorted order.
314    #[inline]
315    pub fn keys(&self) -> impl Iterator<Item = &K> {
316        self.entries.iter().map(|e| &e.key)
317    }
318
319    /// Iterate over values in key-sorted order.
320    #[inline]
321    pub fn values(&self) -> impl Iterator<Item = &V> {
322        self.entries.iter().map(|e| &e.value)
323    }
324}
325
326// -- TLS codec for TlsMap --
327// Delegates to Vec<TlsMapEntry<K, V>> for the QUIC variable-length wire format,
328// then validates sorted + unique keys on deserialization.
329
330impl<K: Size, V: Size> Size for TlsMap<K, V> {
331    #[inline]
332    fn tls_serialized_len(&self) -> usize {
333        self.entries.tls_serialized_len()
334    }
335}
336
337impl<K: Serialize + Size + std::fmt::Debug, V: Serialize + Size + std::fmt::Debug> Serialize
338    for TlsMap<K, V>
339{
340    #[inline]
341    fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
342        self.entries.tls_serialize(writer)
343    }
344}
345
346impl<K, V> Deserialize for TlsMap<K, V>
347where
348    K: Deserialize + Size + Ord + Eq,
349    V: Deserialize + Size,
350{
351    // implements: META-012
352    fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
353    where
354        Self: Sized,
355    {
356        let entries = Vec::<TlsMapEntry<K, V>>::tls_deserialize(bytes)?;
357
358        // Verify sorted and unique
359        for [l, r] in entries.array_windows::<2>() {
360            if l.key >= r.key {
361                return Err(tls_codec::Error::DecodingError(
362                    "TlsMap entries not sorted or contain duplicates".into(),
363                ));
364            }
365        }
366
367        Ok(Self { entries })
368    }
369}
370
371// -- Delta operations --
372
373/// Undo action for rolling back a mutation during [`TlsMap::apply_delta`].
374enum UndoAction<K, V> {
375    /// Undo an insert by removing the key.
376    Remove(K),
377    /// Undo an update by restoring the previous value.
378    Restore(K, V),
379    /// Undo a delete by re-inserting the key-value pair.
380    Insert(K, V),
381}
382
383/// A single mutation to apply to a [`TlsMap`].
384///
385/// Wire format: `u8(tag) || K(tls) [|| V(tls)]` where tag 0 = Insert, 1 = Update, 2 = Delete.
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub enum TlsMapMutation<K, V> {
388    /// Insert a new key-value pair. Fails if the key already exists.
389    Insert {
390        /// The key to insert.
391        key: K,
392        /// The value to associate with the key.
393        value: V,
394    },
395    /// Update an existing key's value. Fails if the key doesn't exist.
396    Update {
397        /// The key to update.
398        key: K,
399        /// The new value.
400        value: V,
401    },
402    /// Delete a key. Fails if the key doesn't exist.
403    Delete {
404        /// The key to delete.
405        key: K,
406    },
407}
408
409/// A list of mutations to apply atomically to a [`TlsMap`].
410///
411/// Built with a fluent API and applied via [`TlsMap::apply_delta`].
412///
413/// ```
414/// use xmtp_mls_common::tls_map::TlsMapDelta;
415///
416/// let delta = TlsMapDelta::<u8, u8>::new()
417///     .insert(1, 10)
418///     .update(2, 20)
419///     .delete(3);
420/// assert_eq!(delta.mutations.len(), 3);
421/// ```
422#[derive(Debug, Clone, PartialEq, Eq)]
423pub struct TlsMapDelta<K, V> {
424    /// The ordered list of mutations to apply.
425    pub mutations: Vec<TlsMapMutation<K, V>>,
426}
427
428impl<K, V> TlsMapDelta<K, V> {
429    /// Create an empty delta with no mutations.
430    #[inline]
431    pub fn new() -> Self {
432        Self {
433            mutations: Vec::new(),
434        }
435    }
436
437    /// Append an insert mutation. Consumes and returns `self` for chaining.
438    #[inline]
439    pub fn insert(mut self, key: K, value: V) -> Self {
440        self.mutations.push(TlsMapMutation::Insert { key, value });
441        self
442    }
443
444    /// Append an update mutation. Consumes and returns `self` for chaining.
445    #[inline]
446    pub fn update(mut self, key: K, value: V) -> Self {
447        self.mutations.push(TlsMapMutation::Update { key, value });
448        self
449    }
450
451    /// Append a delete mutation. Consumes and returns `self` for chaining.
452    #[inline]
453    pub fn delete(mut self, key: K) -> Self {
454        self.mutations.push(TlsMapMutation::Delete { key });
455        self
456    }
457}
458
459impl<K, V> Default for TlsMapDelta<K, V> {
460    #[inline]
461    fn default() -> Self {
462        Self::new()
463    }
464}
465
466impl<K, V> TlsMap<K, V>
467where
468    K: Ord + Eq + Clone,
469{
470    /// Apply a delta atomically. If any mutation fails, the map is unchanged.
471    ///
472    /// ```
473    /// use xmtp_mls_common::tls_map::{TlsMap, TlsMapDelta};
474    ///
475    /// let mut map = TlsMap::<u8, u8>::new();
476    /// map.insert(1, 10).unwrap();
477    ///
478    /// let delta = TlsMapDelta::new()
479    ///     .insert(2, 20)
480    ///     .update(1, 15)
481    ///     .delete(1);
482    ///
483    /// map.apply_delta(delta).unwrap();
484    /// assert_eq!(map.get(&2), Some(&20));
485    /// assert!(!map.contains_key(&1));
486    /// ```
487    // implements: META-013
488    pub fn apply_delta(&mut self, delta: TlsMapDelta<K, V>) -> Result<(), TlsMapError> {
489        let mut undo_stack: Vec<UndoAction<K, V>> = Vec::with_capacity(delta.mutations.len());
490
491        for mutation in delta.mutations {
492            match mutation {
493                TlsMapMutation::Insert { key, value } => {
494                    if let Err(e) = self.insert(key.clone(), value) {
495                        self.rollback(undo_stack);
496                        return Err(e);
497                    }
498                    undo_stack.push(UndoAction::Remove(key));
499                }
500                TlsMapMutation::Update { key, mut value } => match self.get_mut(&key) {
501                    Some(old) => {
502                        std::mem::swap(old, &mut value);
503                        undo_stack.push(UndoAction::Restore(key, value));
504                    }
505                    None => {
506                        self.rollback(undo_stack);
507                        return Err(TlsMapError::KeyNotFound);
508                    }
509                },
510                TlsMapMutation::Delete { key } => match self.remove(&key) {
511                    Ok(old) => {
512                        undo_stack.push(UndoAction::Insert(key, old));
513                    }
514                    Err(e) => {
515                        self.rollback(undo_stack);
516                        return Err(e);
517                    }
518                },
519            }
520        }
521
522        Ok(())
523    }
524
525    /// Replay undo actions in reverse to restore the map to its prior state.
526    fn rollback(&mut self, undo_stack: Vec<UndoAction<K, V>>) {
527        for action in undo_stack.into_iter().rev() {
528            match action {
529                UndoAction::Remove(key) => {
530                    // ignore the error as it only happens if the key is not present
531                    let _ = self.remove(&key);
532                }
533                UndoAction::Restore(key, value) | UndoAction::Insert(key, value) => {
534                    self.set(key, value);
535                }
536            }
537        }
538    }
539}
540
541// -- TLS codec for delta types --
542
543// Mutation tag: 0 = Insert, 1 = Update, 2 = Delete
544impl<K: Size, V: Size> Size for TlsMapMutation<K, V> {
545    fn tls_serialized_len(&self) -> usize {
546        1 + match self {
547            Self::Insert { key, value } | Self::Update { key, value } => {
548                key.tls_serialized_len() + value.tls_serialized_len()
549            }
550            Self::Delete { key } => key.tls_serialized_len(),
551        }
552    }
553}
554
555impl<K: Serialize + Size, V: Serialize + Size> Serialize for TlsMapMutation<K, V> {
556    fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
557        match self {
558            Self::Insert { key, value } => {
559                let mut written = 0u8.tls_serialize(writer)?;
560                written += key.tls_serialize(writer)?;
561                written += value.tls_serialize(writer)?;
562                Ok(written)
563            }
564            Self::Update { key, value } => {
565                let mut written = 1u8.tls_serialize(writer)?;
566                written += key.tls_serialize(writer)?;
567                written += value.tls_serialize(writer)?;
568                Ok(written)
569            }
570            Self::Delete { key } => {
571                let mut written = 2u8.tls_serialize(writer)?;
572                written += key.tls_serialize(writer)?;
573                Ok(written)
574            }
575        }
576    }
577}
578
579impl<K, V> Deserialize for TlsMapMutation<K, V>
580where
581    K: Deserialize + Size,
582    V: Deserialize + Size,
583{
584    fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
585    where
586        Self: Sized,
587    {
588        let tag = u8::tls_deserialize(bytes)?;
589        match tag {
590            0 => {
591                let key = K::tls_deserialize(bytes)?;
592                let value = V::tls_deserialize(bytes)?;
593                Ok(Self::Insert { key, value })
594            }
595            1 => {
596                let key = K::tls_deserialize(bytes)?;
597                let value = V::tls_deserialize(bytes)?;
598                Ok(Self::Update { key, value })
599            }
600            2 => {
601                let key = K::tls_deserialize(bytes)?;
602                Ok(Self::Delete { key })
603            }
604            _ => Err(tls_codec::Error::DecodingError(format!(
605                "unknown TlsMapMutation tag: {tag}"
606            ))),
607        }
608    }
609}
610
611impl<K: Size, V: Size> Size for TlsMapDelta<K, V> {
612    #[inline]
613    fn tls_serialized_len(&self) -> usize {
614        self.mutations.tls_serialized_len()
615    }
616}
617
618impl<K: Serialize + Size + std::fmt::Debug, V: Serialize + Size + std::fmt::Debug> Serialize
619    for TlsMapDelta<K, V>
620{
621    #[inline]
622    fn tls_serialize<W: Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
623        self.mutations.tls_serialize(writer)
624    }
625}
626
627impl<K, V> Deserialize for TlsMapDelta<K, V>
628where
629    K: Deserialize + Size,
630    V: Deserialize + Size,
631{
632    fn tls_deserialize<R: Read>(bytes: &mut R) -> Result<Self, tls_codec::Error>
633    where
634        Self: Sized,
635    {
636        let mutations = Vec::<TlsMapMutation<K, V>>::tls_deserialize(bytes)?;
637        Ok(Self { mutations })
638    }
639}
640
641// -- IntoIterator --
642
643impl<K, V> IntoIterator for TlsMap<K, V> {
644    type Item = (K, V);
645    type IntoIter =
646        std::iter::Map<std::vec::IntoIter<TlsMapEntry<K, V>>, fn(TlsMapEntry<K, V>) -> (K, V)>;
647
648    #[inline]
649    fn into_iter(self) -> Self::IntoIter {
650        self.entries.into_iter().map(|e| (e.key, e.value))
651    }
652}
653
654impl<K: Ord + Eq, V> FromIterator<(K, V)> for TlsMap<K, V> {
655    #[inline]
656    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
657        Self::from_pairs(iter)
658    }
659}
660
661#[cfg(test)]
662mod tests {
663    use super::*;
664    use proptest::prelude::*;
665
666    /// Build a TlsMap from pairs using last-wins semantics for duplicate keys,
667    /// matching real-world `HashMap::collect` behavior. Uses HashMap internally
668    /// so insertion into TlsMap happens in arbitrary (non-sorted) order.
669    fn build_map<K: Ord + Eq, V>(pairs: impl Iterator<Item = (K, V)>) -> TlsMap<K, V> {
670        pairs.collect()
671    }
672
673    /// Generates the core property-based test suite for a given K, V type pair.
674    /// Every type combination that implements the TLS traits must pass all of these.
675    /// `$n` bounds generated pairs. Counts above the key space test duplicate keys.
676    macro_rules! tls_map_tests {
677        ($mod_name:ident, $K:ty, $V:ty, $n:expr, $mutate_v:expr) => {
678            mod $mod_name {
679                use super::*;
680
681                proptest! {
682                    #[test]
683                    fn round_trip(
684                        pairs in proptest::collection::vec((any::<$K>(), any::<$V>()), 0..$n),
685                        single_key in any::<$K>(),
686                        single_value in any::<$V>(),
687                    ) {
688                        let map = build_map(pairs.clone().into_iter());
689                        let bytes = map.tls_serialize_detached().expect("round trip serialization should succeed");
690                        prop_assert_eq!(bytes.len(), map.tls_serialized_len(), "serialized size");
691                        let deserialized =
692                            TlsMap::<$K, $V>::tls_deserialize_exact(&bytes).expect("round trip deserialization should succeed");
693                        prop_assert_eq!(map, deserialized);
694
695                        let from_pairs = TlsMap::from_pairs(pairs.clone());
696                        let collected = pairs.clone().into_iter().collect::<TlsMap<$K, $V>>();
697                        let mut set_map = TlsMap::new();
698                        for (key, value) in pairs {
699                            set_map.set(key, value);
700                        }
701                        let canonical = from_pairs.tls_serialize_detached().unwrap();
702                        prop_assert_eq!(&canonical, &collected.tls_serialize_detached().unwrap(), "collect encoding");
703                        prop_assert_eq!(&canonical, &set_map.tls_serialize_detached().unwrap(), "set encoding");
704
705                        let mut single = TlsMap::<$K, $V>::new();
706                        single.insert(single_key, single_value).unwrap();
707                        let single_bytes = single.tls_serialize_detached().unwrap();
708                        prop_assert_eq!(single_bytes.len(), single.tls_serialized_len(), "single-entry size");
709                        prop_assert_eq!(
710                            single,
711                            TlsMap::<$K, $V>::tls_deserialize_exact(&single_bytes).unwrap(),
712                            "single-entry round trip"
713                        );
714                        prop_assert_eq!(TlsMap::<$K, $V>::new().tls_serialize_detached().unwrap(), vec![0], "empty encoding");
715                    }
716
717                    #[test]
718                    // verifies: META-012
719                    fn insertion_order_irrelevant(
720                        pairs in proptest::collection::hash_map(
721                            any::<$K>(), any::<$V>(), 2..$n
722                        )
723                    ) {
724                        let map_a = build_map(pairs.into_iter());
725                        let map_b = build_map(map_a.clone().into_iter().rev());
726                        prop_assert_eq!(
727                            map_a.tls_serialize_detached().unwrap(),
728                            map_b.tls_serialize_detached().unwrap()
729                        );
730                    }
731
732                    #[test]
733                    // verifies: META-012
734                    fn keys_always_sorted(
735                        pairs in proptest::collection::vec((any::<$K>(), any::<$V>()), 0..$n)
736                    ) {
737                        let expected: std::collections::HashMap<$K, $V> = pairs.clone().into_iter().collect();
738                        let map = build_map(pairs.into_iter());
739                        let keys: Vec<&$K> = map.keys().collect();
740                        for w in keys.array_windows::<2>() {
741                            prop_assert!(w[0] < w[1]);
742                        }
743                        let borrowed: Vec<_> = map.iter().collect();
744                        prop_assert_eq!(borrowed.len(), map.len(), "borrowed pairs");
745                        for (key, value) in &borrowed {
746                            prop_assert_eq!(expected.get(*key), Some(*value), "borrowed pair value");
747                        }
748                        prop_assert_eq!(map.values().collect::<Vec<_>>(), borrowed.iter().map(|(_, v)| *v).collect::<Vec<_>>(), "borrowed values");
749                        let owned: Vec<_> = map.clone().into_iter().collect();
750                        for w in owned.array_windows::<2>() {
751                            prop_assert!(w[0].0 < w[1].0, "owned order");
752                        }
753                        prop_assert_eq!(owned.len(), borrowed.len(), "owned pairs");
754                        for ((key, value), (borrowed_key, borrowed_value)) in owned.iter().zip(&borrowed) {
755                            prop_assert_eq!(key, *borrowed_key, "owned key");
756                            prop_assert_eq!(value, *borrowed_value, "owned value");
757                        }
758                        let mut one = TlsMap::<$K, $V>::new();
759                        let key = <$K>::default();
760                        let value = <$V>::default();
761                        one.set(key.clone(), value.clone());
762                        prop_assert_eq!(one.iter().collect::<Vec<_>>(), vec![(&key, &value)], "one borrowed pair");
763                        prop_assert_eq!(one.values().collect::<Vec<_>>(), vec![&value], "one borrowed value");
764                    }
765
766                    #[test]
767                    fn get_returns_inserted(
768                        pairs in proptest::collection::vec((any::<$K>(), any::<$V>()), 1..$n)
769                    ) {
770                        let mut map = build_map(pairs.clone().into_iter());
771                        let expected: std::collections::HashMap<$K, $V> = pairs
772                            .into_iter()
773                            .collect();
774                        prop_assert_eq!(map.len(), expected.len());
775                        for (k, v) in &expected {
776                            prop_assert_eq!(map.get(k), Some(v));
777                            let changed = ($mutate_v)(v.clone());
778                            prop_assert!(matches!(map.insert(k.clone(), changed), Err(TlsMapError::KeyExists)), "duplicate insert");
779                        }
780                        let mut removed = map.clone();
781                        for (k, v) in &expected {
782                            prop_assert_eq!(&removed.remove(k).unwrap(), v, "remove returns value");
783                        }
784                        prop_assert!(removed.is_empty(), "all keys removed");
785                        if let Some((key, value)) = expected.iter().next() {
786                            let changed = ($mutate_v)(value.clone());
787                            *map.get_mut(key).unwrap() = changed.clone();
788                            prop_assert_eq!(map.get(key), Some(&changed), "mutable get");
789                        }
790                    }
791
792                    #[test]
793                    fn set_upsert(pairs in proptest::collection::vec((any::<$K>(), any::<$V>()), 1..$n)) {
794                        let mut map = TlsMap::new();
795                        let mut key_set = std::collections::BTreeSet::new();
796                        for (k, v) in pairs {
797                            key_set.insert(k.clone());
798                            map.set(k.clone(), v.clone());
799                            prop_assert_eq!(map.get(&k), Some(&v));
800                            let v2 = ($mutate_v)(v);
801                            map.set(k.clone(), v2.clone());
802                            prop_assert_eq!(map.get(&k), Some(&v2));
803                            prop_assert_eq!(map.len(), key_set.len());
804                        }
805                    }
806
807                    #[test]
808                    // verifies: META-013
809                    fn delta_rollback_on_failure(
810                        pairs in proptest::collection::vec(
811                            (any::<$K>(), any::<$V>()), 1..std::cmp::min($n, 200)
812                        ),
813                        bad_key: $K,
814                    ) {
815                        let mut map = build_map(pairs.into_iter());
816                        let _ = map.remove(&bad_key);
817                        prop_assume!(!map.is_empty());
818                        let (known_key, old_value) = map.iter().next().unwrap();
819                        let known_key = known_key.clone();
820                        let changed_value = ($mutate_v)(old_value.clone());
821                        let snapshot = map.clone();
822
823                        let delta = TlsMapDelta::new()
824                            .update(known_key, changed_value)
825                            .update(bad_key, <$V>::default());
826                        prop_assert!(map.apply_delta(delta).is_err());
827                        prop_assert_eq!(map, snapshot);
828                    }
829
830                    /// Reject bytes where two entries are serialized in descending key order.
831                    #[test]
832                    // verifies: META-012
833                    fn rejects_unsorted(
834                        a in any::<$K>(),
835                        b in any::<$K>(),
836                        va in any::<$V>(),
837                        vb in any::<$V>(),
838                    ) {
839                        if a >= b { return Ok(()); }
840                        // a < b โ€” serialize b first (wrong order)
841                        let mut content = Vec::new();
842                        b.tls_serialize(&mut content).unwrap();
843                        va.tls_serialize(&mut content).unwrap();
844                        a.tls_serialize(&mut content).unwrap();
845                        vb.tls_serialize(&mut content).unwrap();
846
847                        let mut bytes = Vec::new();
848                        tls_codec::vlen::write_length(&mut bytes, content.len()).unwrap();
849                        bytes.extend_from_slice(&content);
850
851                        prop_assert!(TlsMap::<$K, $V>::tls_deserialize_exact(&bytes).is_err());
852                    }
853
854                    /// Reject bytes where the same key appears twice.
855                    #[test]
856                    // verifies: META-012
857                    fn rejects_duplicates(
858                        key in any::<$K>(),
859                        va in any::<$V>(),
860                        vb in any::<$V>(),
861                    ) {
862                        let mut content = Vec::new();
863                        key.tls_serialize(&mut content).unwrap();
864                        va.tls_serialize(&mut content).unwrap();
865                        key.tls_serialize(&mut content).unwrap();
866                        vb.tls_serialize(&mut content).unwrap();
867
868                        let mut bytes = Vec::new();
869                        tls_codec::vlen::write_length(&mut bytes, content.len()).unwrap();
870                        bytes.extend_from_slice(&content);
871
872                        prop_assert!(TlsMap::<$K, $V>::tls_deserialize_exact(&bytes).is_err());
873                    }
874
875                    /// Insert new keys, update some existing keys, update+delete others.
876                    #[test]
877                    // verifies: META-013
878                    fn delta_apply_sequence(
879                        existing in proptest::collection::hash_map(
880                            any::<$K>(), any::<$V>(), 2..std::cmp::min($n, 50)
881                        ),
882                        new_entries in proptest::collection::hash_map(
883                            any::<$K>(), any::<$V>(), 1..std::cmp::min($n, 50)
884                        ),
885                        updated_value in any::<$V>(),
886                    ) {
887                        let new_entries: std::collections::HashMap<_, _> = new_entries
888                            .into_iter()
889                            .filter(|(k, _)| !existing.contains_key(k))
890                            .collect();
891                        if new_entries.is_empty() { return Ok(()); }
892
893                        // Split existing keys: first half update-only, second half update+delete
894                        let existing_keys: Vec<_> = existing.keys().cloned().collect();
895                        let mid = existing_keys.len() / 2;
896                        let (update_only, update_and_delete) = existing_keys.split_at(mid);
897                        if update_only.is_empty() || update_and_delete.is_empty() { return Ok(()); }
898
899                        let mut map: TlsMap<$K, $V> = existing.iter()
900                            .map(|(k, v)| (k.clone(), v.clone()))
901                            .collect();
902
903                        let mut delta = TlsMapDelta::new();
904                        for (k, v) in &new_entries {
905                            delta = delta.insert(k.clone(), v.clone());
906                        }
907                        for k in &existing_keys {
908                            delta = delta.update(k.clone(), updated_value.clone());
909                        }
910                        for k in update_and_delete {
911                            delta = delta.delete(k.clone());
912                        }
913
914                        map.apply_delta(delta).unwrap();
915                        prop_assert_eq!(map.len(), new_entries.len() + update_only.len());
916                        for (k, v) in &new_entries {
917                            prop_assert_eq!(map.get(k), Some(v));
918                        }
919                        for k in update_only {
920                            prop_assert_eq!(map.get(k), Some(&updated_value));
921                        }
922                        for k in update_and_delete {
923                            prop_assert!(!map.contains_key(k));
924                        }
925                    }
926
927                    /// Nested map with known outer key type, parameterized inner.
928                    #[test]
929                    fn nested_map_round_trip(
930                        entries in proptest::collection::hash_map(
931                            any::<u64>(),
932                            proptest::collection::hash_map(
933                                any::<$K>(), any::<$V>(), 0..std::cmp::min($n, 20)
934                            ),
935                            1..10
936                        ),
937                        reverse_entries in proptest::collection::hash_map(
938                            any::<$K>(),
939                            proptest::collection::hash_map(any::<u64>(), any::<$V>(), 0..20),
940                            1..std::cmp::min($n, 10)
941                        ),
942                    ) {
943                        let outer: TlsMap<u64, TlsMap<$K, $V>> = entries
944                            .into_iter()
945                            .map(|(k, inner)| (k, inner.into_iter().collect()))
946                            .collect();
947                        let bytes = outer.tls_serialize_detached().unwrap();
948                        let deserialized = TlsMap::<u64, TlsMap<$K, $V>>::tls_deserialize_exact(&bytes).unwrap();
949                        prop_assert_eq!(outer, deserialized, "u64 outer keys");
950
951                        let reverse_outer: TlsMap<$K, TlsMap<u64, $V>> = reverse_entries
952                            .into_iter()
953                            .map(|(k, inner)| (k, inner.into_iter().collect()))
954                            .collect();
955                        let reverse_bytes = reverse_outer.tls_serialize_detached().unwrap();
956                        let reverse_result = TlsMap::<$K, TlsMap<u64, $V>>::tls_deserialize_exact(&reverse_bytes).unwrap();
957                        prop_assert_eq!(reverse_outer, reverse_result, "parameterized outer keys");
958                    }
959
960                    /// Delta (list of mutations) round-trips through serialization.
961                    #[test]
962                    fn delta_round_trip(
963                        mutations in proptest::collection::vec(
964                            (any::<$K>(), any::<$V>(), 0u8..3), 0..20
965                        ),
966                        key in any::<$K>(),
967                        value in any::<$V>(),
968                    ) {
969                        let mut delta = TlsMapDelta::new();
970                        for (key, value, tag) in mutations {
971                            delta = match tag {
972                                0 => delta.insert(key, value),
973                                1 => delta.update(key, value),
974                                _ => delta.delete(key),
975                            };
976                        }
977                        let bytes = delta.tls_serialize_detached().unwrap();
978                        let rt = TlsMapDelta::<$K, $V>::tls_deserialize_exact(&bytes).unwrap();
979                        prop_assert_eq!(delta, rt);
980                        for mutation in [
981                            TlsMapMutation::Insert { key: key.clone(), value: value.clone() },
982                            TlsMapMutation::Update { key: key.clone(), value: value.clone() },
983                            TlsMapMutation::Delete { key: key.clone() },
984                        ] {
985                            let bytes = mutation.tls_serialize_detached().unwrap();
986                            let decoded = TlsMapMutation::<$K, $V>::tls_deserialize_exact(&bytes).unwrap();
987                            prop_assert_eq!(mutation, decoded, "mutation kind round trip");
988                        }
989                        let key_len = key.tls_serialized_len();
990                        let mut key_only = Vec::new();
991                        tls_codec::vlen::write_length(&mut key_only, 1 + key_len).unwrap();
992                        3u8.tls_serialize(&mut key_only).unwrap();
993                        key.tls_serialize(&mut key_only).unwrap();
994                        prop_assert!(TlsMapDelta::<$K, $V>::tls_deserialize_exact(&key_only).is_err(), "invalid mutation tag with key only");
995
996                        let mut key_and_value = Vec::new();
997                        tls_codec::vlen::write_length(&mut key_and_value, 1 + key_len + value.tls_serialized_len()).unwrap();
998                        3u8.tls_serialize(&mut key_and_value).unwrap();
999                        key.tls_serialize(&mut key_and_value).unwrap();
1000                        value.tls_serialize(&mut key_and_value).unwrap();
1001                        prop_assert!(TlsMapDelta::<$K, $V>::tls_deserialize_exact(&key_and_value).is_err(), "invalid mutation tag with key and value");
1002                    }
1003                }
1004            }
1005        };
1006    }
1007
1008    // u8 keys: 1000 entries across 256 key space guarantees heavy collisions
1009    tls_map_tests!(u8_u8, u8, u8, 1000, |v: u8| v.wrapping_add(1));
1010    tls_map_tests!(u8_u16, u8, u16, 1000, |v: u16| v.wrapping_add(1));
1011    tls_map_tests!(u8_u32, u8, u32, 1000, |v: u32| v.wrapping_add(1));
1012    tls_map_tests!(u8_u64, u8, u64, 1000, |v: u64| v.wrapping_add(1));
1013    tls_map_tests!(u16_u8, u16, u8, 1000, |v: u8| v.wrapping_add(1));
1014    tls_map_tests!(u16_u16, u16, u16, 1000, |v: u16| v.wrapping_add(1));
1015    tls_map_tests!(u16_u32, u16, u32, 1000, |v: u32| v.wrapping_add(1));
1016    tls_map_tests!(u16_u64, u16, u64, 1000, |v: u64| v.wrapping_add(1));
1017    tls_map_tests!(u32_u8, u32, u8, 1000, |v: u8| v.wrapping_add(1));
1018    tls_map_tests!(u32_u16, u32, u16, 1000, |v: u16| v.wrapping_add(1));
1019    tls_map_tests!(u32_u32, u32, u32, 1000, |v: u32| v.wrapping_add(1));
1020    tls_map_tests!(u32_u64, u32, u64, 1000, |v: u64| v.wrapping_add(1));
1021    tls_map_tests!(u64_u8, u64, u8, 1000, |v: u8| v.wrapping_add(1));
1022    tls_map_tests!(u64_u16, u64, u16, 1000, |v: u16| v.wrapping_add(1));
1023    tls_map_tests!(u64_u32, u64, u32, 1000, |v: u32| v.wrapping_add(1));
1024    tls_map_tests!(u64_u64, u64, u64, 1000, |v: u64| v.wrapping_add(1));
1025    tls_map_tests!(u16_32_bytes, u16, [u8; 32], 1000, |v: [u8; 32]| {
1026        let mut v = v;
1027        v[0] = v[0].wrapping_add(1);
1028        v
1029    });
1030    tls_map_tests!(u64_vec, u64, Vec<u8>, 1000, |v: Vec<u8>| {
1031        let mut v = v;
1032        v.push(42);
1033        v
1034    });
1035    tls_map_tests!(_32_bytes_vec, [u8; 32], Vec<u8>, 1000, |v: Vec<u8>| {
1036        let mut v = v;
1037        v.push(1);
1038        v
1039    });
1040    tls_map_tests!(vec_vec, Vec<u8>, Vec<u8>, 1000, |v: Vec<u8>| {
1041        let mut v = v;
1042        v.push(2);
1043        v
1044    });
1045}