diff --git a/soroban-sdk/src/bytes.rs b/soroban-sdk/src/bytes.rs index 1e11c59d2..1605341bd 100644 --- a/soroban-sdk/src/bytes.rs +++ b/soroban-sdk/src/bytes.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "alloc")] +extern crate alloc; + use core::{ borrow::Borrow, cmp::Ordering, @@ -602,6 +605,70 @@ impl Bytes { pub fn iter(&self) -> BytesIter { self.clone().into_iter() } + + /// Copy the bytes into a buffer of given size. + /// + /// Returns the buffer and a range of where the bytes live in the given + /// buffer. + /// + /// Suitable when the size of the bytes isn't a fixed size but it is known + /// to be under a certain size, or failure due to overflow is acceptable. + /// + /// ### Panics + /// + /// If the size of the bytes is larger than the size of the buffer. To avoid + /// this, first slice the bytes into a smaller size then convert to a + /// buffer. + #[must_use] + pub fn to_buffer(&self) -> BytesBuffer { + let mut buffer = [0u8; B]; + let len = self.len() as usize; + { + let slice = &mut buffer[0..len]; + self.copy_into_slice(slice); + } + BytesBuffer { buffer, len } + } + + /// Copy the bytes into a Rust alloc Vec of size matching the bytes. + /// + /// Returns the Vec. Allocates using the built-in allocator. + /// + /// Suitable when the size of the bytes isn't a fixed size and the allocator + /// functionality of the sdk is enabled. + #[cfg(feature = "alloc")] + #[must_use] + pub fn to_alloc_vec(&self) -> alloc::vec::Vec { + let len = self.len() as usize; + let mut vec = alloc::vec::from_elem(0u8, len); + self.copy_into_slice(&mut vec); + vec + } +} + +/// A `BytesBuffer` stores a variable number of bytes, up to a fixed limit `B`. +/// +/// The bytes are stored in a fixed-size non-heap-allocated structure. It is a +/// minimal wrapper around a fixed-size `[u8;B]` byte array and a length field +/// indicating the amount of the byte array containing meaningful data. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BytesBuffer { + buffer: [u8; B], + len: usize, +} + +impl Borrow<[u8]> for BytesBuffer { + /// Returns a borrow slice of the bytes stored in the BytesBuffer. + fn borrow(&self) -> &[u8] { + self.as_slice() + } +} + +impl BytesBuffer { + /// Returns a borrow slice of the bytes stored in the BytesBuffer. + pub fn as_slice(&self) -> &[u8] { + &self.buffer[..self.len] + } } impl IntoIterator for Bytes { diff --git a/soroban-sdk/src/tests.rs b/soroban-sdk/src/tests.rs index 27a8fe711..1c1f6bdb6 100644 --- a/soroban-sdk/src/tests.rs +++ b/soroban-sdk/src/tests.rs @@ -3,6 +3,8 @@ mod address; mod auth; mod budget; +mod bytes_alloc_vec; +mod bytes_buffer; mod contract_add_i32; mod contract_assert; mod contract_docs; diff --git a/soroban-sdk/src/tests/bytes_alloc_vec.rs b/soroban-sdk/src/tests/bytes_alloc_vec.rs new file mode 100644 index 000000000..123320425 --- /dev/null +++ b/soroban-sdk/src/tests/bytes_alloc_vec.rs @@ -0,0 +1,15 @@ +#![cfg(feature = "alloc")] + +use crate::{Bytes, Env}; + +#[test] +fn test_bytes_alloc_vec() { + let env = Env::default(); + + let bytes = Bytes::from_slice(&env, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + assert_eq!( + &[1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10], + bytes.to_alloc_vec().as_slice() + ); +} diff --git a/soroban-sdk/src/tests/bytes_buffer.rs b/soroban-sdk/src/tests/bytes_buffer.rs new file mode 100644 index 000000000..1b2844200 --- /dev/null +++ b/soroban-sdk/src/tests/bytes_buffer.rs @@ -0,0 +1,23 @@ +use crate::{Bytes, Env}; + +#[test] +fn test_bytes_buffer() { + let env = Env::default(); + + let bytes = Bytes::from_slice(&env, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + assert_eq!( + &[1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10], + bytes.to_buffer::<1024>().as_slice(), + ); +} + +#[test] +#[should_panic(expected = "range end index 10 out of range for slice of length 9")] +fn test_bytes_buffer_panic() { + let env = Env::default(); + + let bytes = Bytes::from_slice(&env, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + let _ = bytes.to_buffer::<9>(); +}