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 the element into a possibly borrowed byte slice.
15    ///
16    /// NOTE: `Cow` is used here to avoid unnecessary cloning.
17    fn to_bytes(&self) -> Cow<'_, [u8]>;
18
19    /// Converts the element into an owned byte vector.
20    ///
21    /// This method consumes `self` and avoids cloning when possible.
22    fn into_bytes(self) -> Vec<u8>;
23
24    /// Converts bytes into an element.
25    fn from_bytes(bytes: Cow<[u8]>) -> Self;
26
27    /// The size bounds of the type.
28    const BOUND: Bound;
29
30    /// Like `to_bytes`, but checks that bytes conform to declared bounds.
31    fn to_bytes_checked(&self) -> Cow<'_, [u8]> {
32        let bytes = self.to_bytes();
33        Self::check_bounds(&bytes);
34        bytes
35    }
36
37    /// Like `into_bytes`, but checks that bytes conform to declared bounds.
38    fn into_bytes_checked(self) -> Vec<u8>
39    where
40        Self: Sized,
41    {
42        let bytes = self.into_bytes();
43        Self::check_bounds(&bytes);
44        bytes
45    }
46
47    /// Validates that a byte slice fits within this type's declared bounds.
48    #[inline]
49    fn check_bounds(bytes: &[u8]) {
50        if let Bound::Bounded {
51            max_size,
52            is_fixed_size,
53        } = Self::BOUND
54        {
55            let actual = bytes.len();
56            if is_fixed_size {
57                assert_eq!(
58                    actual, max_size as usize,
59                    "expected a fixed-size element with length {} bytes, but found {} bytes",
60                    max_size, actual
61                );
62            } else {
63                assert!(
64                    actual <= max_size as usize,
65                    "expected an element with length <= {} bytes, but found {} bytes",
66                    max_size,
67                    actual
68                );
69            }
70        }
71    }
72}
73
74#[derive(Debug, PartialEq)]
75/// States whether the type's size is bounded or unbounded.
76pub enum Bound {
77    /// The type has no size bounds.
78    Unbounded,
79
80    /// The type has size bounds.
81    Bounded {
82        /// The maximum size, in bytes, of the type when serialized.
83        max_size: u32,
84
85        /// True if all the values of this type have fixed-width encoding.
86        /// Some data structures, such as stable vector, can take
87        /// advantage of fixed size to avoid storing an explicit entry
88        /// size.
89        ///
90        /// Examples: little-/big-endian encoding of u16/u32/u64, tuples
91        /// and arrays of fixed-size types.
92        is_fixed_size: bool,
93    },
94}
95
96impl Bound {
97    /// Returns the maximum size of the type if bounded, panics if unbounded.
98    pub const fn max_size(&self) -> u32 {
99        if let Bound::Bounded { max_size, .. } = self {
100            *max_size
101        } else {
102            panic!("Cannot get max size of unbounded type.");
103        }
104    }
105
106    /// Returns true if the type is fixed in size, false otherwise.
107    pub const fn is_fixed_size(&self) -> bool {
108        if let Bound::Bounded { is_fixed_size, .. } = self {
109            *is_fixed_size
110        } else {
111            false
112        }
113    }
114}
115
116/// Variable-size, but limited in capacity byte array.
117#[derive(Eq, Copy, Clone)]
118pub struct Blob<const N: usize> {
119    storage: [u8; N],
120    size: u32,
121}
122
123impl<const N: usize> Blob<N> {
124    /// Returns the contents of this array as a byte slice.
125    pub fn as_slice(&self) -> &[u8] {
126        &self.storage[0..self.len()]
127    }
128
129    /// Returns true if the array is empty.
130    pub fn is_empty(&self) -> bool {
131        self.len() == 0
132    }
133
134    /// Returns the actual length of this array.
135    pub fn len(&self) -> usize {
136        self.size as usize
137    }
138}
139
140impl<const N: usize> Default for Blob<N> {
141    fn default() -> Self {
142        Self {
143            storage: [0; N],
144            size: 0,
145        }
146    }
147}
148
149#[derive(Debug)]
150pub struct TryFromSliceError;
151
152impl<const N: usize> TryFrom<&[u8]> for Blob<N> {
153    type Error = TryFromSliceError;
154
155    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
156        if value.len() > N {
157            return Err(TryFromSliceError);
158        }
159        let mut result = Self::default();
160        result.storage[0..value.len()].copy_from_slice(value);
161        result.size = value.len() as u32;
162        Ok(result)
163    }
164}
165
166impl<const N: usize> AsRef<[u8]> for Blob<N> {
167    fn as_ref(&self) -> &[u8] {
168        self.as_slice()
169    }
170}
171
172impl<const N: usize> PartialEq for Blob<N> {
173    fn eq(&self, other: &Self) -> bool {
174        self.as_slice().eq(other.as_slice())
175    }
176}
177
178impl<const N: usize> PartialOrd for Blob<N> {
179    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
180        Some(self.cmp(other))
181    }
182}
183
184impl<const N: usize> Ord for Blob<N> {
185    fn cmp(&self, other: &Self) -> Ordering {
186        self.as_slice().cmp(other.as_slice())
187    }
188}
189
190impl<const N: usize> fmt::Debug for Blob<N> {
191    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
192        self.as_slice().fmt(fmt)
193    }
194}
195
196impl<const N: usize> Storable for Blob<N> {
197    #[inline]
198    fn to_bytes(&self) -> Cow<'_, [u8]> {
199        Cow::Borrowed(self.as_slice())
200    }
201
202    #[inline]
203    fn into_bytes(self) -> Vec<u8> {
204        self.as_slice().to_vec()
205    }
206
207    #[inline]
208    fn from_bytes(bytes: Cow<[u8]>) -> Self {
209        Self::try_from(bytes.borrow()).unwrap()
210    }
211
212    const BOUND: Bound = Bound::Bounded {
213        max_size: N as u32,
214        is_fixed_size: false,
215    };
216}
217
218/// Unbounded vector of bytes, always of length `N`.
219#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
220pub struct UnboundedVecN<const N: usize>(Vec<u8>);
221
222impl<const N: usize> UnboundedVecN<N> {
223    pub const MAX_SIZE: u32 = N as u32;
224
225    pub fn from(slice: &[u8]) -> Self {
226        assert!(
227            slice.len() <= N,
228            "expected a slice with length <= {} bytes, but found {} bytes",
229            N,
230            slice.len()
231        );
232        let mut vec = Vec::with_capacity(N);
233        vec.extend_from_slice(slice);
234        vec.resize(N, 0);
235        Self(vec)
236    }
237}
238
239impl<const N: usize> Default for UnboundedVecN<N> {
240    fn default() -> Self {
241        Self(vec![0; N])
242    }
243}
244
245impl<const N: usize> Storable for UnboundedVecN<N> {
246    fn to_bytes(&self) -> Cow<'_, [u8]> {
247        Cow::Owned(self.0.clone())
248    }
249
250    #[inline]
251    fn into_bytes(self) -> Vec<u8> {
252        self.0
253    }
254
255    #[inline]
256    fn from_bytes(bytes: Cow<[u8]>) -> Self {
257        Self(bytes.into_owned())
258    }
259
260    const BOUND: Bound = Bound::Unbounded;
261}
262
263/// Bounded vector of bytes, always of length `N`.
264#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
265pub struct BoundedVecN<const N: usize>(Vec<u8>);
266
267impl<const N: usize> BoundedVecN<N> {
268    pub const MAX_SIZE: u32 = N as u32;
269
270    pub fn from(slice: &[u8]) -> Self {
271        assert!(
272            slice.len() <= N,
273            "expected a slice with length <= {} bytes, but found {} bytes",
274            N,
275            slice.len()
276        );
277        let mut vec = Vec::with_capacity(N);
278        vec.extend_from_slice(slice);
279        vec.resize(N, 0);
280        Self(vec)
281    }
282}
283
284impl<const N: usize> Default for BoundedVecN<N> {
285    fn default() -> Self {
286        Self(vec![0; N])
287    }
288}
289
290impl<const N: usize> Storable for BoundedVecN<N> {
291    fn to_bytes(&self) -> Cow<'_, [u8]> {
292        Cow::Owned(self.0.clone())
293    }
294
295    #[inline]
296    fn into_bytes(self) -> Vec<u8> {
297        self.0
298    }
299
300    #[inline]
301    fn from_bytes(bytes: Cow<[u8]>) -> Self {
302        Self(bytes.into_owned())
303    }
304
305    const BOUND: Bound = Bound::Bounded {
306        max_size: N as u32,
307        is_fixed_size: false,
308    };
309}
310
311// NOTE: Below are a few implementations of `Storable` for common types.
312// Some of these implementations use `unwrap`, as opposed to returning a `Result`
313// with a possible error. The reason behind this decision is that these
314// `unwrap`s would be triggered in one of the following cases:
315//
316// 1) The implementation of `Storable` has a bug.
317// 2) The data being stored in the stable structure is corrupt.
318//
319// Both of these errors are irrecoverable at runtime, and given the additional
320// complexity of exposing these errors in the API of stable structures, an `unwrap`
321// in case of a detected error is preferable and safer.
322
323impl Storable for () {
324    #[inline]
325    fn to_bytes(&self) -> Cow<'_, [u8]> {
326        Cow::Borrowed(&[])
327    }
328
329    #[inline]
330    fn into_bytes(self) -> Vec<u8> {
331        Vec::new()
332    }
333
334    #[inline]
335    fn from_bytes(bytes: Cow<[u8]>) -> Self {
336        assert!(bytes.is_empty());
337    }
338
339    const BOUND: Bound = Bound::Bounded {
340        max_size: 0,
341        // A `()` should in theory be fixed in size, but this flag was initially
342        // set incorrectly and it cannot be fixed to maintain backward-compatibility.
343        is_fixed_size: false,
344    };
345}
346
347impl Storable for Vec<u8> {
348    #[inline]
349    fn to_bytes(&self) -> Cow<'_, [u8]> {
350        Cow::Borrowed(self)
351    }
352
353    #[inline]
354    fn into_bytes(self) -> Vec<u8> {
355        self
356    }
357
358    #[inline]
359    fn from_bytes(bytes: Cow<[u8]>) -> Self {
360        bytes.into_owned()
361    }
362
363    const BOUND: Bound = Bound::Unbounded;
364}
365
366impl Storable for String {
367    #[inline]
368    fn to_bytes(&self) -> Cow<'_, [u8]> {
369        Cow::Borrowed(self.as_bytes())
370    }
371
372    #[inline]
373    fn into_bytes(self) -> Vec<u8> {
374        self.into_bytes()
375    }
376
377    #[inline]
378    fn from_bytes(bytes: Cow<[u8]>) -> Self {
379        String::from_utf8(bytes.into_owned()).unwrap()
380    }
381
382    const BOUND: Bound = Bound::Unbounded;
383}
384
385impl Storable for u128 {
386    #[inline]
387    fn to_bytes(&self) -> Cow<'_, [u8]> {
388        Cow::Owned(self.into_bytes())
389    }
390
391    #[inline]
392    fn into_bytes(self) -> Vec<u8> {
393        self.to_be_bytes().to_vec()
394    }
395
396    #[inline]
397    fn from_bytes(bytes: Cow<[u8]>) -> Self {
398        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
399    }
400
401    const BOUND: Bound = Bound::Bounded {
402        max_size: 16,
403        is_fixed_size: true,
404    };
405}
406
407impl Storable for u64 {
408    #[inline]
409    fn to_bytes(&self) -> Cow<'_, [u8]> {
410        Cow::Owned(self.into_bytes())
411    }
412
413    #[inline]
414    fn into_bytes(self) -> Vec<u8> {
415        self.to_be_bytes().to_vec()
416    }
417
418    #[inline]
419    fn from_bytes(bytes: Cow<[u8]>) -> Self {
420        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
421    }
422
423    const BOUND: Bound = Bound::Bounded {
424        max_size: 8,
425        is_fixed_size: true,
426    };
427}
428
429impl Storable for f64 {
430    #[inline]
431    fn to_bytes(&self) -> Cow<'_, [u8]> {
432        Cow::Owned(self.into_bytes())
433    }
434
435    #[inline]
436    fn into_bytes(self) -> Vec<u8> {
437        self.to_be_bytes().to_vec()
438    }
439
440    #[inline]
441    fn from_bytes(bytes: Cow<[u8]>) -> Self {
442        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
443    }
444
445    const BOUND: Bound = Bound::Bounded {
446        max_size: 8,
447        is_fixed_size: true,
448    };
449}
450
451impl Storable for u32 {
452    #[inline]
453    fn to_bytes(&self) -> Cow<'_, [u8]> {
454        Cow::Owned(self.into_bytes())
455    }
456
457    #[inline]
458    fn into_bytes(self) -> Vec<u8> {
459        self.to_be_bytes().to_vec()
460    }
461
462    #[inline]
463    fn from_bytes(bytes: Cow<[u8]>) -> Self {
464        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
465    }
466
467    const BOUND: Bound = Bound::Bounded {
468        max_size: 4,
469        is_fixed_size: true,
470    };
471}
472
473impl Storable for f32 {
474    #[inline]
475    fn to_bytes(&self) -> Cow<'_, [u8]> {
476        Cow::Owned(self.into_bytes())
477    }
478
479    #[inline]
480    fn into_bytes(self) -> Vec<u8> {
481        self.to_be_bytes().to_vec()
482    }
483
484    #[inline]
485    fn from_bytes(bytes: Cow<[u8]>) -> Self {
486        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
487    }
488
489    const BOUND: Bound = Bound::Bounded {
490        max_size: 4,
491        is_fixed_size: true,
492    };
493}
494
495impl Storable for u16 {
496    #[inline]
497    fn to_bytes(&self) -> Cow<'_, [u8]> {
498        Cow::Owned(self.into_bytes())
499    }
500
501    #[inline]
502    fn into_bytes(self) -> Vec<u8> {
503        self.to_be_bytes().to_vec()
504    }
505
506    #[inline]
507    fn from_bytes(bytes: Cow<[u8]>) -> Self {
508        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
509    }
510
511    const BOUND: Bound = Bound::Bounded {
512        max_size: 2,
513        is_fixed_size: true,
514    };
515}
516
517impl Storable for u8 {
518    #[inline]
519    fn to_bytes(&self) -> Cow<'_, [u8]> {
520        Cow::Owned(self.into_bytes())
521    }
522
523    #[inline]
524    fn into_bytes(self) -> Vec<u8> {
525        self.to_be_bytes().to_vec()
526    }
527
528    #[inline]
529    fn from_bytes(bytes: Cow<[u8]>) -> Self {
530        Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
531    }
532
533    const BOUND: Bound = Bound::Bounded {
534        max_size: 1,
535        is_fixed_size: true,
536    };
537}
538
539impl Storable for bool {
540    fn to_bytes(&self) -> Cow<'_, [u8]> {
541        Cow::Owned(if *self { 1_u8 } else { 0_u8 }.to_be_bytes().to_vec())
542    }
543
544    fn into_bytes(self) -> Vec<u8> {
545        if self { 1_u8 } else { 0_u8 }.to_be_bytes().to_vec()
546    }
547
548    #[inline]
549    fn from_bytes(bytes: Cow<[u8]>) -> Self {
550        assert_eq!(bytes.len(), 1);
551        match bytes[0] {
552            0 => false,
553            1 => true,
554            other => panic!("Invalid bool encoding: expected 0 or 1, found {}", other),
555        }
556    }
557
558    const BOUND: Bound = Bound::Bounded {
559        max_size: 1,
560        is_fixed_size: true,
561    };
562}
563
564impl<const N: usize> Storable for [u8; N] {
565    #[inline]
566    fn to_bytes(&self) -> Cow<'_, [u8]> {
567        Cow::Borrowed(&self[..])
568    }
569
570    #[inline]
571    fn into_bytes(self) -> Vec<u8> {
572        self.to_vec()
573    }
574
575    #[inline]
576    fn from_bytes(bytes: Cow<[u8]>) -> Self {
577        assert_eq!(bytes.len(), N);
578        let mut arr = [0; N];
579        arr[0..N].copy_from_slice(&bytes);
580        arr
581    }
582
583    const BOUND: Bound = Bound::Bounded {
584        max_size: N as u32,
585        is_fixed_size: true,
586    };
587}
588
589impl<T: Storable> Storable for Reverse<T> {
590    #[inline]
591    fn to_bytes(&self) -> Cow<'_, [u8]> {
592        self.0.to_bytes()
593    }
594
595    #[inline]
596    fn into_bytes(self) -> Vec<u8> {
597        self.0.into_bytes()
598    }
599
600    #[inline]
601    fn from_bytes(bytes: Cow<[u8]>) -> Self {
602        Self(T::from_bytes(bytes))
603    }
604
605    const BOUND: Bound = T::BOUND;
606}
607
608impl<T: Storable> Storable for Option<T> {
609    fn to_bytes(&self) -> Cow<'_, [u8]> {
610        match self {
611            Some(t) => {
612                let mut bytes = t.to_bytes().into_owned();
613                bytes.push(1);
614                Cow::Owned(bytes)
615            }
616            None => Cow::Borrowed(&[0]),
617        }
618    }
619
620    fn into_bytes(self) -> Vec<u8> {
621        match self {
622            Some(t) => {
623                let mut bytes = t.into_bytes();
624                bytes.push(1);
625                bytes
626            }
627            None => vec![0],
628        }
629    }
630
631    #[inline]
632    fn from_bytes(bytes: Cow<[u8]>) -> Self {
633        match bytes.split_last() {
634            Some((last, rest)) => match last {
635                0 => {
636                    assert!(rest.is_empty(), "Invalid Option encoding: unexpected prefix before the None marker: {rest:?}");
637                    None
638                }
639                1 => Some(T::from_bytes(Cow::Borrowed(rest))),
640                _ => panic!("Invalid Option encoding: unexpected variant marker {last}"),
641            },
642            None => panic!("Invalid Option encoding: expected at least one byte"),
643        }
644    }
645
646    const BOUND: Bound = {
647        match T::BOUND {
648            Bound::Bounded {
649                max_size,
650                is_fixed_size,
651            } => Bound::Bounded {
652                max_size: max_size + 1,
653                is_fixed_size,
654            },
655            Bound::Unbounded => Bound::Unbounded,
656        }
657    };
658}
659
660impl Storable for Principal {
661    fn to_bytes(&self) -> Cow<'_, [u8]> {
662        Cow::Borrowed(self.as_slice())
663    }
664
665    fn into_bytes(self) -> Vec<u8> {
666        self.as_slice().to_vec()
667    }
668
669    #[inline]
670    fn from_bytes(bytes: Cow<[u8]>) -> Self {
671        Self::from_slice(&bytes)
672    }
673
674    const BOUND: Bound = Bound::Bounded {
675        max_size: Principal::MAX_LENGTH_IN_BYTES as u32,
676        is_fixed_size: false,
677    };
678}
679
680pub(crate) struct Bounds {
681    pub max_size: u32,
682    pub is_fixed_size: bool,
683}
684
685/// Returns the bounds of the given type, panics if unbounded.
686pub(crate) const fn bounds<A: Storable>() -> Bounds {
687    if let Bound::Bounded {
688        max_size,
689        is_fixed_size,
690    } = A::BOUND
691    {
692        Bounds {
693            max_size,
694            is_fixed_size,
695        }
696    } else {
697        panic!("Cannot get bounds of unbounded type.");
698    }
699}
700
701pub(crate) const fn bytes_to_store_size_bounded(bounds: &Bounds) -> u32 {
702    if bounds.is_fixed_size {
703        0
704    } else {
705        bytes_to_store_size(bounds.max_size as usize) as u32
706    }
707}
708
709const fn bytes_to_store_size(bytes_size: usize) -> usize {
710    if bytes_size <= u8::MAX as usize {
711        1
712    } else if bytes_size <= u16::MAX as usize {
713        2
714    } else {
715        4
716    }
717}