diff --git a/src/base_vec.rs b/src/base_vec.rs index c99d4a70..af6232ae 100644 --- a/src/base_vec.rs +++ b/src/base_vec.rs @@ -31,7 +31,7 @@ //! type is fixed in size, the `SLOT_SIZE` is equal to the max size. //! Otherwise, the `SLOT_SIZE` is the max size plus the number of //! bytes required to represent integers up to that max size. -use crate::storable::{bounds, bytes_to_store_size}; +use crate::storable::{bounds, bytes_to_store_size_bounded}; use crate::{ read_u32, read_u64, safe_write, write_u32, write_u64, Address, GrowFailed, Memory, Storable, }; @@ -341,7 +341,7 @@ impl fmt::Debug for BaseVec { fn slot_size() -> u32 { let t_bounds = bounds::(); - t_bounds.max_size + bytes_to_store_size(&t_bounds) + t_bounds.max_size + bytes_to_store_size_bounded(&t_bounds) } pub struct Iter<'a, T, M> diff --git a/src/storable.rs b/src/storable.rs index dfcb55e8..05fd42b2 100644 --- a/src/storable.rs +++ b/src/storable.rs @@ -50,6 +50,7 @@ pub trait Storable { } } +#[derive(Debug, PartialEq)] /// States whether the type's size is bounded or unbounded. pub enum Bound { /// The type has no size bounds. @@ -419,17 +420,17 @@ where bytes[0..a_bytes.len()].copy_from_slice(a_bytes.borrow()); bytes[a_max_size..a_max_size + b_bytes.len()].copy_from_slice(b_bytes.borrow()); - let a_size_len = bytes_to_store_size(&a_bounds) as usize; - let b_size_len = bytes_to_store_size(&b_bounds) as usize; + let a_size_len = bytes_to_store_size_bounded(&a_bounds) as usize; + let b_size_len = bytes_to_store_size_bounded(&b_bounds) as usize; let sizes_offset: usize = a_max_size + b_max_size; - encode_size( + encode_size_of_bound( &mut bytes[sizes_offset..sizes_offset + a_size_len], a_bytes.len(), &a_bounds, ); - encode_size( + encode_size_of_bound( &mut bytes[sizes_offset + a_size_len..sizes_offset + a_size_len + b_size_len], b_bytes.len(), &b_bounds, @@ -452,10 +453,13 @@ where let b_max_size = b_bounds.max_size as usize; let sizes_offset = a_max_size + b_max_size; - let a_size_len = bytes_to_store_size(&a_bounds) as usize; - let b_size_len = bytes_to_store_size(&b_bounds) as usize; - let a_len = decode_size(&bytes[sizes_offset..sizes_offset + a_size_len], &a_bounds); - let b_len = decode_size( + let a_size_len = bytes_to_store_size_bounded(&a_bounds) as usize; + let b_size_len = bytes_to_store_size_bounded(&b_bounds) as usize; + let a_len = decode_size_of_bound( + &bytes[sizes_offset..sizes_offset + a_size_len], + &a_bounds, + ); + let b_len = decode_size_of_bound( &bytes[sizes_offset + a_size_len..sizes_offset + a_size_len + b_size_len], &b_bounds, ); @@ -477,8 +481,8 @@ where let max_size = a_bounds.max_size + b_bounds.max_size - + bytes_to_store_size(&a_bounds) - + bytes_to_store_size(&b_bounds); + + bytes_to_store_size_bounded(&a_bounds) + + bytes_to_store_size_bounded(&b_bounds); let is_fixed_size = a_bounds.is_fixed_size && b_bounds.is_fixed_size; @@ -568,40 +572,272 @@ pub(crate) const fn bounds() -> Bounds { } } -fn decode_size(src: &[u8], bounds: &Bounds) -> usize { +fn decode_size_of_bound(src: &[u8], bounds: &Bounds) -> usize { if bounds.is_fixed_size { bounds.max_size as usize - } else if bounds.max_size <= u8::MAX as u32 { - src[0] as usize - } else if bounds.max_size <= u16::MAX as u32 { - u16::from_be_bytes([src[0], src[1]]) as usize } else { - u32::from_be_bytes([src[0], src[1], src[2], src[3]]) as usize + decode_size(src, bytes_to_store_size(bounds.max_size as usize)) } } -fn encode_size(dst: &mut [u8], n: usize, bounds: &Bounds) { +fn encode_size_of_bound(dst: &mut [u8], n: usize, bounds: &Bounds) { if bounds.is_fixed_size { return; } + encode_size(dst, n, bytes_to_store_size(bounds.max_size as usize)); +} - if bounds.max_size <= u8::MAX as u32 { - dst[0] = n as u8; - } else if bounds.max_size <= u16::MAX as u32 { - dst[0..2].copy_from_slice(&(n as u16).to_be_bytes()); - } else { - dst[0..4].copy_from_slice(&(n as u32).to_be_bytes()); +/// Decodes size from the beginning of `src` of length `size_len` and returns it. +fn decode_size(src: &[u8], size_len: usize) -> usize { + match size_len { + 1 => src[0] as usize, + 2 => u16::from_be_bytes([src[0], src[1]]) as usize, + _ => u32::from_be_bytes([src[0], src[1], src[2], src[3]]) as usize, } } -pub(crate) const fn bytes_to_store_size(bounds: &Bounds) -> u32 { +/// Encodes `size` at the beginning of `dst` of length `bytes_to_store_size` bytes. +fn encode_size(dst: &mut [u8], size: usize, bytes_to_store_size: usize) { + match bytes_to_store_size { + 1 => dst[0] = size as u8, + 2 => dst[0..2].copy_from_slice(&(size as u16).to_be_bytes()), + _ => dst[0..4].copy_from_slice(&(size as u32).to_be_bytes()), + }; +} + +pub(crate) const fn bytes_to_store_size_bounded(bounds: &Bounds) -> u32 { if bounds.is_fixed_size { 0 - } else if bounds.max_size <= u8::MAX as u32 { + } else { + bytes_to_store_size(bounds.max_size as usize) as u32 + } +} + +const fn bytes_to_store_size(bytes_size: usize) -> usize { + if bytes_size <= u8::MAX as usize { 1 - } else if bounds.max_size <= u16::MAX as u32 { + } else if bytes_size <= u16::MAX as usize { 2 } else { 4 } } + +fn encode_size_lengths(sizes: Vec) -> u8 { + assert!(sizes.len() <= 4); + + let mut size_lengths_byte: u8 = 0; + + for size in sizes.iter() { + let size_length = bytes_to_store_size(*size); + // Number of bytes required to store the size of every + // element is represented with 2 bits. + size_lengths_byte <<= 2; + // `size_length` can take value in {1, 2, 4}, but to + // compress it into 2 bit we will decrement its value. + size_lengths_byte += (size_length - 1) as u8; + } + + size_lengths_byte +} + +fn decode_size_lengths(mut encoded_bytes_to_store: u8, number_of_encoded_lengths: u8) -> Vec { + assert!(number_of_encoded_lengths <= 4); + + let mut bytes_to_store_sizes = vec![]; + + for _ in 0..number_of_encoded_lengths { + // The number of bytes required to store the size of every + // element is represented with 2 bits. Hence we use + // mask `11`, equivalent to 3 in the decimal system. + let mask: u8 = 3; + // The number of bytes required to store size can take value + // in {1, 2, 4}, but to compress it to 2-bit, + // when encoding we decreased the value, hence now we need + // to do inverse. + let bytes_to_store: u8 = (encoded_bytes_to_store & mask) + 1; + bytes_to_store_sizes.push(bytes_to_store); + encoded_bytes_to_store >>= 2; + } + + // Because encoding and decoding are started on the same + // end of the byte, we need to reverse `bytes_to_store_sizes` + // to get sizes in order. + bytes_to_store_sizes.reverse(); + + bytes_to_store_sizes +} + +// Encodes a serialized element `T` in a tuple. +// The element is assumed to be at the beginning of `dst`. +// Returns the number of bytes written to `dst`. +fn encode_tuple_element(dst: &mut [u8], bytes: &[u8], last: bool) -> usize { + let mut bytes_written: usize = 0; + let size = bytes.len(); + + if !last && !T::BOUND.is_fixed_size() { + encode_size(&mut dst[bytes_written..], size, bytes_to_store_size(size)); + bytes_written += bytes_to_store_size(size); + } + + dst[bytes_written..bytes_written + size].copy_from_slice(bytes); + bytes_written + size +} + +// Decodes an element `T` from a tuple. +// +// The element is assumed to be at the beginning of `src`. +// The length of the size of the element should be provided if the element is *not* fixed in size. +// +// Returns the element `T` and the number of bytes read from `src`. +fn decode_tuple_element(src: &[u8], size_len: Option, last: bool) -> (T, usize) { + let mut bytes_read: usize = 0; + + let size = if let Some(size_len) = size_len { + let size = decode_size(&src[bytes_read..], size_len as usize); + bytes_read += size_len as usize; + size + } else if let Bound::Bounded { + max_size, + is_fixed_size: true, + } = T::BOUND + { + max_size as usize + } else { + // This case should only happen for the last element. + assert!(last); + src.len() + }; + + ( + T::from_bytes(Cow::Borrowed(&src[bytes_read..bytes_read + size])), + bytes_read + size, + ) +} + +// Returns number of bytes required to store encoding of sizes for elements of type A and B. +const fn sizes_overhead(a_size: usize, b_size: usize) -> usize { + let mut sizes_overhead = 0; + + if !(A::BOUND.is_fixed_size() && B::BOUND.is_fixed_size()) { + // 1B for size lengths encoding + sizes_overhead += 1; + + if !A::BOUND.is_fixed_size() { + sizes_overhead += bytes_to_store_size(a_size); + } + + if !B::BOUND.is_fixed_size() { + sizes_overhead += bytes_to_store_size(b_size); + } + } + + sizes_overhead +} + +impl Storable for (A, B, C) +where + A: Storable, + B: Storable, + C: Storable, +{ + // Tuple (A, B, C) will be serialized in the following form: + // If A and B have fixed size + // + // Otherwise + // + fn to_bytes(&self) -> Cow<[u8]> { + let a_bytes = self.0.to_bytes(); + let a_size = a_bytes.len(); + + let b_bytes = self.1.to_bytes(); + let b_size = b_bytes.len(); + + let c_bytes = self.2.to_bytes(); + let c_size = c_bytes.len(); + + let sizes_overhead = sizes_overhead::(a_size, b_size); + + let output_size = a_size + b_size + c_size + sizes_overhead; + + let mut bytes_written = 0; + + let mut bytes = vec![0; output_size]; + + if sizes_overhead != 0 { + bytes[bytes_written] = encode_size_lengths(vec![a_size, b_size]); + bytes_written += 1; + } + + bytes_written += + encode_tuple_element::(&mut bytes[bytes_written..], a_bytes.borrow(), false); + bytes_written += + encode_tuple_element::(&mut bytes[bytes_written..], b_bytes.borrow(), false); + bytes_written += + encode_tuple_element::(&mut bytes[bytes_written..], c_bytes.borrow(), true); + + assert_eq!(bytes_written, output_size); + + Cow::Owned(bytes) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + let mut bytes_read_total = 0; + + let mut size_lengths = [None, None]; + + if !(A::BOUND.is_fixed_size() && B::BOUND.is_fixed_size()) { + let lengths = decode_size_lengths(bytes[bytes_read_total], 2); + bytes_read_total += 1; + + if !A::BOUND.is_fixed_size() { + size_lengths[0] = Some(lengths[0]); + } + + if !B::BOUND.is_fixed_size() { + size_lengths[1] = Some(lengths[1]); + } + } + + let (a, bytes_read) = + decode_tuple_element::(&bytes[bytes_read_total..], size_lengths[0], false); + bytes_read_total += bytes_read; + + let (b, bytes_read) = + decode_tuple_element::(&bytes[bytes_read_total..], size_lengths[1], false); + bytes_read_total += bytes_read; + + let (c, bytes_read) = decode_tuple_element::(&bytes[bytes_read_total..], None, true); + + bytes_read_total += bytes_read; + + assert_eq!(bytes_read_total, bytes.len()); + + (a, b, c) + } + + const BOUND: Bound = { + match (A::BOUND, B::BOUND, C::BOUND) { + (Bound::Bounded { .. }, Bound::Bounded { .. }, Bound::Bounded { .. }) => { + let a_bounds = bounds::(); + let b_bounds = bounds::(); + let c_bounds = bounds::(); + + let sizes_overhead = + sizes_overhead::(a_bounds.max_size as usize, b_bounds.max_size as usize) + as u32; + + Bound::Bounded { + max_size: a_bounds.max_size + + b_bounds.max_size + + c_bounds.max_size + + sizes_overhead, + is_fixed_size: a_bounds.is_fixed_size + && b_bounds.is_fixed_size + && c_bounds.is_fixed_size, + } + } + _ => Bound::Unbounded, + } + }; +} diff --git a/src/storable/tests.rs b/src/storable/tests.rs index e04c55e5..a735defa 100644 --- a/src/storable/tests.rs +++ b/src/storable/tests.rs @@ -13,6 +13,32 @@ proptest! { prop_assert_eq!(tuple, Storable::from_bytes(bytes)); } + #[test] + fn tuple_with_three_elements_fixed_size_roundtrip(x in any::(), y in uniform20(any::()), z in uniform20(any::())) { + let tuple = (x, y, z); + let bytes = tuple.to_bytes(); + // 8B x bytes | 20B y bytes | 20B z bytes + prop_assert_eq!(bytes.len(), 48); + prop_assert_eq!(tuple, Storable::from_bytes(bytes)); + } + + #[test] + fn tuple_with_three_unbounded_elements_roundtrip(v1 in pvec(any::(), 0..4), v2 in pvec(any::(), 0..8), v3 in pvec(any::(), 0..12)) { + let tuple = (v1, v2, v3); + assert_eq!(tuple, Storable::from_bytes(tuple.to_bytes())); + } + + #[test] + fn tuple_with_three_elements_bounded_and_unbounded_roundtrip(x in pvec(any::(), 4..5), y in any::(), z in pvec(any::(), 12..13)) { + let tuple = (x, y, z); + let tuple_copy = tuple.clone(); + let bytes = tuple_copy.to_bytes(); + // 1B sizes len | 1B x size | 4B x bytes | 0B y size | 8B y bytes | 12B z bytes + prop_assert_eq!(bytes.len(), 26); + prop_assert_eq!(tuple, Storable::from_bytes(bytes)); + } + + #[test] fn tuple_variable_width_u8_roundtrip(x in any::(), v in pvec(any::(), 0..40)) { let bytes = Blob::<48>::try_from(&v[..]).unwrap(); @@ -20,6 +46,14 @@ proptest! { prop_assert_eq!(tuple, Storable::from_bytes(tuple.to_bytes())); } + #[test] + fn tuple_with_three_elements_variable_width_u8_roundtrip(x in any::(), v1 in pvec(any::(), 0..40), v2 in pvec(any::(), 0..80)) { + let v1_bytes = Blob::<40>::try_from(&v1[..]).unwrap(); + let v2_bytes = Blob::<80>::try_from(&v2[..]).unwrap(); + let tuple = (x, v1_bytes, v2_bytes); + prop_assert_eq!(tuple, Storable::from_bytes(tuple.to_bytes())); + } + #[test] fn tuple_variable_width_u16_roundtrip(x in any::(), v in pvec(any::(), 0..40)) { let bytes = Blob::<300>::try_from(&v[..]).unwrap(); @@ -27,6 +61,15 @@ proptest! { prop_assert_eq!(tuple, Storable::from_bytes(tuple.to_bytes())); } + #[test] + fn tuple_with_three_elements_variable_width_u16_roundtrip(x in any::(), v1 in pvec(any::(), 0..40), v2 in pvec(any::(), 0..80)) { + let v1_bytes = Blob::<300>::try_from(&v1[..]).unwrap(); + let v2_bytes = Blob::<300>::try_from(&v2[..]).unwrap(); + + let tuple = (x, v1_bytes, v2_bytes); + prop_assert_eq!(tuple, Storable::from_bytes(tuple.to_bytes())); + } + #[test] fn f64_roundtrip(v in any::()) { prop_assert_eq!(v, Storable::from_bytes(v.to_bytes())); @@ -52,12 +95,33 @@ proptest! { prop_assert_eq!(v, Storable::from_bytes(v.to_bytes())); } + #[test] + fn optional_tuple_with_three_elements_roundtrip(v in proptest::option::of((any::(), uniform20(any::()), uniform20(any::())))) { + prop_assert_eq!(v, Storable::from_bytes(v.to_bytes())); + } + + #[test] + fn optional_tuple_with_three_unbounded_elements_roundtrip(v in proptest::option::of((pvec(any::(), 0..4), pvec(any::(), 0..8), pvec(any::(), 0..12)))) { + prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes())); + } + #[test] fn optional_tuple_variable_width_u8_roundtrip(v in proptest::option::of((any::(), pvec(any::(), 0..40)))) { let v = v.map(|(n, bytes)| (n, Blob::<48>::try_from(&bytes[..]).unwrap())); prop_assert_eq!(v, Storable::from_bytes(v.to_bytes())); } + #[test] + fn optional_tuple_with_three_elements_variable_width_u8_roundtrip(v in proptest::option::of((any::(), pvec(any::(), 0..40), pvec(any::(), 0..80)))) { + let v = v.map(|(n, bytes_1, bytes_2)| (n, Blob::<40>::try_from(&bytes_1[..]).unwrap(), Blob::<80>::try_from(&bytes_2[..]).unwrap())); + prop_assert_eq!(v, Storable::from_bytes(v.to_bytes())); + } + + #[test] + fn optional_tuple_with_three_elements_bounded_and_unbounded_roundtrip(v in proptest::option::of((any::(), pvec(any::(), 0..40), pvec(any::(), 0..80)))) { + prop_assert_eq!(v.clone(), Storable::from_bytes(v.to_bytes())); + } + #[test] fn principal_roundtrip(mut bytes in pvec(any::(), 0..=28), tag in proptest::prop_oneof![Just(1),Just(2),Just(3),Just(4),Just(7)]) { bytes.push(tag); @@ -175,3 +239,46 @@ fn storable_for_bool() { assert!(!bool::from_bytes(false.to_bytes())); assert!(bool::from_bytes(true.to_bytes())); } + +#[test] +fn tuple_with_three_elements_test_bound() { + // <8B a_bytes> <8B b_bytes> <8B c_bytes> + assert_eq!(<(u64, u64, u64)>::BOUND.max_size(), 24); + assert!(<(u64, u64, u64)>::BOUND.is_fixed_size()); + + // <8B a_bytes> <8B b_bytes> <8B c_bytes> + assert_eq!(<(u64, u64, Blob<8>)>::BOUND.max_size(), 24); + assert!(!<(u64, u64, Blob<8>)>::BOUND.is_fixed_size()); + + // <1B size_lengths> <1B size_a> <8B a_bytes> <0B size_b> <8B b_bytes> <8B c_bytes> + assert_eq!(<(Blob<8>, u64, u64)>::BOUND.max_size(), 26); + assert!(!<(Blob<8>, u64, u64)>::BOUND.is_fixed_size()); + + // <1B size_lengths> <0B size_a> <8B a_bytes> <1B size_b> <8B b_bytes> <8B c_bytes> + assert_eq!(<(u64, Blob<8>, u64)>::BOUND.max_size(), 26); + assert!(!<(u64, Blob<8>, u64)>::BOUND.is_fixed_size()); + + // <1B size_lengths> <1B size_a> <8B a_bytes> <1B size_b> <8B b_bytes> <8B c_bytes> + assert_eq!(<(Blob<8>, Blob<8>, u64)>::BOUND.max_size(), 27); + assert!(!<(Blob<8>, Blob<8>, u64)>::BOUND.is_fixed_size()); + + // <1B size_lengths> <1B size_a> <8B a_bytes> <0B size_b> <8B b_bytes> <8B c_bytes> + assert_eq!(<(Blob<8>, u64, Blob<8>)>::BOUND.max_size(), 26); + assert!(!<(Blob<8>, u64, Blob<8>)>::BOUND.is_fixed_size()); + + // <1B size_lengths> <0B size_a> <8B a_bytes> <1B size_b> <8B b_bytes> <8B c_bytes> + assert_eq!(<(u64, Blob<8>, Blob<8>)>::BOUND.max_size(), 26); + assert!(!<(u64, Blob<8>, Blob<8>)>::BOUND.is_fixed_size()); + + // <1B size_lengths> <1B size_a> <8B a_bytes> <1B size_b> <8B b_bytes> <8B c_bytes> + assert_eq!(<(Blob<8>, Blob<8>, Blob<8>)>::BOUND.max_size(), 27); + assert!(!<(Blob<8>, Blob<8>, Blob<8>)>::BOUND.is_fixed_size()); + + assert_eq!(<(Blob<8>, Blob<8>, String)>::BOUND, Bound::Unbounded); + assert_eq!(<(Blob<8>, String, Blob<8>)>::BOUND, Bound::Unbounded); + assert_eq!(<(String, Blob<8>, Blob<8>)>::BOUND, Bound::Unbounded); + assert_eq!(<(String, String, Blob<8>)>::BOUND, Bound::Unbounded); + assert_eq!(<(String, Blob<8>, String)>::BOUND, Bound::Unbounded); + assert_eq!(<(Blob<8>, String, String)>::BOUND, Bound::Unbounded); + assert_eq!(<(String, String, String)>::BOUND, Bound::Unbounded); +}