ex3_ic_stable_structures/
storable.rs1use std::borrow::{Borrow, Cow};
2use std::cmp::{Ordering, Reverse};
3use std::convert::{TryFrom, TryInto};
4use std::fmt;
5
6#[cfg(test)]
7mod tests;
8
9pub trait Storable {
11 fn to_bytes(&self) -> Cow<[u8]>;
15
16 fn from_bytes(bytes: Cow<[u8]>) -> Self;
18
19 const BOUND: Bound;
21
22 fn to_bytes_checked(&self) -> Cow<[u8]> {
25 let bytes = self.to_bytes();
26 if let Bound::Bounded {
27 max_size,
28 is_fixed_size,
29 } = Self::BOUND
30 {
31 if is_fixed_size {
32 assert_eq!(
33 bytes.len(),
34 max_size as usize,
35 "expected a fixed-size element with length {} bytes, but found {} bytes",
36 max_size,
37 bytes.len()
38 );
39 } else {
40 assert!(
41 bytes.len() <= max_size as usize,
42 "expected an element with length <= {} bytes, but found {} bytes",
43 max_size,
44 bytes.len()
45 );
46 }
47 }
48 bytes
49 }
50}
51
52pub enum Bound {
54 Unbounded,
56
57 Bounded {
59 max_size: u32,
61
62 is_fixed_size: bool,
70 },
71}
72
73impl Bound {
74 pub const fn max_size(&self) -> u32 {
76 if let Bound::Bounded { max_size, .. } = self {
77 *max_size
78 } else {
79 panic!("Cannot get max size of unbounded type.");
80 }
81 }
82
83 pub const fn is_fixed_size(&self) -> bool {
85 if let Bound::Bounded { is_fixed_size, .. } = self {
86 *is_fixed_size
87 } else {
88 false
89 }
90 }
91}
92
93#[derive(Eq, Copy, Clone)]
95pub struct Blob<const N: usize> {
96 storage: [u8; N],
97 size: u32,
98}
99
100impl<const N: usize> Blob<N> {
101 pub fn as_slice(&self) -> &[u8] {
103 &self.storage[0..self.len()]
104 }
105
106 pub fn is_empty(&self) -> bool {
108 self.len() == 0
109 }
110
111 pub fn len(&self) -> usize {
113 self.size as usize
114 }
115}
116
117impl<const N: usize> Default for Blob<N> {
118 fn default() -> Self {
119 Self {
120 storage: [0; N],
121 size: 0,
122 }
123 }
124}
125
126#[derive(Debug)]
127pub struct TryFromSliceError;
128
129impl<const N: usize> TryFrom<&[u8]> for Blob<N> {
130 type Error = TryFromSliceError;
131
132 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
133 if value.len() > N {
134 return Err(TryFromSliceError);
135 }
136 let mut result = Self::default();
137 result.storage[0..value.len()].copy_from_slice(value);
138 result.size = value.len() as u32;
139 Ok(result)
140 }
141}
142
143impl<const N: usize> AsRef<[u8]> for Blob<N> {
144 fn as_ref(&self) -> &[u8] {
145 self.as_slice()
146 }
147}
148
149impl<const N: usize> PartialEq for Blob<N> {
150 fn eq(&self, other: &Self) -> bool {
151 self.as_slice().eq(other.as_slice())
152 }
153}
154
155impl<const N: usize> PartialOrd for Blob<N> {
156 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
157 self.as_slice().partial_cmp(other.as_slice())
158 }
159}
160
161impl<const N: usize> Ord for Blob<N> {
162 fn cmp(&self, other: &Self) -> Ordering {
163 self.as_slice().cmp(other.as_slice())
164 }
165}
166
167impl<const N: usize> fmt::Debug for Blob<N> {
168 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
169 self.as_slice().fmt(fmt)
170 }
171}
172
173impl<const N: usize> Storable for Blob<N> {
174 fn to_bytes(&self) -> Cow<[u8]> {
175 Cow::Borrowed(self.as_slice())
176 }
177
178 fn from_bytes(bytes: Cow<[u8]>) -> Self {
179 Self::try_from(bytes.borrow()).unwrap()
180 }
181
182 const BOUND: Bound = Bound::Bounded {
183 max_size: N as u32,
184 is_fixed_size: false,
185 };
186}
187
188impl Storable for () {
201 fn to_bytes(&self) -> Cow<[u8]> {
202 Cow::Borrowed(&[])
203 }
204
205 fn from_bytes(bytes: Cow<[u8]>) -> Self {
206 assert!(bytes.is_empty());
207 }
208
209 const BOUND: Bound = Bound::Bounded {
210 max_size: 0,
211 is_fixed_size: false,
214 };
215}
216
217impl Storable for Vec<u8> {
218 fn to_bytes(&self) -> Cow<[u8]> {
219 Cow::Borrowed(self)
220 }
221
222 fn from_bytes(bytes: Cow<[u8]>) -> Self {
223 bytes.to_vec()
224 }
225
226 const BOUND: Bound = Bound::Unbounded;
227}
228
229impl Storable for String {
230 fn to_bytes(&self) -> Cow<[u8]> {
231 Cow::Borrowed(self.as_bytes())
232 }
233
234 fn from_bytes(bytes: Cow<[u8]>) -> Self {
235 String::from_utf8(bytes.to_vec()).unwrap()
236 }
237
238 const BOUND: Bound = Bound::Unbounded;
239}
240
241impl Storable for u128 {
242 fn to_bytes(&self) -> Cow<[u8]> {
243 Cow::Owned(self.to_be_bytes().to_vec())
244 }
245
246 fn from_bytes(bytes: Cow<[u8]>) -> Self {
247 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
248 }
249
250 const BOUND: Bound = Bound::Bounded {
251 max_size: 16,
252 is_fixed_size: true,
253 };
254}
255
256impl Storable for u64 {
257 fn to_bytes(&self) -> Cow<[u8]> {
258 Cow::Owned(self.to_be_bytes().to_vec())
259 }
260
261 fn from_bytes(bytes: Cow<[u8]>) -> Self {
262 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
263 }
264
265 const BOUND: Bound = Bound::Bounded {
266 max_size: 8,
267 is_fixed_size: true,
268 };
269}
270
271impl Storable for f64 {
272 fn to_bytes(&self) -> Cow<[u8]> {
273 Cow::Owned(self.to_be_bytes().to_vec())
274 }
275
276 fn from_bytes(bytes: Cow<[u8]>) -> Self {
277 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
278 }
279
280 const BOUND: Bound = Bound::Bounded {
281 max_size: 8,
282 is_fixed_size: true,
283 };
284}
285
286impl Storable for u32 {
287 fn to_bytes(&self) -> Cow<[u8]> {
288 Cow::Owned(self.to_be_bytes().to_vec())
289 }
290
291 fn from_bytes(bytes: Cow<[u8]>) -> Self {
292 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
293 }
294
295 const BOUND: Bound = Bound::Bounded {
296 max_size: 4,
297 is_fixed_size: true,
298 };
299}
300
301impl Storable for f32 {
302 fn to_bytes(&self) -> Cow<[u8]> {
303 Cow::Owned(self.to_be_bytes().to_vec())
304 }
305
306 fn from_bytes(bytes: Cow<[u8]>) -> Self {
307 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
308 }
309
310 const BOUND: Bound = Bound::Bounded {
311 max_size: 4,
312 is_fixed_size: true,
313 };
314}
315
316impl Storable for u16 {
317 fn to_bytes(&self) -> Cow<[u8]> {
318 Cow::Owned(self.to_be_bytes().to_vec())
319 }
320
321 fn from_bytes(bytes: Cow<[u8]>) -> Self {
322 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
323 }
324
325 const BOUND: Bound = Bound::Bounded {
326 max_size: 2,
327 is_fixed_size: true,
328 };
329}
330
331impl Storable for u8 {
332 fn to_bytes(&self) -> Cow<[u8]> {
333 Cow::Owned(self.to_be_bytes().to_vec())
334 }
335
336 fn from_bytes(bytes: Cow<[u8]>) -> Self {
337 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
338 }
339
340 const BOUND: Bound = Bound::Bounded {
341 max_size: 1,
342 is_fixed_size: true,
343 };
344}
345
346impl<const N: usize> Storable for [u8; N] {
347 fn to_bytes(&self) -> Cow<[u8]> {
348 Cow::Borrowed(&self[..])
349 }
350
351 fn from_bytes(bytes: Cow<[u8]>) -> Self {
352 assert_eq!(bytes.len(), N);
353 let mut arr = [0; N];
354 arr[0..N].copy_from_slice(&bytes);
355 arr
356 }
357
358 const BOUND: Bound = Bound::Bounded {
359 max_size: N as u32,
360 is_fixed_size: true,
361 };
362}
363
364impl<T: Storable> Storable for Reverse<T> {
365 fn to_bytes(&self) -> Cow<[u8]> {
366 self.0.to_bytes()
367 }
368
369 fn from_bytes(bytes: Cow<[u8]>) -> Self {
370 Self(T::from_bytes(bytes))
371 }
372
373 const BOUND: Bound = T::BOUND;
374}
375
376impl<A, B> Storable for (A, B)
377where
378 A: Storable,
379 B: Storable,
380{
381 fn to_bytes(&self) -> Cow<[u8]> {
382 match Self::BOUND {
383 Bound::Bounded { max_size, .. } => {
384 let mut bytes = vec![0; max_size as usize];
385 let a_bytes = self.0.to_bytes();
386 let b_bytes = self.1.to_bytes();
387
388 let a_bounds = bounds::<A>();
389 let b_bounds = bounds::<B>();
390
391 let a_max_size = a_bounds.max_size as usize;
392 let b_max_size = b_bounds.max_size as usize;
393
394 debug_assert!(a_bytes.len() <= a_max_size);
395 debug_assert!(b_bytes.len() <= b_max_size);
396
397 bytes[0..a_bytes.len()].copy_from_slice(a_bytes.borrow());
398 bytes[a_max_size..a_max_size + b_bytes.len()].copy_from_slice(b_bytes.borrow());
399
400 let a_size_len = bytes_to_store_size(&a_bounds) as usize;
401 let b_size_len = bytes_to_store_size(&b_bounds) as usize;
402
403 let sizes_offset: usize = a_max_size + b_max_size;
404
405 encode_size(
406 &mut bytes[sizes_offset..sizes_offset + a_size_len],
407 a_bytes.len(),
408 &a_bounds,
409 );
410 encode_size(
411 &mut bytes[sizes_offset + a_size_len..sizes_offset + a_size_len + b_size_len],
412 b_bytes.len(),
413 &b_bounds,
414 );
415
416 Cow::Owned(bytes)
417 }
418 _ => todo!("Serializing tuples with unbounded types is not yet supported."),
419 }
420 }
421
422 fn from_bytes(bytes: Cow<[u8]>) -> Self {
423 match Self::BOUND {
424 Bound::Bounded { max_size, .. } => {
425 assert_eq!(bytes.len(), max_size as usize);
426
427 let a_bounds = bounds::<A>();
428 let b_bounds = bounds::<B>();
429 let a_max_size = a_bounds.max_size as usize;
430 let b_max_size = b_bounds.max_size as usize;
431 let sizes_offset = a_max_size + b_max_size;
432
433 let a_size_len = bytes_to_store_size(&a_bounds) as usize;
434 let b_size_len = bytes_to_store_size(&b_bounds) as usize;
435 let a_len = decode_size(&bytes[sizes_offset..sizes_offset + a_size_len], &a_bounds);
436 let b_len = decode_size(
437 &bytes[sizes_offset + a_size_len..sizes_offset + a_size_len + b_size_len],
438 &b_bounds,
439 );
440
441 let a = A::from_bytes(Cow::Borrowed(&bytes[0..a_len]));
442 let b = B::from_bytes(Cow::Borrowed(&bytes[a_max_size..a_max_size + b_len]));
443
444 (a, b)
445 }
446 _ => todo!("Deserializing tuples with unbounded types is not yet supported."),
447 }
448 }
449
450 const BOUND: Bound = {
451 match (A::BOUND, B::BOUND) {
452 (Bound::Bounded { .. }, Bound::Bounded { .. }) => {
453 let a_bounds = bounds::<A>();
454 let b_bounds = bounds::<B>();
455
456 let max_size = a_bounds.max_size
457 + b_bounds.max_size
458 + bytes_to_store_size(&a_bounds)
459 + bytes_to_store_size(&b_bounds);
460
461 let is_fixed_size = a_bounds.is_fixed_size && b_bounds.is_fixed_size;
462
463 Bound::Bounded {
464 max_size,
465 is_fixed_size,
466 }
467 }
468 _ => Bound::Unbounded,
469 }
470 };
471}
472
473impl<T: Storable> Storable for Option<T> {
474 fn to_bytes(&self) -> Cow<[u8]> {
475 match self {
476 Some(t) => {
477 let mut bytes = t.to_bytes().into_owned();
478 bytes.push(1);
479 Cow::Owned(bytes)
480 }
481 None => Cow::Borrowed(&[0]),
482 }
483 }
484
485 fn from_bytes(bytes: Cow<[u8]>) -> Self {
486 match bytes.split_last() {
487 Some((last, rest)) => match last {
488 0 => {
489 assert!(rest.is_empty(), "Invalid Option encoding: unexpected prefix before the None marker: {rest:?}");
490 None
491 }
492 1 => Some(T::from_bytes(Cow::Borrowed(rest))),
493 _ => panic!("Invalid Option encoding: unexpected variant marker {last}"),
494 },
495 None => panic!("Invalid Option encoding: expected at least one byte"),
496 }
497 }
498
499 const BOUND: Bound = {
500 match T::BOUND {
501 Bound::Bounded {
502 max_size,
503 is_fixed_size,
504 } => Bound::Bounded {
505 max_size: max_size + 1,
506 is_fixed_size,
507 },
508 Bound::Unbounded => Bound::Unbounded,
509 }
510 };
511}
512
513pub(crate) struct Bounds {
514 pub max_size: u32,
515 pub is_fixed_size: bool,
516}
517
518pub(crate) const fn bounds<A: Storable>() -> Bounds {
520 if let Bound::Bounded {
521 max_size,
522 is_fixed_size,
523 } = A::BOUND
524 {
525 Bounds {
526 max_size,
527 is_fixed_size,
528 }
529 } else {
530 panic!("Cannot get bounds of unbounded type.");
531 }
532}
533
534fn decode_size(src: &[u8], bounds: &Bounds) -> usize {
535 if bounds.is_fixed_size {
536 bounds.max_size as usize
537 } else if bounds.max_size <= u8::MAX as u32 {
538 src[0] as usize
539 } else if bounds.max_size <= u16::MAX as u32 {
540 u16::from_be_bytes([src[0], src[1]]) as usize
541 } else {
542 u32::from_be_bytes([src[0], src[1], src[2], src[3]]) as usize
543 }
544}
545
546fn encode_size(dst: &mut [u8], n: usize, bounds: &Bounds) {
547 if bounds.is_fixed_size {
548 return;
549 }
550
551 if bounds.max_size <= u8::MAX as u32 {
552 dst[0] = n as u8;
553 } else if bounds.max_size <= u16::MAX as u32 {
554 dst[0..2].copy_from_slice(&(n as u16).to_be_bytes());
555 } else {
556 dst[0..4].copy_from_slice(&(n as u32).to_be_bytes());
557 }
558}
559
560pub(crate) const fn bytes_to_store_size(bounds: &Bounds) -> u32 {
561 if bounds.is_fixed_size {
562 0
563 } else if bounds.max_size <= u8::MAX as u32 {
564 1
565 } else if bounds.max_size <= u16::MAX as u32 {
566 2
567 } else {
568 4
569 }
570}