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    #[inline]
183    fn from_bytes(bytes: Cow<[u8]>) -> Self {
184        Self::try_from(bytes.borrow()).unwrap()
185    }
186
187    const BOUND: Bound = Bound::Bounded {
188        max_size: N as u32,
189        is_fixed_size: false,
190    };
191}
192
193/// Unbounded vector of bytes, always of length `N`.
194#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
195pub struct UnboundedVecN<const N: usize>(Vec<u8>);
196
197impl<const N: usize> UnboundedVecN<N> {
198    pub const MAX_SIZE: u32 = N as u32;
199
200    pub fn from(slice: &[u8]) -> Self {
201        assert!(
202            slice.len() <= N,
203            "expected a slice with length <= {} bytes, but found {} bytes",
204            N,
205            slice.len()
206        );
207        let mut vec = Vec::with_capacity(N);
208        vec.extend_from_slice(slice);
209        vec.resize(N, 0);
210        Self(vec)
211    }
212}
213
214impl<const N: usize> Default for UnboundedVecN<N> {
215    fn default() -> Self {
216        Self(vec![0; N])
217    }
218}
219
220impl<const N: usize> Storable for UnboundedVecN<N> {
221    fn to_bytes(&self) -> Cow<[u8]> {
222        Cow::Owned(self.0.clone())
223    }
224
225    #[inline]
226    fn from_bytes(bytes: Cow<[u8]>) -> Self {
227        Self(bytes.into_owned())
228    }
229
230    const BOUND: Bound = Bound::Unbounded;
231}
232
233/// Bounded vector of bytes, always of length `N`.
234#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
235pub struct BoundedVecN<const N: usize>(Vec<u8>);
236
237impl<const N: usize> BoundedVecN<N> {
238    pub const MAX_SIZE: u32 = N as u32;
239
240    pub fn from(slice: &[u8]) -> Self {
241        assert!(
242            slice.len() <= N,
243            "expected a slice with length <= {} bytes, but found {} bytes",
244            N,
245            slice.len()
246        );
247        let mut vec = Vec::with_capacity(N);
248        vec.extend_from_slice(slice);
249        vec.resize(N, 0);
250        Self(vec)
251    }
252}
253
254impl<const N: usize> Default for BoundedVecN<N> {
255    fn default() -> Self {
256        Self(vec![0; N])
257    }
258}
259
260impl<const N: usize> Storable for BoundedVecN<N> {
261    fn to_bytes(&self) -> Cow<[u8]> {
262        Cow::Owned(self.0.clone())
263    }
264
265    #[inline]
266    fn from_bytes(bytes: Cow<[u8]>) -> Self {
267        Self(bytes.into_owned())
268    }
269
270    const BOUND: Bound = Bound::Bounded {
271        max_size: N as u32,
272        is_fixed_size: false,
273    };
274}
275
276// NOTE: Below are a few implementations of `Storable` for common types.
277// Some of these implementations use `unwrap`, as opposed to returning a `Result`
278// with a possible error. The reason behind this decision is that these
279// `unwrap`s would be triggered in one of the following cases:
280//
281// 1) The implementation of `Storable` has a bug.
282// 2) The data being stored in the stable structure is corrupt.
283//
284// Both of these errors are irrecoverable at runtime, and given the additional
285// complexity of exposing these errors in the API of stable structures, an `unwrap`
286// in case of a detected error is preferable and safer.
287
288impl Storable for () {
289    fn to_bytes(&self) -> Cow<[u8]> {
290        Cow::Borrowed(&[])
291    }
292
293    #[inline]
294    fn from_bytes(bytes: Cow<[u8]>) -> Self {
295        assert!(bytes.is_empty());
296    }
297
298    const BOUND: Bound = Bound::Bounded {
299        max_size: 0,
300        // A `()` should in theory be fixed in size, but this flag was initially
301        // set incorrectly and it cannot be fixed to maintain backward-compatibility.
302        is_fixed_size: false,
303    };
304}
305
306impl Storable for Vec<u8> {
307    fn to_bytes(&self) -> Cow<[u8]> {
308        Cow::Borrowed(self)
309    }
310
311    #[inline]
312    fn from_bytes(bytes: Cow<[u8]>) -> Self {
313        bytes.into_owned()
314    }
315
316    const BOUND: Bound = Bound::Unbounded;
317}
318
319impl Storable for String {
320    fn to_bytes(&self) -> Cow<[u8]> {
321        Cow::Borrowed(self.as_bytes())
322    }
323
324    #[inline]
325    fn from_bytes(bytes: Cow<[u8]>) -> Self {
326        String::from_utf8(bytes.into_owned()).unwrap()
327    }
328
329    const BOUND: Bound = Bound::Unbounded;
330}
331
332impl Storable for u128 {
333    fn to_bytes(&self) -> Cow<[u8]> {
334        Cow::Owned(self.to_be_bytes().to_vec())
335    }
336
337    #[inline]
338    fn from_bytes(bytes: Cow<[u8]>) -> Self {
339        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
340    }
341
342    const BOUND: Bound = Bound::Bounded {
343        max_size: 16,
344        is_fixed_size: true,
345    };
346}
347
348impl Storable for u64 {
349    fn to_bytes(&self) -> Cow<[u8]> {
350        Cow::Owned(self.to_be_bytes().to_vec())
351    }
352
353    #[inline]
354    fn from_bytes(bytes: Cow<[u8]>) -> Self {
355        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
356    }
357
358    const BOUND: Bound = Bound::Bounded {
359        max_size: 8,
360        is_fixed_size: true,
361    };
362}
363
364impl Storable for f64 {
365    fn to_bytes(&self) -> Cow<[u8]> {
366        Cow::Owned(self.to_be_bytes().to_vec())
367    }
368
369    #[inline]
370    fn from_bytes(bytes: Cow<[u8]>) -> Self {
371        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
372    }
373
374    const BOUND: Bound = Bound::Bounded {
375        max_size: 8,
376        is_fixed_size: true,
377    };
378}
379
380impl Storable for u32 {
381    fn to_bytes(&self) -> Cow<[u8]> {
382        Cow::Owned(self.to_be_bytes().to_vec())
383    }
384
385    #[inline]
386    fn from_bytes(bytes: Cow<[u8]>) -> Self {
387        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
388    }
389
390    const BOUND: Bound = Bound::Bounded {
391        max_size: 4,
392        is_fixed_size: true,
393    };
394}
395
396impl Storable for f32 {
397    fn to_bytes(&self) -> Cow<[u8]> {
398        Cow::Owned(self.to_be_bytes().to_vec())
399    }
400
401    #[inline]
402    fn from_bytes(bytes: Cow<[u8]>) -> Self {
403        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
404    }
405
406    const BOUND: Bound = Bound::Bounded {
407        max_size: 4,
408        is_fixed_size: true,
409    };
410}
411
412impl Storable for u16 {
413    fn to_bytes(&self) -> Cow<[u8]> {
414        Cow::Owned(self.to_be_bytes().to_vec())
415    }
416
417    #[inline]
418    fn from_bytes(bytes: Cow<[u8]>) -> Self {
419        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
420    }
421
422    const BOUND: Bound = Bound::Bounded {
423        max_size: 2,
424        is_fixed_size: true,
425    };
426}
427
428impl Storable for u8 {
429    fn to_bytes(&self) -> Cow<[u8]> {
430        Cow::Owned(self.to_be_bytes().to_vec())
431    }
432
433    #[inline]
434    fn from_bytes(bytes: Cow<[u8]>) -> Self {
435        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
436    }
437
438    const BOUND: Bound = Bound::Bounded {
439        max_size: 1,
440        is_fixed_size: true,
441    };
442}
443
444impl Storable for bool {
445    fn to_bytes(&self) -> Cow<[u8]> {
446        let num: u8 = if *self { 1 } else { 0 };
447        Cow::Owned(num.to_be_bytes().to_vec())
448    }
449
450    #[inline]
451    fn from_bytes(bytes: Cow<[u8]>) -> Self {
452        assert_eq!(bytes.len(), 1);
453        match bytes[0] {
454            0 => false,
455            1 => true,
456            other => panic!("Invalid bool encoding: expected 0 or 1, found {}", other),
457        }
458    }
459
460    const BOUND: Bound = Bound::Bounded {
461        max_size: 1,
462        is_fixed_size: true,
463    };
464}
465
466impl<const N: usize> Storable for [u8; N] {
467    fn to_bytes(&self) -> Cow<[u8]> {
468        Cow::Borrowed(&self[..])
469    }
470
471    #[inline]
472    fn from_bytes(bytes: Cow<[u8]>) -> Self {
473        assert_eq!(bytes.len(), N);
474        let mut arr = [0; N];
475        arr[0..N].copy_from_slice(&bytes);
476        arr
477    }
478
479    const BOUND: Bound = Bound::Bounded {
480        max_size: N as u32,
481        is_fixed_size: true,
482    };
483}
484
485impl<T: Storable> Storable for Reverse<T> {
486    fn to_bytes(&self) -> Cow<[u8]> {
487        self.0.to_bytes()
488    }
489
490    #[inline]
491    fn from_bytes(bytes: Cow<[u8]>) -> Self {
492        Self(T::from_bytes(bytes))
493    }
494
495    const BOUND: Bound = T::BOUND;
496}
497
498impl<T: Storable> Storable for Option<T> {
499    fn to_bytes(&self) -> Cow<[u8]> {
500        match self {
501            Some(t) => {
502                let mut bytes = t.to_bytes().into_owned();
503                bytes.push(1);
504                Cow::Owned(bytes)
505            }
506            None => Cow::Borrowed(&[0]),
507        }
508    }
509
510    #[inline]
511    fn from_bytes(bytes: Cow<[u8]>) -> Self {
512        match bytes.split_last() {
513            Some((last, rest)) => match last {
514                0 => {
515                    assert!(rest.is_empty(), "Invalid Option encoding: unexpected prefix before the None marker: {rest:?}");
516                    None
517                }
518                1 => Some(T::from_bytes(Cow::Borrowed(rest))),
519                _ => panic!("Invalid Option encoding: unexpected variant marker {last}"),
520            },
521            None => panic!("Invalid Option encoding: expected at least one byte"),
522        }
523    }
524
525    const BOUND: Bound = {
526        match T::BOUND {
527            Bound::Bounded {
528                max_size,
529                is_fixed_size,
530            } => Bound::Bounded {
531                max_size: max_size + 1,
532                is_fixed_size,
533            },
534            Bound::Unbounded => Bound::Unbounded,
535        }
536    };
537}
538
539impl Storable for Principal {
540    fn to_bytes(&self) -> Cow<[u8]> {
541        Cow::Borrowed(self.as_slice())
542    }
543
544    #[inline]
545    fn from_bytes(bytes: Cow<[u8]>) -> Self {
546        Self::from_slice(&bytes)
547    }
548
549    const BOUND: Bound = Bound::Bounded {
550        max_size: Principal::MAX_LENGTH_IN_BYTES as u32,
551        is_fixed_size: false,
552    };
553}
554
555pub(crate) struct Bounds {
556    pub max_size: u32,
557    pub is_fixed_size: bool,
558}
559
560/// Returns the bounds of the given type, panics if unbounded.
561pub(crate) const fn bounds<A: Storable>() -> Bounds {
562    if let Bound::Bounded {
563        max_size,
564        is_fixed_size,
565    } = A::BOUND
566    {
567        Bounds {
568            max_size,
569            is_fixed_size,
570        }
571    } else {
572        panic!("Cannot get bounds of unbounded type.");
573    }
574}
575
576pub(crate) const fn bytes_to_store_size_bounded(bounds: &Bounds) -> u32 {
577    if bounds.is_fixed_size {
578        0
579    } else {
580        bytes_to_store_size(bounds.max_size as usize) as u32
581    }
582}
583
584const fn bytes_to_store_size(bytes_size: usize) -> usize {
585    if bytes_size <= u8::MAX as usize {
586        1
587    } else if bytes_size <= u16::MAX as usize {
588        2
589    } else {
590        4
591    }
592}