Skip to main content

sui_sdk_types/
bitmap.rs

1/// A compressed bitmap using the [Roaring bitmap compression scheme](https://roaringbitmap.org/).
2///
3/// # BCS
4///
5/// The BCS serialized form for this type is defined by the following ABNF:
6///
7/// ```text
8/// roaring-bitmap = bytes  ; where the contents of the bytes are valid
9///                         ; according to the serialized spec for
10///                         ; roaring bitmaps
11/// ```
12#[derive(Clone, Debug, Default, PartialEq)]
13pub struct Bitmap(roaring::RoaringBitmap);
14
15impl Bitmap {
16    /// Creates an empty `Bitmap`.
17    ///
18    /// # Examples
19    ///
20    /// ```rust
21    /// use sui_sdk_types::Bitmap;
22    /// let bitmap = Bitmap::new();
23    /// ```
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    /// Returns the number of distinct integers added to the set.
29    ///
30    /// # Examples
31    ///
32    /// ```rust
33    /// use sui_sdk_types::Bitmap;
34    ///
35    /// let mut bitmap = Bitmap::new();
36    /// assert_eq!(bitmap.len(), 0);
37    ///
38    /// bitmap.insert(3);
39    /// assert_eq!(bitmap.len(), 1);
40    ///
41    /// bitmap.insert(3);
42    /// bitmap.insert(4);
43    /// assert_eq!(bitmap.len(), 2);
44    /// ```
45    pub fn len(&self) -> u64 {
46        self.0.len()
47    }
48
49    /// Clears all integers in this set.
50    ///
51    /// # Examples
52    ///
53    /// ```rust
54    /// use sui_sdk_types::Bitmap;
55    ///
56    /// let mut bitmap = Bitmap::new();
57    /// bitmap.insert(1);
58    /// assert_eq!(bitmap.contains(1), true);
59    /// bitmap.clear();
60    /// assert_eq!(bitmap.contains(1), false);
61    /// ```
62    pub fn clear(&mut self) {
63        self.0.clear()
64    }
65
66    /// Returns `true` if there are no integers in this set.
67    ///
68    /// # Examples
69    ///
70    /// ```rust
71    /// use sui_sdk_types::Bitmap;
72    ///
73    /// let mut bitmap = Bitmap::new();
74    /// assert_eq!(bitmap.is_empty(), true);
75    ///
76    /// bitmap.insert(3);
77    /// assert_eq!(bitmap.is_empty(), false);
78    /// ```
79    pub fn is_empty(&self) -> bool {
80        self.0.is_empty()
81    }
82
83    /// Adds a value to the set.
84    ///
85    /// Returns whether the value was absent from the set.
86    ///
87    /// # Examples
88    ///
89    /// ```rust
90    /// use sui_sdk_types::Bitmap;
91    ///
92    /// let mut bitmap = Bitmap::new();
93    /// assert_eq!(bitmap.insert(3), true);
94    /// assert_eq!(bitmap.insert(3), false);
95    /// assert_eq!(bitmap.contains(3), true);
96    /// ```
97    pub fn insert(&mut self, value: u32) -> bool {
98        self.0.insert(value)
99    }
100
101    /// Inserts a range of values.
102    /// Returns the number of inserted values.
103    ///
104    /// # Examples
105    ///
106    /// ```rust
107    /// use sui_sdk_types::Bitmap;
108    ///
109    /// let mut bitmap = Bitmap::new();
110    /// bitmap.insert_range(2..4);
111    /// assert!(bitmap.contains(2));
112    /// assert!(bitmap.contains(3));
113    /// assert!(!bitmap.contains(4));
114    /// ```
115    pub fn insert_range<R>(&mut self, range: R) -> u64
116    where
117        R: std::ops::RangeBounds<u32>,
118    {
119        self.0.insert_range(range)
120    }
121
122    /// Removes a value from the set. Returns `true` if the value was present in the set.
123    ///
124    /// # Examples
125    ///
126    /// ```rust
127    /// use sui_sdk_types::Bitmap;
128    ///
129    /// let mut bitmap = Bitmap::new();
130    /// bitmap.insert(3);
131    /// assert_eq!(bitmap.remove(3), true);
132    /// assert_eq!(bitmap.remove(3), false);
133    /// assert_eq!(bitmap.contains(3), false);
134    /// ```
135    pub fn remove(&mut self, value: u32) -> bool {
136        self.0.remove(value)
137    }
138
139    /// Returns `true` if this set contains the specified integer.
140    ///
141    /// # Examples
142    ///
143    /// ```rust
144    /// use sui_sdk_types::Bitmap;
145    ///
146    /// let mut bitmap = Bitmap::new();
147    /// bitmap.insert(1);
148    /// assert_eq!(bitmap.contains(0), false);
149    /// assert_eq!(bitmap.contains(1), true);
150    /// assert_eq!(bitmap.contains(100), false);
151    /// ```
152    pub fn contains(&self, value: u32) -> bool {
153        self.0.contains(value)
154    }
155
156    /// Iterator over each value stored in the Bitmap, guarantees values are ordered by value.
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// use core::iter::FromIterator;
162    /// use sui_sdk_types::Bitmap;
163    ///
164    /// let bitmap = (1..3).collect::<Bitmap>();
165    /// let mut iter = bitmap.iter();
166    ///
167    /// assert_eq!(iter.next(), Some(1));
168    /// assert_eq!(iter.next(), Some(2));
169    /// assert_eq!(iter.next(), None);
170    /// ```
171    pub fn iter(&self) -> impl Iterator<Item = u32> + use<'_> {
172        self.0.iter()
173    }
174
175    /// Deserialize a bitmap into memory from [the standard Roaring on-disk
176    /// format][format]. This is compatible with the official C/C++, Java and
177    /// Go implementations. This method checks that all of the internal values
178    /// are valid.
179    ///
180    /// [format]: https://github.com/RoaringBitmap/RoaringFormatSpec
181    ///
182    /// # Examples
183    ///
184    /// ```rust
185    /// use sui_sdk_types::Bitmap;
186    ///
187    /// let bitmap1: Bitmap = (1..4).collect();
188    /// let mut bytes = vec![];
189    /// bitmap1.serialize_into(&mut bytes).unwrap();
190    /// let bitmap2 = Bitmap::deserialize_from(&bytes[..]).unwrap();
191    ///
192    /// assert_eq!(bitmap1, bitmap2);
193    /// ```
194    #[cfg(feature = "serde")]
195    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
196    pub fn deserialize_from<R: std::io::Read>(reader: R) -> std::io::Result<Self> {
197        roaring::RoaringBitmap::deserialize_from(reader).map(Self)
198    }
199
200    /// Serialize this bitmap into [the standard Roaring on-disk format][format].
201    /// This is compatible with the official C/C++, Java and Go implementations.
202    ///
203    /// [format]: https://github.com/RoaringBitmap/RoaringFormatSpec
204    ///
205    /// # Examples
206    ///
207    /// ```rust
208    /// use sui_sdk_types::Bitmap;
209    ///
210    /// let bitmap1: Bitmap = (1..4).collect();
211    /// let mut bytes = vec![];
212    /// bitmap1.serialize_into(&mut bytes).unwrap();
213    /// let bitmap2 = Bitmap::deserialize_from(&bytes[..]).unwrap();
214    ///
215    /// assert_eq!(bitmap1, bitmap2);
216    /// ```
217    #[cfg(feature = "serde")]
218    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
219    pub fn serialize_into<W: std::io::Write>(&self, writer: W) -> std::io::Result<()> {
220        self.0.serialize_into(writer)
221    }
222}
223
224impl FromIterator<u32> for Bitmap {
225    fn from_iter<I: IntoIterator<Item = u32>>(iterator: I) -> Bitmap {
226        let mut bitmap = Self::new();
227        bitmap.extend(iterator);
228        bitmap
229    }
230}
231
232impl<'a> FromIterator<&'a u32> for Bitmap {
233    fn from_iter<I: IntoIterator<Item = &'a u32>>(iterator: I) -> Bitmap {
234        let mut bitmap = Self::new();
235        bitmap.extend(iterator);
236        bitmap
237    }
238}
239
240impl Extend<u32> for Bitmap {
241    fn extend<I: IntoIterator<Item = u32>>(&mut self, values: I) {
242        self.0.extend(values)
243    }
244}
245
246impl<'a> Extend<&'a u32> for Bitmap {
247    fn extend<I: IntoIterator<Item = &'a u32>>(&mut self, values: I) {
248        self.extend(values.into_iter().copied());
249    }
250}
251
252#[cfg(feature = "serde")]
253#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
254impl serde::Serialize for Bitmap {
255    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
256    where
257        S: serde::Serializer,
258    {
259        use serde_with::SerializeAs;
260
261        let mut bytes = vec![];
262
263        self.serialize_into(&mut bytes)
264            .map_err(serde::ser::Error::custom)?;
265
266        if serializer.is_human_readable() {
267            let b64 = <base64ct::Base64 as base64ct::Encoding>::encode_string(&bytes);
268            serde::Serialize::serialize(&b64, serializer)
269        } else {
270            serde_with::Bytes::serialize_as(&bytes, serializer)
271        }
272    }
273}
274
275#[cfg(feature = "serde")]
276#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
277impl<'de> serde::Deserialize<'de> for Bitmap {
278    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
279    where
280        D: serde::Deserializer<'de>,
281    {
282        use serde_with::DeserializeAs;
283
284        if deserializer.is_human_readable() {
285            let b64: std::borrow::Cow<'de, str> = serde::Deserialize::deserialize(deserializer)?;
286            let bytes = <base64ct::Base64 as base64ct::Encoding>::decode_vec(&b64)
287                .map_err(serde::de::Error::custom)?;
288            deserialize_canonical_bitmap(&bytes).map_err(serde::de::Error::custom)
289        } else {
290            let bytes: std::borrow::Cow<'de, [u8]> =
291                serde_with::Bytes::deserialize_as(deserializer)?;
292            deserialize_canonical_bitmap(&bytes).map_err(serde::de::Error::custom)
293        }
294    }
295}
296
297/// Deserialize a roaring bitmap from `bytes` and ensure the entire input
298/// was consumed. The library documents BCS canonical encoding ("one-to-one
299/// correspondence between in-memory values and valid byte representations")
300/// as a guarantee, so any trailing bytes after the canonical roaring
301/// payload must be rejected — otherwise an attacker can produce multiple
302/// distinct BCS encodings of the same logical bitmap (and, by extension,
303/// of any signature type that contains one).
304#[cfg(feature = "serde")]
305fn deserialize_canonical_bitmap(bytes: &[u8]) -> std::io::Result<Bitmap> {
306    let mut reader: &[u8] = bytes;
307    let bitmap = Bitmap::deserialize_from(&mut reader)?;
308    if !reader.is_empty() {
309        return Err(std::io::Error::new(
310            std::io::ErrorKind::InvalidData,
311            format!(
312                "trailing bytes after roaring bitmap encoding: {} byte(s) remaining",
313                reader.len(),
314            ),
315        ));
316    }
317    Ok(bitmap)
318}
319
320#[cfg(feature = "proptest")]
321#[cfg_attr(doc_cfg, doc(cfg(feature = "proptest")))]
322impl proptest::arbitrary::Arbitrary for Bitmap {
323    type Parameters = ();
324    type Strategy = proptest::strategy::BoxedStrategy<Self>;
325
326    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
327        use proptest::collection::vec;
328        use proptest::prelude::*;
329
330        vec(any::<u32>(), 0..32).prop_map(Self::from_iter).boxed()
331    }
332}
333
334#[cfg(test)]
335mod test {
336    use super::*;
337
338    use base64ct::Encoding;
339
340    #[test]
341    fn test_unique_deserialize() {
342        let raw = "OjAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAFoAAABcAAAAXgAAAGAAAABiAAAAZAAAAGYAAABoAAAAagAAAAEAAQABAAEAAQABAAEAAQABAAEA";
343        let bytes = base64ct::Base64::decode_vec(raw).unwrap();
344
345        roaring::RoaringBitmap::deserialize_from(&bytes[..]).unwrap_err();
346        Bitmap::deserialize_from(&bytes[..]).unwrap_err();
347    }
348
349    // Regression test: padding the BCS-encoded bitmap field with arbitrary
350    // bytes used to deserialize successfully and produce a logically
351    // equivalent `Bitmap`, breaking the documented BCS canonical-encoding
352    // guarantee. Any input that is not a fully-consumed canonical roaring
353    // payload must now be rejected.
354    #[test]
355    fn bcs_deserialize_rejects_trailing_bytes() {
356        let bitmap: Bitmap = (1..4).collect();
357        let mut canonical = Vec::new();
358        bitmap.serialize_into(&mut canonical).unwrap();
359
360        // The canonical encoding round-trips.
361        let canonical_bcs = bcs::to_bytes::<Vec<u8>>(&canonical).unwrap();
362        let decoded: Bitmap = bcs::from_bytes(&canonical_bcs).unwrap();
363        assert_eq!(decoded, bitmap);
364
365        // Padding the bytes field with junk and updating the length prefix
366        // produces a non-canonical encoding that decodes to the same logical
367        // value via roaring::deserialize_from but contains trailing bytes.
368        let mut padded = canonical.clone();
369        padded.extend_from_slice(&[0xff, 0xff, 0xff, 0xff]);
370        let padded_bcs = bcs::to_bytes::<Vec<u8>>(&padded).unwrap();
371        let err = bcs::from_bytes::<Bitmap>(&padded_bcs).unwrap_err();
372        assert!(
373            err.to_string().contains("trailing bytes"),
374            "unexpected error: {err}"
375        );
376    }
377}