Skip to content

Commit 8aa0a1a

Browse files
authored
feat: add zero copy slice (#1547)
1 parent 88f6f88 commit 8aa0a1a

File tree

13 files changed

+1091
-324
lines changed

13 files changed

+1091
-324
lines changed

program-libs/zero-copy/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ std = []
1414
[dependencies]
1515
solana-program = { workspace = true, optional = true }
1616
thiserror = {version="2.0", default-features = false}
17-
num-traits = { version = "0.2" }
18-
zerocopy = {version="0.8.14"}
17+
num-traits = { workspace = true }
18+
zerocopy = { workspace = true}
1919

2020
[dev-dependencies]
2121
rand = "0.8"
2222
num-traits.workspace = true
23-
zerocopy = {version="0.8.14", features=["derive"]}
23+
zerocopy = {workspace = true, features=["derive"]}

program-libs/zero-copy/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
# Light Zero Copy
3+
4+
### Security Considerations
5+
- do not use on a 32 bit target with length greater than u32
6+
- only length until u64 is supported

program-libs/zero-copy/src/borsh.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
use core::{
2+
mem::size_of,
3+
ops::{Deref, DerefMut},
4+
};
5+
use std::vec::Vec;
6+
7+
use zerocopy::{little_endian::U32, FromBytes, Immutable, KnownLayout, Ref};
8+
9+
use crate::errors::ZeroCopyError;
10+
11+
pub trait Deserialize<'a>
12+
where
13+
Self: Sized,
14+
{
15+
type Output;
16+
17+
fn zero_copy_at(bytes: &'a [u8]) -> Result<(Self::Output, &'a [u8]), ZeroCopyError>;
18+
}
19+
20+
impl<'a, T: KnownLayout + Immutable + FromBytes> Deserialize<'a> for Ref<&'a [u8], T> {
21+
type Output = Ref<&'a [u8], T>;
22+
23+
#[inline]
24+
fn zero_copy_at(bytes: &'a [u8]) -> Result<(Self, &'a [u8]), ZeroCopyError> {
25+
let (bytes, remaining_bytes) = Ref::<&[u8], T>::from_prefix(bytes)?;
26+
Ok((bytes, remaining_bytes))
27+
}
28+
}
29+
30+
impl<'a, T: Deserialize<'a>> Deserialize<'a> for Option<T> {
31+
type Output = Option<T::Output>;
32+
#[inline]
33+
fn zero_copy_at(bytes: &'a [u8]) -> Result<(Self::Output, &'a [u8]), ZeroCopyError> {
34+
if bytes.len() < size_of::<u8>() {
35+
return Err(ZeroCopyError::ArraySize(1, bytes.len()));
36+
}
37+
let (option_byte, bytes) = bytes.split_at(1);
38+
Ok(match option_byte[0] {
39+
0u8 => (None, bytes),
40+
1u8 => {
41+
let (value, bytes) = T::zero_copy_at(bytes)?;
42+
(Some(value), bytes)
43+
}
44+
_ => return Err(ZeroCopyError::InvalidOptionByte(option_byte[0])),
45+
})
46+
}
47+
}
48+
49+
impl Deserialize<'_> for u8 {
50+
type Output = Self;
51+
52+
/// Not a zero copy but cheaper.
53+
/// A u8 should not be deserialized on it's own but as part of a struct.
54+
#[inline]
55+
fn zero_copy_at(bytes: &[u8]) -> Result<(u8, &[u8]), ZeroCopyError> {
56+
if bytes.len() < size_of::<u8>() {
57+
return Err(ZeroCopyError::ArraySize(1, bytes.len()));
58+
}
59+
let (bytes, remaining_bytes) = bytes.split_at(size_of::<u8>());
60+
Ok((bytes[0], remaining_bytes))
61+
}
62+
}
63+
64+
macro_rules! impl_deserialize_for_primitive {
65+
($($t:ty),*) => {
66+
$(
67+
impl<'a> Deserialize<'a> for $t {
68+
type Output = Ref<&'a [u8], $t>;
69+
70+
#[inline]
71+
fn zero_copy_at(bytes: &'a [u8]) -> Result<(Self::Output, &'a [u8]), ZeroCopyError> {
72+
Self::Output::zero_copy_at(bytes)
73+
}
74+
}
75+
)*
76+
};
77+
}
78+
79+
impl_deserialize_for_primitive!(u16, i16, u32, i32, u64, i64);
80+
81+
impl<'a, T: Deserialize<'a>> Deserialize<'a> for Vec<T> {
82+
type Output = Vec<T::Output>;
83+
#[inline]
84+
fn zero_copy_at(bytes: &'a [u8]) -> Result<(Self::Output, &'a [u8]), ZeroCopyError> {
85+
let (num_slices, mut bytes) = Ref::<&[u8], U32>::from_prefix(bytes)?;
86+
let num_slices = u32::from(*num_slices) as usize;
87+
let mut slices = Vec::with_capacity(num_slices);
88+
for _ in 0..num_slices {
89+
let (slice, _bytes) = T::zero_copy_at(bytes)?;
90+
bytes = _bytes;
91+
slices.push(slice);
92+
}
93+
Ok((slices, bytes))
94+
}
95+
}
96+
97+
#[derive(Clone, Debug, Default, PartialEq)]
98+
pub struct VecU8<T>(Vec<T>);
99+
impl<T> VecU8<T> {
100+
pub fn new() -> Self {
101+
Self(Vec::new())
102+
}
103+
}
104+
105+
impl<T> Deref for VecU8<T> {
106+
type Target = Vec<T>;
107+
fn deref(&self) -> &Self::Target {
108+
&self.0
109+
}
110+
}
111+
112+
impl DerefMut for VecU8<u8> {
113+
fn deref_mut(&mut self) -> &mut Self::Target {
114+
&mut self.0
115+
}
116+
}
117+
118+
impl<'a, T: Deserialize<'a>> Deserialize<'a> for VecU8<T> {
119+
type Output = Vec<T::Output>;
120+
121+
#[inline]
122+
fn zero_copy_at(bytes: &'a [u8]) -> Result<(Self::Output, &'a [u8]), ZeroCopyError> {
123+
let (num_slices, mut bytes) = Ref::<&[u8], u8>::from_prefix(bytes)?;
124+
let num_slices = u32::from(*num_slices) as usize;
125+
let mut slices = Vec::with_capacity(num_slices);
126+
for _ in 0..num_slices {
127+
let (slice, _bytes) = T::zero_copy_at(bytes)?;
128+
bytes = _bytes;
129+
slices.push(slice);
130+
}
131+
Ok((slices, bytes))
132+
}
133+
}
134+
135+
#[test]
136+
fn test_vecu8() {
137+
use std::vec;
138+
let bytes = vec![8, 1u8, 2, 3, 4, 5, 6, 7, 8];
139+
let (vec, remaining_bytes) = VecU8::<u8>::zero_copy_at(&bytes).unwrap();
140+
assert_eq!(vec, vec![1u8, 2, 3, 4, 5, 6, 7, 8]);
141+
assert_eq!(remaining_bytes, &[]);
142+
}
143+
144+
#[test]
145+
fn test_deserialize_ref() {
146+
let bytes = [1, 0, 0, 0]; // Little-endian representation of 1
147+
let (ref_data, remaining) = Ref::<&[u8], U32>::zero_copy_at(&bytes).unwrap();
148+
assert_eq!(u32::from(*ref_data), 1);
149+
assert_eq!(remaining, &[]);
150+
let res = Ref::<&[u8], U32>::zero_copy_at(&[]);
151+
assert_eq!(res, Err(ZeroCopyError::Size));
152+
}
153+
154+
#[test]
155+
fn test_deserialize_option_some() {
156+
let bytes = [1, 2]; // 1 indicates Some, followed by the value 2
157+
let (option_value, remaining) = Option::<u8>::zero_copy_at(&bytes).unwrap();
158+
assert_eq!(option_value, Some(2));
159+
assert_eq!(remaining, &[]);
160+
let res = Option::<u8>::zero_copy_at(&[]);
161+
assert_eq!(res, Err(ZeroCopyError::ArraySize(1, 0)));
162+
let bytes = [2, 0]; // 2 indicates invalid option byte
163+
let res = Option::<u8>::zero_copy_at(&bytes);
164+
assert_eq!(res, Err(ZeroCopyError::InvalidOptionByte(2)));
165+
}
166+
167+
#[test]
168+
fn test_deserialize_option_none() {
169+
let bytes = [0]; // 0 indicates None
170+
let (option_value, remaining) = Option::<u8>::zero_copy_at(&bytes).unwrap();
171+
assert_eq!(option_value, None);
172+
assert_eq!(remaining, &[]);
173+
}
174+
175+
#[test]
176+
fn test_deserialize_u8() {
177+
let bytes = [0xFF]; // Value 255
178+
let (value, remaining) = u8::zero_copy_at(&bytes).unwrap();
179+
assert_eq!(value, 255);
180+
assert_eq!(remaining, &[]);
181+
let res = u8::zero_copy_at(&[]);
182+
assert_eq!(res, Err(ZeroCopyError::ArraySize(1, 0)));
183+
}
184+
185+
#[test]
186+
fn test_deserialize_u16() {
187+
let bytes = 2323u16.to_le_bytes();
188+
let (value, remaining) = u16::zero_copy_at(bytes.as_slice()).unwrap();
189+
assert_eq!(*value, 2323u16);
190+
assert_eq!(remaining, &[]);
191+
let res = u16::zero_copy_at(&[0u8]);
192+
assert_eq!(res, Err(ZeroCopyError::Size));
193+
}
194+
195+
#[test]
196+
fn test_deserialize_vec() {
197+
let bytes = [2, 0, 0, 0, 1, 2]; // Length 2, followed by values 1 and 2
198+
let (vec, remaining) = Vec::<u8>::zero_copy_at(&bytes).unwrap();
199+
assert_eq!(vec, std::vec![1, 2]);
200+
assert_eq!(remaining, &[]);
201+
}
202+
203+
#[test]
204+
fn test_vecu8_deref() {
205+
let data = std::vec![1, 2, 3];
206+
let vec_u8 = VecU8(data.clone());
207+
assert_eq!(&*vec_u8, &data);
208+
209+
let mut vec = VecU8::new();
210+
vec.push(1u8);
211+
assert_eq!(*vec, std::vec![1u8]);
212+
}
213+
214+
#[test]
215+
fn test_deserialize_vecu8() {
216+
let bytes = [3, 4, 5, 6]; // Length 3, followed by values 4, 5, 6
217+
let (vec, remaining) = VecU8::<u8>::zero_copy_at(&bytes).unwrap();
218+
assert_eq!(vec, std::vec![4, 5, 6]);
219+
assert_eq!(remaining, &[]);
220+
}

