Skip to main content

ab_io_type/
variable_bytes.rs

1use crate::metadata::{IoTypeMetadataKind, MAX_METADATA_CAPACITY, concat_metadata_sources};
2use crate::trivial_type::TrivialType;
3use crate::{DerefWrapper, IoType, IoTypeOptional};
4use core::mem::MaybeUninit;
5use core::ops::{Deref, DerefMut};
6use core::ptr::NonNull;
7use core::{ptr, slice};
8
9/// Container for storing variable number of bytes.
10///
11/// `RECOMMENDED_ALLOCATION` is what is being used when a host needs to allocate memory for call
12/// into guest, but guest may receive an allocation with more or less memory in practice depending
13/// on other circumstances, like when called from another contract with specific allocation
14/// specified.
15#[derive(Debug)]
16#[repr(C)]
17pub struct VariableBytes<const RECOMMENDED_ALLOCATION: u32 = 0> {
18    bytes: NonNull<u8>,
19    size: NonNull<u32>,
20    capacity: u32,
21}
22
23// SAFETY: Low-level (effectively internal) implementation that upholds safety requirements
24unsafe impl<const RECOMMENDED_ALLOCATION: u32> IoType for VariableBytes<RECOMMENDED_ALLOCATION> {
25    const METADATA: &[u8] = {
26        const fn metadata(recommended_allocation: u32) -> ([u8; MAX_METADATA_CAPACITY], usize) {
27            if recommended_allocation == 0 {
28                return concat_metadata_sources(&[&[IoTypeMetadataKind::VariableBytes0 as u8]]);
29            } else if recommended_allocation == 512 {
30                return concat_metadata_sources(&[&[IoTypeMetadataKind::VariableBytes512 as u8]]);
31            } else if recommended_allocation == 1024 {
32                return concat_metadata_sources(&[&[IoTypeMetadataKind::VariableBytes1024 as u8]]);
33            } else if recommended_allocation == 2028 {
34                return concat_metadata_sources(&[&[IoTypeMetadataKind::VariableBytes2028 as u8]]);
35            } else if recommended_allocation == 4096 {
36                return concat_metadata_sources(&[&[IoTypeMetadataKind::VariableBytes4096 as u8]]);
37            } else if recommended_allocation == 8192 {
38                return concat_metadata_sources(&[&[IoTypeMetadataKind::VariableBytes8192 as u8]]);
39            } else if recommended_allocation == 16384 {
40                return concat_metadata_sources(&[&[IoTypeMetadataKind::VariableBytes16384 as u8]]);
41            } else if recommended_allocation == 32768 {
42                return concat_metadata_sources(&[&[IoTypeMetadataKind::VariableBytes32768 as u8]]);
43            } else if recommended_allocation == 65536 {
44                return concat_metadata_sources(&[&[IoTypeMetadataKind::VariableBytes65536 as u8]]);
45            } else if recommended_allocation == 131_072 {
46                return concat_metadata_sources(&[
47                    &[IoTypeMetadataKind::VariableBytes131072 as u8],
48                ]);
49            } else if recommended_allocation == 262_144 {
50                return concat_metadata_sources(&[
51                    &[IoTypeMetadataKind::VariableBytes262144 as u8],
52                ]);
53            } else if recommended_allocation == 524_288 {
54                return concat_metadata_sources(&[
55                    &[IoTypeMetadataKind::VariableBytes524288 as u8],
56                ]);
57            } else if recommended_allocation == 1_048_576 {
58                return concat_metadata_sources(&[&[
59                    IoTypeMetadataKind::VariableBytes1048576 as u8
60                ]]);
61            }
62
63            let (io_type, size_bytes) = if recommended_allocation < 2u32.pow(8) {
64                (IoTypeMetadataKind::VariableBytes8b, 1)
65            } else if recommended_allocation < 2u32.pow(16) {
66                (IoTypeMetadataKind::VariableBytes16b, 2)
67            } else {
68                (IoTypeMetadataKind::VariableBytes32b, 4)
69            };
70
71            concat_metadata_sources(&[
72                &[io_type as u8],
73                recommended_allocation.to_le_bytes().split_at(size_bytes).0,
74            ])
75        }
76
77        // Strange syntax to allow Rust to extend the lifetime of metadata scratch automatically
78        metadata(RECOMMENDED_ALLOCATION)
79            .0
80            .split_at(metadata(RECOMMENDED_ALLOCATION).1)
81            .0
82    };
83
84    // TODO: Use `[u8; U32_TO_USIZE::<RECOMMENDED_ALLOCATION>]` with `generic_const_args`
85    type PointerType = u8;
86
87    #[inline(always)]
88    fn size(&self) -> u32 {
89        self.size()
90    }
91
92    #[inline(always)]
93    fn capacity(&self) -> u32 {
94        self.capacity
95    }
96
97    #[inline(always)]
98    #[track_caller]
99    unsafe fn set_size(&mut self, size: u32) {
100        debug_assert!(
101            size <= self.capacity,
102            "`set_size` called with invalid input {size} for capacity {}",
103            self.capacity
104        );
105
106        // SAFETY: guaranteed to be initialized by constructors
107        unsafe {
108            self.size.write(size);
109        }
110    }
111
112    #[inline(always)]
113    #[track_caller]
114    unsafe fn from_ptr<'a>(
115        ptr: &'a NonNull<Self::PointerType>,
116        size: &'a u32,
117        capacity: u32,
118    ) -> impl Deref<Target = Self> + 'a {
119        debug_assert!(ptr.is_aligned(), "Misaligned pointer");
120        debug_assert!(
121            *size <= capacity,
122            "Size {size} must not exceed capacity {capacity}"
123        );
124
125        DerefWrapper(Self {
126            bytes: *ptr,
127            size: NonNull::from_ref(size),
128            capacity,
129        })
130    }
131
132    #[inline(always)]
133    #[track_caller]
134    unsafe fn from_mut_ptr<'a>(
135        ptr: &'a mut NonNull<Self::PointerType>,
136        size: &'a mut u32,
137        capacity: u32,
138    ) -> impl DerefMut<Target = Self> + 'a {
139        debug_assert!(ptr.is_aligned(), "Misaligned pointer");
140        debug_assert!(
141            *size <= capacity,
142            "Size {size} must not exceed capacity {capacity}"
143        );
144
145        DerefWrapper(Self {
146            bytes: *ptr,
147            size: NonNull::from_mut(size),
148            capacity,
149        })
150    }
151
152    #[inline(always)]
153    unsafe fn as_ptr(&self) -> impl Deref<Target = NonNull<Self::PointerType>> {
154        &self.bytes
155    }
156
157    #[inline(always)]
158    unsafe fn as_mut_ptr(&mut self) -> impl DerefMut<Target = NonNull<Self::PointerType>> {
159        &mut self.bytes
160    }
161}
162
163impl<const RECOMMENDED_ALLOCATION: u32> IoTypeOptional for VariableBytes<RECOMMENDED_ALLOCATION> {}
164
165impl<const RECOMMENDED_ALLOCATION: u32> VariableBytes<RECOMMENDED_ALLOCATION> {
166    /// Create a new shared instance from provided memory buffer.
167    ///
168    /// # Panics
169    /// Panics if `buffer.len() != size`
170    //
171    // `impl Deref` is used to tie lifetime of returned value to inputs, but still treat it as a
172    // shared reference for most practical purposes.
173    #[inline(always)]
174    #[track_caller]
175    pub const fn from_buffer<'a>(
176        buffer: &'a [<Self as IoType>::PointerType],
177        size: &'a u32,
178    ) -> impl Deref<Target = Self> + 'a {
179        debug_assert!(buffer.len() == *size as usize, "Invalid size");
180        // TODO: Use `debug_assert_eq` when it is available in const environment
181        // debug_assert_eq!(buffer.len(), *size as usize, "Invalid size");
182
183        DerefWrapper(Self {
184            bytes: NonNull::new(buffer.as_ptr().cast_mut()).expect("Not null; qed"),
185            size: NonNull::from_ref(size),
186            capacity: *size,
187        })
188    }
189
190    /// Create a new exclusive instance from provided memory buffer.
191    ///
192    /// # Panics
193    /// Panics if `buffer.len() != size`
194    //
195    // `impl DerefMut` is used to tie lifetime of returned value to inputs, but still treat it as an
196    // exclusive reference for most practical purposes.
197    #[inline(always)]
198    #[track_caller]
199    pub fn from_buffer_mut<'a>(
200        buffer: &'a mut [<Self as IoType>::PointerType],
201        size: &'a mut u32,
202    ) -> impl DerefMut<Target = Self> + 'a {
203        debug_assert_eq!(buffer.len(), *size as usize, "Invalid size");
204
205        DerefWrapper(Self {
206            bytes: NonNull::new(buffer.as_mut_ptr()).expect("Not null; qed"),
207            size: NonNull::from_mut(size),
208            capacity: *size,
209        })
210    }
211
212    /// Create a new shared instance from provided memory buffer.
213    ///
214    /// # Panics
215    /// Panics if `size > CAPACITY`
216    //
217    // `impl Deref` is used to tie lifetime of returned value to inputs, but still treat it as a
218    // shared reference for most practical purposes.
219    #[inline(always)]
220    #[track_caller]
221    pub fn from_uninit<'a>(
222        uninit: &'a mut [MaybeUninit<<Self as IoType>::PointerType>],
223        size: &'a mut u32,
224    ) -> impl DerefMut<Target = Self> + 'a {
225        let capacity = uninit.len();
226        debug_assert!(
227            *size as usize <= capacity,
228            "Size {size} must not exceed capacity {capacity}"
229        );
230        let capacity = capacity as u32;
231
232        DerefWrapper(Self {
233            bytes: NonNull::new(uninit.as_mut_ptr().cast_init()).expect("Not null; qed"),
234            size: NonNull::from_mut(size),
235            capacity,
236        })
237    }
238
239    // Size in bytes
240    #[inline(always)]
241    pub const fn size(&self) -> u32 {
242        // SAFETY: guaranteed to be initialized by constructors
243        unsafe { self.size.read() }
244    }
245
246    /// Capacity in bytes
247    #[inline(always)]
248    pub fn capacity(&self) -> u32 {
249        self.capacity
250    }
251
252    /// Try to get access to initialized bytes
253    #[inline(always)]
254    pub const fn get_initialized(&self) -> &[u8] {
255        let size = self.size();
256        let ptr = self.bytes.as_ptr();
257        // SAFETY: guaranteed by constructor and explicit methods by the user
258        unsafe { slice::from_raw_parts(ptr, size as usize) }
259    }
260
261    /// Try to get exclusive access to initialized `Data`, returns `None` if not initialized
262    #[inline(always)]
263    pub fn get_initialized_mut(&mut self) -> &mut [u8] {
264        let size = self.size();
265        let ptr = self.bytes.as_ptr();
266        // SAFETY: guaranteed by constructor and explicit methods by the user
267        unsafe { slice::from_raw_parts_mut(ptr, size as usize) }
268    }
269
270    /// Append some bytes by using more of allocated, but currently unused bytes.
271    ///
272    /// `true` is returned on success, but if there isn't enough unused bytes left, `false` is.
273    #[inline(always)]
274    #[must_use = "Operation may fail"]
275    pub fn append(&mut self, bytes: &[u8]) -> bool {
276        let size = self.size();
277        if bytes.len() + size as usize > self.capacity as usize {
278            return false;
279        }
280
281        // May overflow, which is not allowed
282        let Ok(offset) = isize::try_from(size) else {
283            return false;
284        };
285
286        // SAFETY: allocation range and offset are checked above, the allocation itself is
287        // guaranteed by constructors
288        let mut start = unsafe { self.bytes.offset(offset) };
289        // SAFETY: Alignment is the same, writing happens in properly allocated memory guaranteed by
290        // constructors, number of bytes is checked above, Rust ownership rules will prevent any
291        // overlap here (creating reference to non-initialized part of allocation would already be
292        // undefined behavior anyway)
293        unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), start.as_mut(), bytes.len()) }
294
295        true
296    }
297
298    /// Truncate internal initialized bytes to this size.
299    ///
300    /// Returns `true` on success or `false` if `new_size` is larger than [`Self::size()`].
301    #[inline(always)]
302    #[must_use = "Operation may fail"]
303    pub fn truncate(&mut self, new_size: u32) -> bool {
304        if new_size > self.size() {
305            return false;
306        }
307
308        // SAFETY: guaranteed to be initialized by constructors
309        unsafe {
310            self.size.write(new_size);
311        }
312
313        true
314    }
315
316    /// Copy contents from another `IoType`.
317    ///
318    /// Returns `false` if actual capacity of the instance is not enough to copy contents of `src`
319    #[inline(always)]
320    #[must_use = "Operation may fail"]
321    pub fn copy_from<T>(&mut self, src: &T) -> bool
322    where
323        T: IoType,
324    {
325        let src_size = src.size();
326        if src_size > self.capacity {
327            return false;
328        }
329
330        // SAFETY: `src` can't be the same as `&mut self` if invariants of constructor arguments
331        // were upheld, size is checked to be within capacity above
332        unsafe {
333            self.bytes
334                .copy_from_nonoverlapping(src.as_ptr().cast::<u8>(), src_size as usize);
335            self.size.write(src_size);
336        }
337
338        true
339    }
340
341    /// Get exclusive access to the underlying pointer with no checks.
342    ///
343    /// Can be used for initialization with [`Self::assume_init()`] called afterward to confirm how
344    /// many bytes are in use right now.
345    #[inline(always)]
346    pub fn as_mut_ptr(&mut self) -> &mut NonNull<u8> {
347        &mut self.bytes
348    }
349
350    /// Cast a shared reference to this instance into a reference to an instance of a different
351    /// recommended allocation
352    #[inline(always)]
353    pub fn cast_ref<const DIFFERENT_RECOMMENDED_ALLOCATION: u32>(
354        &self,
355    ) -> &VariableBytes<DIFFERENT_RECOMMENDED_ALLOCATION> {
356        // SAFETY: `VariableBytes` has a fixed layout due to `#[repr(C)]`, which doesn't depend on
357        // recommended allocation
358        unsafe {
359            NonNull::from_ref(self)
360                .cast::<VariableBytes<DIFFERENT_RECOMMENDED_ALLOCATION>>()
361                .as_ref()
362        }
363    }
364
365    /// Cast an exclusive reference to this instance into a reference to an instance of a different
366    /// recommended allocation
367    #[inline(always)]
368    pub fn cast_mut<const DIFFERENT_RECOMMENDED_ALLOCATION: u32>(
369        &mut self,
370    ) -> &mut VariableBytes<DIFFERENT_RECOMMENDED_ALLOCATION> {
371        // SAFETY: `VariableBytes` has a fixed layout due to `#[repr(C)]`, which doesn't depend on
372        // recommended allocation
373        unsafe {
374            NonNull::from_mut(self)
375                .cast::<VariableBytes<DIFFERENT_RECOMMENDED_ALLOCATION>>()
376                .as_mut()
377        }
378    }
379
380    /// Reads and returns value of type `T` or `None` if there is not enough data.
381    ///
382    /// Checks alignment internally to support both aligned and unaligned reads.
383    #[inline(always)]
384    pub fn read_trivial_type<T>(&self) -> Option<T>
385    where
386        T: TrivialType,
387    {
388        if self.size() < T::SIZE {
389            return None;
390        }
391
392        let ptr = self.bytes.cast::<T>();
393
394        // SAFETY: Trivial types are safe to read as bytes, pointer validity is a guaranteed
395        // internal invariant
396        let value = unsafe {
397            if ptr.is_aligned() {
398                ptr.read()
399            } else {
400                ptr.read_unaligned()
401            }
402        };
403
404        Some(value)
405    }
406
407    /// Assume that the first `size` are initialized and can be read.
408    ///
409    /// Returns `Some(initialized_bytes)` on success or `None` if `size` is larger than its
410    /// capacity.
411    ///
412    /// # Safety
413    /// Caller must ensure `size` is actually initialized
414    #[inline(always)]
415    #[must_use = "Operation may fail"]
416    pub unsafe fn assume_init(&mut self, size: u32) -> Option<&mut [u8]> {
417        if size > self.capacity {
418            return None;
419        }
420
421        // SAFETY: guaranteed to be initialized by constructors
422        unsafe {
423            self.size.write(size);
424        }
425        Some(self.get_initialized_mut())
426    }
427}