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}