ic_stable_structures/
storable.rs

1use ic_principal::Principal;
2use std::borrow::{Borrow, Cow};
3use std::cmp::{Ordering, Reverse};
4use std::convert::{TryFrom, TryInto};
5use std::fmt;
6
7mod tuples;
8
9#[cfg(test)]
10mod tests;
11
12/// A trait with convenience methods for storing an element into a stable structure.
13pub trait Storable {
14    /// Converts an element into bytes.
15    ///
16    /// NOTE: `Cow` is used here to avoid unnecessary cloning.
17    fn to_bytes(&self) -> Cow<[u8]>;
18
19    /// Converts bytes into an element.
20    fn from_bytes(bytes: Cow<[u8]>) -> Self;
21
22    /// The size bounds of the type.
23    const BOUND: Bound;
24
25    /// Like `to_bytes`, but includes additional checks to ensure the element's serialized bytes
26    /// are within the element's bounds.
27    fn to_bytes_checked(&self) -> Cow<[u8]> {
28        let bytes = self.to_bytes();
29        if let Bound::Bounded {
30            max_size,
31            is_fixed_size,
32        } = Self::BOUND
33        {
34            if is_fixed_size {
35                assert_eq!(
36                    bytes.len(),
37                    max_size as usize,
38                    "expected a fixed-size element with length {} bytes, but found {} bytes",
39                    max_size,
40                    bytes.len()
41                );
42            } else {
43                assert!(
44                    bytes.len() <= max_size as usize,
45                    "expected an element with length <= {} bytes, but found {} bytes",
46                    max_size,
47                    bytes.len()
48                );
49            }
50        }
51        bytes
52    }
53}
54
55#[derive(Debug, PartialEq)]
56/// States whether the type's size is bounded or unbounded.
57pub enum Bound {
58    /// The type has no size bounds.
59    Unbounded,
60
61    /// The type has size bounds.
62    Bounded {
63        /// The maximum size, in bytes, of the type when serialized.
64        max_size: u32,
65
66        /// True if all the values of this type have fixed-width encoding.
67        /// Some data structures, such as stable vector, can take
68        /// advantage of fixed size to avoid storing an explicit entry
69        /// size.
70        ///
71        /// Examples: little-/big-endian encoding of u16/u32/u64, tuples
72        /// and arrays of fixed-size types.
73        is_fixed_size: bool,
74    },
75}
76
77impl Bound {
78    /// Returns the maximum size of the type if bounded, panics if unbounded.
79    pub const fn max_size(&self) -> u32 {
80        if let Bound::Bounded { max_size, .. } = self {
81            *max_size
82        } else {
83            panic!("Cannot get max size of unbounded type.");
84        }
85    }
86
87    /// Returns true if the type is fixed in size, false otherwise.
88    pub const fn is_fixed_size(&self) -> bool {
89        if let Bound::Bounded { is_fixed_size, .. } = self {
90            *is_fixed_size
91        } else {
92            false
93        }
94    }
95}
96
97/// Variable-size, but limited in capacity byte array.
98#[derive(Eq, Copy, Clone)]
99pub struct Blob<const N: usize> {
100    storage: [u8; N],
101    size: u32,
102}
103
104impl<const N: usize> Blob<N> {
105    /// Returns the contents of this array as a byte slice.
106    pub fn as_slice(&self) -> &[u8] {
107        &self.storage[0..self.len()]
108    }
109
110    /// Returns true if the array is empty.
111    pub fn is_empty(&self) -> bool {
112        self.len() == 0
113    }
114
115    /// Returns the actual length of this array.
116    pub fn len(&self) -> usize {
117        self.size as usize
118    }
119}
120
121impl<const N: usize> Default for Blob<N> {
122    fn default() -> Self {
123        Self {
124            storage: [0; N],
125            size: 0,
126        }
127    }
128}
129
130#[derive(Debug)]
131pub struct TryFromSliceError;
132
133impl<const N: usize> TryFrom<&[u8]> for Blob<N> {
134    type Error = TryFromSliceError;
135
136    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
137        if value.len() > N {
138            return Err(TryFromSliceError);
139        }
140        let mut result = Self::default();
141        result.storage[0..value.len()].copy_from_slice(value);
142        result.size = value.len() as u32;
143        Ok(result)
144    }
145}
146
147impl<const N: usize> AsRef<[u8]> for Blob<N> {
148    fn as_ref(&self) -> &[u8] {
149        self.as_slice()
150    }
151}
152
153impl<const N: usize> PartialEq for Blob<N> {
154    fn eq(&self, other: &Self) -> bool {
155        self.as_slice().eq(other.as_slice())
156    }
157}
158
159impl<const N: usize> PartialOrd for Blob<N> {
160    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
161        Some(self.cmp(other))
162    }
163}
164
165impl<const N: usize> Ord for Blob<N> {
166    fn cmp(&self, other: &Self) -> Ordering {
167        self.as_slice().cmp(other.as_slice())
168    }
169}
170
171impl<const N: usize> fmt::Debug for Blob<N> {
172    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
173        self.as_slice().fmt(fmt)
174    }
175}
176
177impl<const N: usize> Storable for Blob<N> {
178    fn to_bytes(&self) -> Cow<[u8]> {
179        Cow::Borrowed(self.as_slice())
180    }
181
182    fn from_bytes(bytes: Cow<[u8]>) -> Self {
183        Self::try_from(bytes.borrow()).unwrap()
184    }
185
186    const BOUND: Bound = Bound::Bounded {
187        max_size: N as u32,
188        is_fixed_size: false,
189    };
190}
191
192// NOTE: Below are a few implementations of `Storable` for common types.
193// Some of these implementations use `unwrap`, as opposed to returning a `Result`
194// with a possible error. The reason behind this decision is that these
195// `unwrap`s would be triggered in one of the following cases:
196//
197// 1) The implementation of `Storable` has a bug.
198// 2) The data being stored in the stable structure is corrupt.
199//
200// Both of these errors are irrecoverable at runtime, and given the additional
201// complexity of exposing these errors in the API of stable structures, an `unwrap`
202// in case of a detected error is preferable and safer.
203
204impl Storable for () {
205    fn to_bytes(&self) -> Cow<[u8]> {
206        Cow::Borrowed(&[])
207    }
208
209    fn from_bytes(bytes: Cow<[u8]>) -> Self {
210        assert!(bytes.is_empty());
211    }
212
213    const BOUND: Bound = Bound::Bounded {
214        max_size: 0,
215        // A `()` should in theory be fixed in size, but this flag was initially
216        // set incorrectly and it cannot be fixed to maintain backward-compatibility.
217        is_fixed_size: false,
218    };
219}
220
221impl Storable for Vec<u8> {
222    fn to_bytes(&self) -> Cow<[u8]> {
223        Cow::Borrowed(self)
224    }
225
226    fn from_bytes(bytes: Cow<[u8]>) -> Self {
227        bytes.to_vec()
228    }
229
230    const BOUND: Bound = Bound::Unbounded;
231}
232
233impl Storable for String {
234    fn to_bytes(&self) -> Cow<[u8]> {
235        Cow::Borrowed(self.as_bytes())
236    }
237
238    fn from_bytes(bytes: Cow<[u8]>) -> Self {
239        String::from_utf8(bytes.to_vec()).unwrap()
240    }
241
242    const BOUND: Bound = Bound::Unbounded;
243}
244
245impl Storable for u128 {
246    fn to_bytes(&self) -> Cow<[u8]> {
247        Cow::Owned(self.to_be_bytes().to_vec())
248    }
249
250    fn from_bytes(bytes: Cow<[u8]>) -> Self {
251        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
252    }
253
254    const BOUND: Bound = Bound::Bounded {
255        max_size: 16,
256        is_fixed_size: true,
257    };
258}
259
260impl Storable for u64 {
261    fn to_bytes(&self) -> Cow<[u8]> {
262        Cow::Owned(self.to_be_bytes().to_vec())
263    }
264
265    fn from_bytes(bytes: Cow<[u8]>) -> Self {
266        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
267    }
268
269    const BOUND: Bound = Bound::Bounded {
270        max_size: 8,
271        is_fixed_size: true,
272    };
273}
274
275impl Storable for f64 {
276    fn to_bytes(&self) -> Cow<[u8]> {
277        Cow::Owned(self.to_be_bytes().to_vec())
278    }
279
280    fn from_bytes(bytes: Cow<[u8]>) -> Self {
281        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
282    }
283
284    const BOUND: Bound = Bound::Bounded {
285        max_size: 8,
286        is_fixed_size: true,
287    };
288}
289
290impl Storable for u32 {
291    fn to_bytes(&self) -> Cow<[u8]> {
292        Cow::Owned(self.to_be_bytes().to_vec())
293    }
294
295    fn from_bytes(bytes: Cow<[u8]>) -> Self {
296        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
297    }
298
299    const BOUND: Bound = Bound::Bounded {
300        max_size: 4,
301        is_fixed_size: true,
302    };
303}
304
305impl Storable for f32 {
306    fn to_bytes(&self) -> Cow<[u8]> {
307        Cow::Owned(self.to_be_bytes().to_vec())
308    }
309
310    fn from_bytes(bytes: Cow<[u8]>) -> Self {
311        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
312    }
313
314    const BOUND: Bound = Bound::Bounded {
315        max_size: 4,
316        is_fixed_size: true,
317    };
318}
319
320impl Storable for u16 {
321    fn to_bytes(&self) -> Cow<[u8]> {
322        Cow::Owned(self.to_be_bytes().to_vec())
323    }
324
325    fn from_bytes(bytes: Cow<[u8]>) -> Self {
326        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
327    }
328
329    const BOUND: Bound = Bound::Bounded {
330        max_size: 2,
331        is_fixed_size: true,
332    };
333}
334
335impl Storable for u8 {
336    fn to_bytes(&self) -> Cow<[u8]> {
337        Cow::Owned(self.to_be_bytes().to_vec())
338    }
339
340    fn from_bytes(bytes: Cow<[u8]>) -> Self {
341        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
342    }
343
344    const BOUND: Bound = Bound::Bounded {
345        max_size: 1,
346        is_fixed_size: true,
347    };
348}
349
350impl Storable for bool {
351    fn to_bytes(&self) -> Cow<[u8]> {
352        let num: u8 = if *self { 1 } else { 0 };
353        Cow::Owned(num.to_be_bytes().to_vec())
354    }
355
356    fn from_bytes(bytes: Cow<[u8]>) -> Self {
357        assert_eq!(bytes.len(), 1);
358        match bytes[0] {
359            0 => false,
360            1 => true,
361            other => panic!("Invalid bool encoding: expected 0 or 1, found {}", other),
362        }
363    }
364
365    const BOUND: Bound = Bound::Bounded {
366        max_size: 1,
367        is_fixed_size: true,
368    };
369}
370
371impl<const N: usize> Storable for [u8; N] {
372    fn to_bytes(&self) -> Cow<[u8]> {
373        Cow::Borrowed(&self[..])
374    }
375
376    fn from_bytes(bytes: Cow<[u8]>) -> Self {
377        assert_eq!(bytes.len(), N);
378        let mut arr = [0; N];
379        arr[0..N].copy_from_slice(&bytes);
380        arr
381    }
382
383    const BOUND: Bound = Bound::Bounded {
384        max_size: N as u32,
385        is_fixed_size: true,
386    };
387}
388
389impl<T: Storable> Storable for Reverse<T> {
390    fn to_bytes(&self) -> Cow<[u8]> {
391        self.0.to_bytes()
392    }
393
394    fn from_bytes(bytes: Cow<[u8]>) -> Self {
395        Self(T::from_bytes(bytes))
396    }
397
398    const BOUND: Bound = T::BOUND;
399}
400
401impl<T: Storable> Storable for Option<T> {
402    fn to_bytes(&self) -> Cow<[u8]> {
403        match self {
404            Some(t) => {
405                let mut bytes = t.to_bytes().into_owned();
406                bytes.push(1);
407                Cow::Owned(bytes)
408            }
409            None => Cow::Borrowed(&[0]),
410        }
411    }
412
413    fn from_bytes(bytes: Cow<[u8]>) -> Self {
414        match bytes.split_last() {
415            Some((last, rest)) => match last {
416                0 => {
417                    assert!(rest.is_empty(), "Invalid Option encoding: unexpected prefix before the None marker: {rest:?}");
418                    None
419                }
420                1 => Some(T::from_bytes(Cow::Borrowed(rest))),
421                _ => panic!("Invalid Option encoding: unexpected variant marker {last}"),
422            },
423            None => panic!("Invalid Option encoding: expected at least one byte"),
424        }
425    }
426
427    const BOUND: Bound = {
428        match T::BOUND {
429            Bound::Bounded {
430                max_size,
431                is_fixed_size,
432            } => Bound::Bounded {
433                max_size: max_size + 1,
434                is_fixed_size,
435            },
436            Bound::Unbounded => Bound::Unbounded,
437        }
438    };
439}
440
441impl Storable for Principal {
442    fn to_bytes(&self) -> Cow<[u8]> {
443        Cow::Borrowed(self.as_slice())
444    }
445
446    fn from_bytes(bytes: Cow<[u8]>) -> Self {
447        Self::from_slice(&bytes)
448    }
449
450    const BOUND: Bound = Bound::Bounded {
451        max_size: Principal::MAX_LENGTH_IN_BYTES as u32,
452        is_fixed_size: false,
453    };
454}
455
456pub(crate) struct Bounds {
457    pub max_size: u32,
458    pub is_fixed_size: bool,
459}
460
461/// Returns the bounds of the given type, panics if unbounded.
462pub(crate) const fn bounds<A: Storable>() -> Bounds {
463    if let Bound::Bounded {
464        max_size,
465        is_fixed_size,
466    } = A::BOUND
467    {
468        Bounds {
469            max_size,
470            is_fixed_size,
471        }
472    } else {
473        panic!("Cannot get bounds of unbounded type.");
474    }
475}
476
477pub(crate) const fn bytes_to_store_size_bounded(bounds: &Bounds) -> u32 {
478    if bounds.is_fixed_size {
479        0
480    } else {
481        bytes_to_store_size(bounds.max_size as usize) as u32
482    }
483}
484
485const fn bytes_to_store_size(bytes_size: usize) -> usize {
486    if bytes_size <= u8::MAX as usize {
487        1
488    } else if bytes_size <= u16::MAX as usize {
489        2
490    } else {
491        4
492    }
493}