program-libs/zero-copy/src/cyclic_vec.rs

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,24 @@ where
4242
}
4343

4444
pub fn new_at(capacity: L, bytes: &'a mut [u8]) -> Result<(Self, &'a mut [u8]), ZeroCopyError> {
45-
let (meta_data, bytes) = bytes.split_at_mut(Self::metadata_size());
45+
if u64::from(capacity) == 0 {
46+
return Err(ZeroCopyError::InvalidCapacity);
47+
}
48+
let metadata_size = Self::metadata_size();
49+
if bytes.len() < metadata_size {
50+
return Err(ZeroCopyError::InsufficientMemoryAllocated(
51+
bytes.len(),
52+
metadata_size,
53+
));
54+
}
55+
let (meta_data, bytes) = bytes.split_at_mut(metadata_size);
4656

4757
let (mut metadata, _padding) = Ref::<&mut [u8], [L; 3]>::from_prefix(meta_data)?;
48-
if u64::from(metadata[LENGTH_INDEX]) != 0 || u64::from(metadata[CURRENT_INDEX_INDEX]) != 0 {
58+
59+
if u64::from(metadata[LENGTH_INDEX]) != 0
60+
|| u64::from(metadata[CURRENT_INDEX_INDEX]) != 0
61+
|| u64::from(metadata[CAPACITY_INDEX]) != 0
62+
{
4963
return Err(ZeroCopyError::MemoryNotZeroed);
5064
}
5165
metadata[CAPACITY_INDEX] = capacity;
@@ -56,64 +70,35 @@ where
5670
Ok((Self { metadata, slice }, remaining_bytes))
5771
}
5872

59-
#[cfg(feature = "std")]
60-
pub fn new_at_multiple(
61-
num: usize,
62-
capacity: L,
63-
mut bytes: &'a mut [u8],
64-
) -> Result<(Vec<Self>, &'a mut [u8]), ZeroCopyError> {
65-
let mut value_vecs = Vec::with_capacity(num);
66-
for _ in 0..num {
67-
let (vec, _bytes) = Self::new_at(capacity, bytes)?;
68-
bytes = _bytes;
69-
value_vecs.push(vec);
70-
}
71-
Ok((value_vecs, bytes))
72-
}
73-
7473
pub fn from_bytes(bytes: &'a mut [u8]) -> Result<Self, ZeroCopyError> {
7574
Ok(Self::from_bytes_at(bytes)?.0)
7675
}
7776

7877
#[inline]
7978
pub fn from_bytes_at(bytes: &'a mut [u8]) -> Result<(Self, &'a mut [u8]), ZeroCopyError> {
80-
let meta_data_size = Self::metadata_size();
81-
if bytes.len() < meta_data_size {
79+
let metadata_size = Self::metadata_size();
80+
if bytes.len() < metadata_size {
8281
return Err(ZeroCopyError::InsufficientMemoryAllocated(
8382
bytes.len(),
84-
meta_data_size,
83+
metadata_size,
8584
));
8685
}
8786

88-
let (meta_data, bytes) = bytes.split_at_mut(meta_data_size);
87+
let (meta_data, bytes) = bytes.split_at_mut(metadata_size);
8988
let (metadata, _padding) = Ref::<&mut [u8], [L; 3]>::from_prefix(meta_data)?;
9089
let usize_len: usize = u64::from(metadata[CAPACITY_INDEX]) as usize;
9190
let full_vector_size = Self::data_size(metadata[CAPACITY_INDEX]);
9291
if bytes.len() < full_vector_size {
9392
return Err(ZeroCopyError::InsufficientMemoryAllocated(
94-
bytes.len(),
95-
full_vector_size,
93+
bytes.len() + metadata_size,
94+
full_vector_size + metadata_size,
9695
));
9796
}
9897
let (slice, remaining_bytes) =
9998
Ref::<&mut [u8], [T]>::from_prefix_with_elems(bytes, usize_len)?;
10099
Ok((Self { metadata, slice }, remaining_bytes))
101100
}
102101

103-
#[cfg(feature = "std")]
104-
pub fn from_bytes_at_multiple(
105-
num: usize,
106-
mut bytes: &'a mut [u8],
107-
) -> Result<(Vec<Self>, &'a mut [u8]), ZeroCopyError> {
108-
let mut value_vecs = Vec::with_capacity(num);
109-
for _ in 0..num {
110-
let (vec, _bytes) = Self::from_bytes_at(bytes)?;
111-
bytes = _bytes;
112-
value_vecs.push(vec);
113-
}
114-
Ok((value_vecs, bytes))
115-
}
116-
117102
/// Convenience method to get the current index of the vector.
118103
#[inline]
119104
fn get_current_index(&self) -> L {
@@ -170,10 +155,7 @@ where
170155
.try_into()
171156
.map_err(|_| ZeroCopyError::InvalidConversion)
172157
.unwrap();
173-
*self.get_len_mut() = 0
174-
.try_into()
175-
.map_err(|_| ZeroCopyError::InvalidConversion)
176-
.unwrap();
158+
*self.get_len_mut() = self.get_current_index();
177159
}
178160

179161
#[inline]
@@ -204,7 +186,7 @@ where
204186
/// First index is the next index after the last index mod capacity.
205187
#[inline]
206188
pub fn first_index(&self) -> usize {
207-
if self.len() < self.capacity() || self.last_index() == self.capacity() {
189+
if self.len() < self.capacity() {
208190
0
209191
} else {
210192
self.last_index().saturating_add(1) % (self.capacity())
@@ -346,6 +328,7 @@ where
346328
if self.vec.capacity() == 0 || self.is_finished {
347329
None
348330
} else {
331+
// Perform one more iteration to perform len() iterations.
349332
if self.current == self.vec.last_index() {
350333
self.is_finished = true;
351334
}

program-libs/zero-copy/src/errors.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ pub enum ZeroCopyError {
2222
InvalidData(Infallible),
2323
#[error("Invalid size.")]
2424
Size,
25+
#[error("Invalid option byte {0} must be 0 (None) or 1 (Some).")]
26+
InvalidOptionByte(u8),
27+
#[error("Invalid capacity. Capacity must be greater than 0.")]
28+
InvalidCapacity,
2529
}
2630

2731
#[cfg(feature = "solana")]
@@ -37,6 +41,8 @@ impl From<ZeroCopyError> for u32 {
3741
ZeroCopyError::InvalidConversion => 15008,
3842
ZeroCopyError::InvalidData(_) => 15009,
3943
ZeroCopyError::Size => 15010,
44+
ZeroCopyError::InvalidOptionByte(_) => 15011,
45+
ZeroCopyError::InvalidCapacity => 15012,
4046
}
4147
}
4248
}

0 commit comments

Comments
 (0)