diff --git a/src/storable.rs b/src/storable.rs index dfcb55e8..ebc177bd 100644 --- a/src/storable.rs +++ b/src/storable.rs @@ -424,12 +424,12 @@ where 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, @@ -454,8 +454,11 @@ where 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_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, ); @@ -492,6 +495,105 @@ where }; } +// Encodes `entry_bytes` size followed with `entry_bytes` into `bytes` +// starting from the index `start`. +// When encoding is saved in `bytes` on indices `[start, end)` +// the function will return index `end` - the first index after +// `start` that is not occupied with encoding. +fn encode_with_size(entry_bytes: &[u8], bytes: &mut [u8], start: usize) -> usize +where + T: Storable, +{ + let size_len = get_num_bytes_required_to_store_size::(); + let actual_size = entry_bytes.len(); + + encode_size::(&mut bytes[start..start + size_len], actual_size); + + bytes[start + size_len..start + size_len + actual_size].copy_from_slice(entry_bytes); + + start + actual_size + size_len +} + +// Deserialize the struct starting at index `start` in `bytes`. +// When serialized struct is saved in `bytes` on indices `[start, end)` the +// function will return deserialized struct and index `end` - the first index +// after `start` that is not occupied with the serialization of the struct. +fn deserialize_with_size(bytes: &[u8], start: usize) -> (T, usize) +where + T: Storable, +{ + let size_len = get_num_bytes_required_to_store_size::(); + let actual_size = decode_size::(&bytes[start..start + size_len]); + + let a = T::from_bytes(Cow::Borrowed( + &bytes[start + size_len..start + size_len + actual_size], + )); + (a, start + actual_size + size_len) +} + +impl Storable for (A, B, C) +where + A: Storable, + B: Storable, + C: Storable, +{ + fn to_bytes(&self) -> Cow<[u8]> { + // Serialize each of the tuple's elements + let a_bytes = self.0.to_bytes(); + let b_bytes = self.1.to_bytes(); + let c_bytes = self.2.to_bytes(); + + let output_size = a_bytes.len() + + get_num_bytes_required_to_store_size::() + + b_bytes.len() + + get_num_bytes_required_to_store_size::() + + c_bytes.len() + + get_num_bytes_required_to_store_size::(); + + let mut bytes = vec![0; output_size]; + + let a_end = encode_with_size::(a_bytes.borrow(), &mut bytes, 0); + let b_end = encode_with_size::(b_bytes.borrow(), &mut bytes, a_end); + encode_with_size::(c_bytes.borrow(), &mut bytes, b_end); + Cow::Owned(bytes) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + if let Bound::Bounded { max_size, .. } = Self::BOUND { + assert!(bytes.len() <= max_size as usize); + } + + let (a, a_end) = deserialize_with_size::(bytes.borrow(), 0); + let (b, b_end) = deserialize_with_size::(bytes.borrow(), a_end); + let (c, _) = deserialize_with_size::(bytes.borrow(), b_end); + + (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::(); + + Bound::Bounded { + max_size: a_bounds.max_size + + bytes_to_store_size(&a_bounds) + + b_bounds.max_size + + bytes_to_store_size(&b_bounds) + + c_bounds.max_size + + bytes_to_store_size(&c_bounds), + is_fixed_size: a_bounds.is_fixed_size + && b_bounds.is_fixed_size + && c_bounds.is_fixed_size, + } + } + _ => Bound::Unbounded, + } + }; +} + impl Storable for Option { fn to_bytes(&self) -> Cow<[u8]> { match self { @@ -568,7 +670,37 @@ pub(crate) const fn bounds() -> Bounds { } } -fn decode_size(src: &[u8], bounds: &Bounds) -> usize { +pub(crate) const fn bytes_to_store_size(bounds: &Bounds) -> u32 { + if bounds.is_fixed_size { + 0 + } else if bounds.max_size <= u8::MAX as u32 { + 1 + } else if bounds.max_size <= u16::MAX as u32 { + 2 + } else { + 4 + } +} + +const NUM_BYTES_TO_STORE_SIZE_OF_UNBOUNDED_TYPE: usize = 4; + +const fn get_num_bytes_required_to_store_size() -> usize +where + T: Storable, +{ + match T::BOUND { + Bound::Bounded { + max_size, + is_fixed_size, + } => bytes_to_store_size(&Bounds { + max_size, + is_fixed_size, + }) as usize, + Bound::Unbounded => NUM_BYTES_TO_STORE_SIZE_OF_UNBOUNDED_TYPE, + } +} + +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 { @@ -580,7 +712,7 @@ fn decode_size(src: &[u8], bounds: &Bounds) -> 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; } @@ -594,14 +726,49 @@ fn encode_size(dst: &mut [u8], n: usize, bounds: &Bounds) { } } -pub(crate) const fn bytes_to_store_size(bounds: &Bounds) -> u32 { - if bounds.is_fixed_size { - 0 - } else if bounds.max_size <= u8::MAX as u32 { - 1 - } else if bounds.max_size <= u16::MAX as u32 { - 2 - } else { - 4 +fn decode_size(src: &[u8]) -> usize +where + T: Storable, +{ + match T::BOUND { + Bound::Bounded { + max_size, + is_fixed_size, + } => { + let size = decode_size_of_bound( + src, + &Bounds { + max_size, + is_fixed_size, + }, + ); + debug_assert!(size <= max_size as usize); + size + } + Bound::Unbounded => u32::from_be_bytes([src[0], src[1], src[2], src[3]]) as usize, + } +} + +fn encode_size(dst: &mut [u8], n: usize) +where + T: Storable, +{ + match T::BOUND { + Bound::Bounded { + max_size, + is_fixed_size, + } => { + debug_assert!(n <= max_size as usize); + encode_size_of_bound( + dst, + n, + &Bounds { + max_size, + is_fixed_size, + }, + ) + } + Bound::Unbounded => dst[0..NUM_BYTES_TO_STORE_SIZE_OF_UNBOUNDED_TYPE] + .copy_from_slice(&(n as u32).to_be_bytes()), } } diff --git a/src/storable/tests.rs b/src/storable/tests.rs index e04c55e5..4ff5ed92 100644 --- a/src/storable/tests.rs +++ b/src/storable/tests.rs @@ -13,6 +13,27 @@ proptest! { prop_assert_eq!(tuple, Storable::from_bytes(bytes)); } + #[test] + fn tuple_with_three_elements_roundtrip(x in any::(), y in uniform20(any::()), z in uniform20(any::())) { + let tuple = (x, y, z); + let bytes = tuple.to_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(v1 in pvec(any::(), 0..4), x in any::(), v2 in pvec(any::(), 0..12)) { + let tuple = (v1, x, v2); + assert_eq!(tuple, Storable::from_bytes(tuple.to_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 +41,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 +56,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 +90,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);