Skip to main content

spl_pod/list/
list_view.rs

1//! `ListView`, a compact, zero-copy array wrapper.
2
3use {
4    crate::{
5        bytemuck::{
6            pod_from_bytes, pod_from_bytes_mut, pod_slice_from_bytes, pod_slice_from_bytes_mut,
7        },
8        error::PodSliceError,
9        list::{list_view_mut::ListViewMut, list_view_read_only::ListViewReadOnly},
10        pod_length::PodLength,
11        primitives::PodU32,
12    },
13    bytemuck::Pod,
14    solana_program_error::ProgramError,
15    std::{
16        marker::PhantomData,
17        mem::{align_of, size_of},
18        ops::Range,
19    },
20};
21
22/// An API for interpreting a raw buffer (`&[u8]`) as a variable-length collection of Pod elements.
23///
24/// `ListView` provides a safe, zero-copy, `Vec`-like interface for a slice of
25/// `Pod` data that resides in an external, pre-allocated `&[u8]` buffer.
26/// It does not own the buffer itself, but acts as a view over it, which can be
27/// read-only (`ListViewReadOnly`) or mutable (`ListViewMut`).
28///
29/// This is useful in environments where allocations are restricted or expensive,
30/// such as Solana programs, allowing for efficient reads and manipulation of
31/// dynamic-length data structures.
32///
33/// ## Memory Layout
34///
35/// The structure assumes the underlying byte buffer is formatted as follows:
36/// 1.  **Length**: A length field of type `L` at the beginning of the buffer,
37///     indicating the number of currently active elements in the collection.
38///     Defaults to `PodU32`. The implementation uses padding to ensure that the
39///     data is correctly aligned for any `Pod` type.
40/// 2.  **Padding**: Optional padding bytes to ensure proper alignment of the data.
41/// 3.  **Data**: The remaining part of the buffer, which is treated as a slice
42///     of `T` elements. The capacity of the collection is the number of `T`
43///     elements that can fit into this data portion.
44pub struct ListView<T: Pod, L: PodLength = PodU32>(PhantomData<(T, L)>);
45
46struct Layout {
47    length_range: Range<usize>,
48    data_range: Range<usize>,
49}
50
51impl<T: Pod, L: PodLength> ListView<T, L> {
52    /// Calculate the total byte size for a `ListView` holding `num_items`.
53    /// This includes the length prefix, padding, and data.
54    pub fn size_of(num_items: usize) -> Result<usize, ProgramError> {
55        let header_padding = Self::header_padding()?;
56        size_of::<T>()
57            .checked_mul(num_items)
58            .and_then(|curr| curr.checked_add(size_of::<L>()))
59            .and_then(|curr| curr.checked_add(header_padding))
60            .ok_or_else(|| PodSliceError::CalculationFailure.into())
61    }
62
63    /// Unpack a read-only buffer into a `ListViewReadOnly`
64    pub fn unpack(buf: &[u8]) -> Result<ListViewReadOnly<T, L>, ProgramError> {
65        let layout = Self::calculate_layout(buf.len())?;
66
67        // Slice the buffer to get the length prefix and the data.
68        // The layout calculation provides the correct ranges, accounting for any
69        // padding between the length and the data.
70        //
71        // buf: [ L L L L | P P | D D D D D D D D ...]
72        //       <----->         <------------------>
73        //      len_bytes            data_bytes
74        let len_bytes = &buf[layout.length_range];
75        let data_bytes = &buf[layout.data_range];
76
77        let length = pod_from_bytes::<L>(len_bytes)?;
78        let data = pod_slice_from_bytes::<T>(data_bytes)?;
79        let capacity = data.len();
80
81        if (*length).into() > capacity {
82            return Err(PodSliceError::BufferTooSmall.into());
83        }
84
85        Ok(ListViewReadOnly {
86            length,
87            data,
88            capacity,
89        })
90    }
91
92    /// Unpack the mutable buffer into a mutable `ListViewMut`
93    pub fn unpack_mut(buf: &mut [u8]) -> Result<ListViewMut<T, L>, ProgramError> {
94        let view = Self::build_mut_view(buf)?;
95        if (*view.length).into() > view.capacity {
96            return Err(PodSliceError::BufferTooSmall.into());
97        }
98        Ok(view)
99    }
100
101    /// Internal helper to build a mutable view without validation or initialization.
102    #[inline]
103    fn build_mut_view(buf: &mut [u8]) -> Result<ListViewMut<T, L>, ProgramError> {
104        let layout = Self::calculate_layout(buf.len())?;
105
106        // Split the buffer to get the length prefix and the data.
107        // buf: [ L L L L | P P | D D D D D D D D ...]
108        //       <---- head ---> <--- tail --------->
109        let (header_bytes, data_bytes) = buf.split_at_mut(layout.data_range.start);
110        // header: [ L L L L | P P ]
111        //           <----->
112        //          len_bytes
113        let len_bytes = &mut header_bytes[layout.length_range];
114
115        // Cast the bytes to typed data
116        let length = pod_from_bytes_mut::<L>(len_bytes)?;
117        let data = pod_slice_from_bytes_mut::<T>(data_bytes)?;
118        let capacity = data.len();
119
120        Ok(ListViewMut {
121            length,
122            data,
123            capacity,
124        })
125    }
126
127    /// Calculate the byte ranges for the length and data sections of the buffer
128    #[inline]
129    fn calculate_layout(buf_len: usize) -> Result<Layout, ProgramError> {
130        let len_field_end = size_of::<L>();
131        let header_padding = Self::header_padding()?;
132        let data_start = len_field_end.saturating_add(header_padding);
133
134        if buf_len < data_start {
135            return Err(PodSliceError::BufferTooSmall.into());
136        }
137
138        Ok(Layout {
139            length_range: 0..len_field_end,
140            data_range: data_start..buf_len,
141        })
142    }
143
144    /// Calculate the padding required to align the data part of the buffer.
145    ///
146    /// The goal is to ensure that the data field `T` starts at a memory offset
147    /// that is a multiple of its alignment requirement.
148    #[inline]
149    fn header_padding() -> Result<usize, ProgramError> {
150        // Enforce that the length prefix type `L` itself does not have alignment requirements
151        if align_of::<L>() != 1 {
152            return Err(ProgramError::InvalidArgument);
153        }
154
155        let length_size = size_of::<L>();
156        let data_align = align_of::<T>();
157
158        // No padding is needed for alignments of 0 or 1
159        if data_align == 0 || data_align == 1 {
160            return Ok(0);
161        }
162
163        // Find how many bytes `length_size` extends past an alignment boundary
164        #[allow(clippy::arithmetic_side_effects)]
165        let remainder = length_size.wrapping_rem(data_align);
166
167        // If already aligned (remainder is 0), no padding is needed.
168        // Otherwise, calculate the distance to the next alignment boundary.
169        if remainder == 0 {
170            Ok(0)
171        } else {
172            Ok(data_align.wrapping_sub(remainder))
173        }
174    }
175}
176
177impl<T: Pod, L> ListView<T, L>
178where
179    L: PodLength,
180    PodSliceError: From<<L as TryFrom<usize>>::Error>,
181{
182    /// Initialize a buffer: sets `length = 0` and returns a mutable `ListViewMut`.
183    pub fn init(buf: &mut [u8]) -> Result<ListViewMut<T, L>, ProgramError> {
184        let view = Self::build_mut_view(buf)?;
185        *view.length = L::try_from(0).map_err(PodSliceError::from)?;
186        Ok(view)
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    #[cfg(not(target_arch = "bpf"))]
193    use crate::primitives::PodU128;
194    use {
195        super::*,
196        crate::{
197            list::List,
198            primitives::{PodU16, PodU32, PodU64},
199        },
200        bytemuck_derive::{Pod as DerivePod, Zeroable},
201    };
202
203    #[test]
204    fn test_size_of_no_padding() {
205        // Case 1: T has align 1, so no padding is ever needed.
206        // 10 items * 1 byte/item + 4 bytes for length = 14
207        assert_eq!(ListView::<u8, PodU32>::size_of(10).unwrap(), 14);
208
209        // Case 2: size_of<L> is a multiple of align_of<T>, so no padding needed.
210        // T = u32 (size 4, align 4), L = PodU32 (size 4). 4 % 4 == 0.
211        // 10 items * 4 bytes/item + 4 bytes for length = 44
212        assert_eq!(ListView::<u32>::size_of(10).unwrap(), 44);
213
214        // Case 3: 0 items. Size should just be size_of<L> + padding.
215        // Padding is 0 here.
216        // 0 items * 4 bytes/item + 4 bytes for length = 4
217        assert_eq!(ListView::<u32>::size_of(0).unwrap(), 4);
218    }
219
220    #[test]
221    fn test_size_of_with_padding() {
222        // Case 1: Padding is required.
223        // T = u64 (size 8, align 8), L = PodU32 (size 4).
224        // Padding required to align data to 8 bytes is 4. (4 + 4 = 8)
225        // (10 items * 8 bytes/item) + 4 bytes for length + 4 bytes for padding = 88
226        assert_eq!(ListView::<u64, PodU32>::size_of(10).unwrap(), 88);
227
228        #[repr(C, align(16))]
229        #[derive(DerivePod, Zeroable, Copy, Clone)]
230        struct Align16(u128);
231
232        // Case 2: Custom struct with high alignment.
233        // size 16, align 16
234        // L = PodU64 (size 8).
235        // Padding required to align data to 16 bytes is 8. (8 + 8 = 16)
236        // (10 items * 16 bytes/item) + 8 bytes for length + 8 bytes for padding = 176
237        assert_eq!(ListView::<Align16>::size_of(10).unwrap(), 176);
238
239        // Case 3: 0 items with padding.
240        // Size should be size_of<L> + padding.
241        // L = PodU32 (size 4), T = u64 (align 8). Padding is 4.
242        // Total size = 4 + 4 = 8
243        assert_eq!(ListView::<u64, PodU32>::size_of(0).unwrap(), 8);
244    }
245
246    #[test]
247    fn test_size_of_overflow() {
248        // Case 1: Multiplication overflows.
249        // `size_of::<u16>() * usize::MAX` will overflow.
250        let err = ListView::<u16, PodU32>::size_of(usize::MAX).unwrap_err();
251        assert_eq!(err, PodSliceError::CalculationFailure.into());
252
253        // Case 2: Multiplication does not overflow, but subsequent addition does.
254        // `size_of::<u8>() * usize::MAX` does not overflow, but adding `size_of<L>` will.
255        let err = ListView::<u8, PodU32>::size_of(usize::MAX).unwrap_err();
256        assert_eq!(err, PodSliceError::CalculationFailure.into());
257    }
258
259    #[test]
260    fn test_fails_with_non_aligned_length_type() {
261        // A custom `PodLength` type with an alignment of 4
262        #[repr(C, align(4))]
263        #[derive(Debug, Copy, Clone, Zeroable, DerivePod)]
264        struct TestPodU32(u32);
265
266        // Implement the traits for `PodLength`
267        impl From<TestPodU32> for usize {
268            fn from(val: TestPodU32) -> Self {
269                val.0 as usize
270            }
271        }
272        impl TryFrom<usize> for TestPodU32 {
273            type Error = PodSliceError;
274            fn try_from(val: usize) -> Result<Self, Self::Error> {
275                Ok(Self(u32::try_from(val)?))
276            }
277        }
278
279        let mut buf = [0u8; 100];
280
281        let err_size_of = ListView::<u8, TestPodU32>::size_of(10).unwrap_err();
282        assert_eq!(err_size_of, ProgramError::InvalidArgument);
283
284        let err_unpack = ListView::<u8, TestPodU32>::unpack(&buf).unwrap_err();
285        assert_eq!(err_unpack, ProgramError::InvalidArgument);
286
287        let err_init = ListView::<u8, TestPodU32>::init(&mut buf).unwrap_err();
288        assert_eq!(err_init, ProgramError::InvalidArgument);
289    }
290
291    #[test]
292    fn test_padding_calculation() {
293        // `u8` has an alignment of 1, so no padding is ever needed.
294        assert_eq!(ListView::<u8, PodU32>::header_padding().unwrap(), 0);
295
296        // Zero-Sized Types like `()` have size 0 and align 1, requiring no padding.
297        assert_eq!(ListView::<(), PodU64>::header_padding().unwrap(), 0);
298
299        // When length and data have the same alignment.
300        assert_eq!(ListView::<u16, PodU16>::header_padding().unwrap(), 0);
301        assert_eq!(ListView::<u32, PodU32>::header_padding().unwrap(), 0);
302        assert_eq!(ListView::<u64, PodU64>::header_padding().unwrap(), 0);
303
304        // When data alignment is smaller than or perfectly divides the length size.
305        assert_eq!(ListView::<u16, PodU64>::header_padding().unwrap(), 0); // 8 % 2 = 0
306        assert_eq!(ListView::<u32, PodU64>::header_padding().unwrap(), 0); // 8 % 4 = 0
307
308        // When padding IS needed.
309        assert_eq!(ListView::<u32, PodU16>::header_padding().unwrap(), 2); // size_of<PodU16> is 2. To align to 4, need 2 bytes.
310        assert_eq!(ListView::<u64, PodU16>::header_padding().unwrap(), 6); // size_of<PodU16> is 2. To align to 8, need 6 bytes.
311        assert_eq!(ListView::<u64, PodU32>::header_padding().unwrap(), 4); // size_of<PodU32> is 4. To align to 8, need 4 bytes.
312
313        // Test with custom, higher alignments.
314        #[repr(C, align(8))]
315        #[derive(DerivePod, Zeroable, Copy, Clone)]
316        struct Align8(u64);
317
318        // Test against different length types
319        assert_eq!(ListView::<Align8, PodU16>::header_padding().unwrap(), 6); // 2 + 6 = 8
320        assert_eq!(ListView::<Align8, PodU32>::header_padding().unwrap(), 4); // 4 + 4 = 8
321        assert_eq!(ListView::<Align8, PodU64>::header_padding().unwrap(), 0); // 8 is already aligned
322
323        #[repr(C, align(16))]
324        #[derive(DerivePod, Zeroable, Copy, Clone)]
325        struct Align16(u128);
326
327        assert_eq!(ListView::<Align16, PodU16>::header_padding().unwrap(), 14); // 2 + 14 = 16
328        assert_eq!(ListView::<Align16, PodU32>::header_padding().unwrap(), 12); // 4 + 12 = 16
329        assert_eq!(ListView::<Align16, PodU64>::header_padding().unwrap(), 8); // 8 + 8 = 16
330    }
331
332    #[test]
333    fn test_unpack_success_no_padding() {
334        // T = u32 (align 4), L = PodU32 (size 4, align 4). No padding needed.
335        let length: u32 = 2;
336        let capacity: usize = 3;
337        let item_size = size_of::<u32>();
338        let len_size = size_of::<PodU32>();
339        let buf_size = len_size + capacity * item_size;
340        let mut buf = vec![0u8; buf_size];
341
342        let pod_len: PodU32 = length.into();
343        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
344
345        let data_start = len_size;
346        let items = [100u32, 200u32];
347        let items_bytes = bytemuck::cast_slice(&items);
348        buf[data_start..(data_start + items_bytes.len())].copy_from_slice(items_bytes);
349
350        let view_ro = ListView::<u32, PodU32>::unpack(&buf).unwrap();
351        assert_eq!(view_ro.len(), length as usize);
352        assert_eq!(view_ro.capacity(), capacity);
353        assert_eq!(*view_ro, items[..]);
354
355        let view_mut = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap();
356        assert_eq!(view_mut.len(), length as usize);
357        assert_eq!(view_mut.capacity(), capacity);
358        assert_eq!(*view_mut, items[..]);
359    }
360
361    #[test]
362    fn test_unpack_success_with_padding() {
363        // T = u64 (align 8), L = PodU32 (size 4, align 4). Needs 4 bytes padding.
364        let padding = ListView::<u64, PodU32>::header_padding().unwrap();
365        assert_eq!(padding, 4);
366
367        let length: u32 = 2;
368        let capacity: usize = 2;
369        let item_size = size_of::<u64>();
370        let len_size = size_of::<PodU32>();
371        let buf_size = len_size + padding + capacity * item_size;
372        let mut buf = vec![0u8; buf_size];
373
374        let pod_len: PodU32 = length.into();
375        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
376
377        // Data starts after length and padding
378        let data_start = len_size + padding;
379        let items = [100u64, 200u64];
380        let items_bytes = bytemuck::cast_slice(&items);
381        buf[data_start..(data_start + items_bytes.len())].copy_from_slice(items_bytes);
382
383        let view_ro = ListView::<u64, PodU32>::unpack(&buf).unwrap();
384        assert_eq!(view_ro.len(), length as usize);
385        assert_eq!(view_ro.capacity(), capacity);
386        assert_eq!(*view_ro, items[..]);
387
388        let view_mut = ListView::<u64, PodU32>::unpack_mut(&mut buf).unwrap();
389        assert_eq!(view_mut.len(), length as usize);
390        assert_eq!(view_mut.capacity(), capacity);
391        assert_eq!(*view_mut, items[..]);
392    }
393
394    #[test]
395    fn test_unpack_success_zero_length() {
396        let capacity: usize = 5;
397        let item_size = size_of::<u32>();
398        let len_size = size_of::<PodU32>();
399        let buf_size = len_size + capacity * item_size;
400        let mut buf = vec![0u8; buf_size];
401
402        let pod_len: PodU32 = 0u32.into();
403        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
404
405        let view_ro = ListView::<u32, PodU32>::unpack(&buf).unwrap();
406        assert_eq!(view_ro.len(), 0);
407        assert_eq!(view_ro.capacity(), capacity);
408        assert!(view_ro.is_empty());
409        assert_eq!(&*view_ro, &[] as &[u32]);
410
411        let view_mut = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap();
412        assert_eq!(view_mut.len(), 0);
413        assert_eq!(view_mut.capacity(), capacity);
414        assert!(view_mut.is_empty());
415        assert_eq!(&*view_mut, &[] as &[u32]);
416    }
417
418    #[test]
419    fn test_unpack_success_full_capacity() {
420        let length: u64 = 3;
421        let capacity: usize = 3;
422        let item_size = size_of::<u64>();
423        let len_size = size_of::<PodU64>();
424        let buf_size = len_size + capacity * item_size;
425        let mut buf = vec![0u8; buf_size];
426
427        let pod_len: PodU64 = length.into();
428        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
429
430        let data_start = len_size;
431        let items = [1u64, 2u64, 3u64];
432        let items_bytes = bytemuck::cast_slice(&items);
433        buf[data_start..].copy_from_slice(items_bytes);
434
435        let view_ro = ListView::<u64>::unpack(&buf).unwrap();
436        assert_eq!(view_ro.len(), length as usize);
437        assert_eq!(view_ro.capacity(), capacity);
438        assert_eq!(*view_ro, items[..]);
439
440        let view_mut = ListView::<u64>::unpack_mut(&mut buf).unwrap();
441        assert_eq!(view_mut.len(), length as usize);
442        assert_eq!(view_mut.capacity(), capacity);
443        assert_eq!(*view_mut, items[..]);
444    }
445
446    #[test]
447    fn test_unpack_fail_buffer_too_small_for_header() {
448        // T = u64 (align 8), L = PodU32 (size 4). Header size is 8.
449        let header_size = ListView::<u64, PodU32>::size_of(0).unwrap();
450        assert_eq!(header_size, 8);
451
452        // Provide a buffer smaller than the required header
453        let mut buf = vec![0u8; header_size - 1]; // 7 bytes
454
455        let err = ListView::<u64, PodU32>::unpack(&buf).unwrap_err();
456        assert_eq!(err, PodSliceError::BufferTooSmall.into());
457
458        let err = ListView::<u64, PodU32>::unpack_mut(&mut buf).unwrap_err();
459        assert_eq!(err, PodSliceError::BufferTooSmall.into());
460    }
461
462    #[test]
463    fn test_unpack_fail_declared_length_exceeds_capacity() {
464        let declared_length: u32 = 4;
465        let capacity: usize = 3; // buffer can only hold 3
466        let item_size = size_of::<u32>();
467        let len_size = size_of::<PodU32>();
468        let buf_size = len_size + capacity * item_size;
469        let mut buf = vec![0u8; buf_size];
470
471        // Write a length that is bigger than capacity
472        let pod_len: PodU32 = declared_length.into();
473        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
474
475        let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
476        assert_eq!(err, PodSliceError::BufferTooSmall.into());
477
478        let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
479        assert_eq!(err, PodSliceError::BufferTooSmall.into());
480    }
481
482    #[test]
483    fn test_unpack_fail_data_part_not_multiple_of_item_size() {
484        let len_size = size_of::<PodU32>();
485
486        // data part is 5 bytes, not a multiple of item_size (4)
487        let buf_size = len_size + 5;
488        let mut buf = vec![0u8; buf_size];
489
490        // bytemuck::try_cast_slice returns an alignment error, which we map to InvalidArgument
491
492        let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
493        assert_eq!(err, ProgramError::InvalidArgument);
494
495        let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
496        assert_eq!(err, ProgramError::InvalidArgument);
497    }
498
499    #[test]
500    fn test_unpack_empty_buffer() {
501        let mut buf = [];
502        let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
503        assert_eq!(err, PodSliceError::BufferTooSmall.into());
504
505        let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
506        assert_eq!(err, PodSliceError::BufferTooSmall.into());
507    }
508
509    #[test]
510    fn test_init_success_no_padding() {
511        // T = u32 (align 4), L = PodU32 (size 4). No padding needed.
512        let capacity: usize = 5;
513        let len_size = size_of::<PodU32>();
514        let buf_size = ListView::<u32, PodU32>::size_of(capacity).unwrap();
515        let mut buf = vec![0xFFu8; buf_size]; // Pre-fill to ensure init zeroes it
516
517        let view = ListView::<u32, PodU32>::init(&mut buf).unwrap();
518
519        assert_eq!(view.len(), 0);
520        assert_eq!(view.capacity(), capacity);
521        assert!(view.is_empty());
522
523        // Check that the underlying buffer's length was actually zeroed
524        let length_bytes = &buf[0..len_size];
525        assert_eq!(length_bytes, &[0u8; 4]);
526    }
527
528    #[test]
529    fn test_init_success_with_padding() {
530        // T = u64 (align 8), L = PodU32 (size 4). Needs 4 bytes padding.
531        let capacity: usize = 3;
532        let len_size = size_of::<PodU32>();
533        let buf_size = ListView::<u64, PodU32>::size_of(capacity).unwrap();
534        let mut buf = vec![0xFFu8; buf_size]; // Pre-fill to ensure init zeroes it
535
536        let view = ListView::<u64, PodU32>::init(&mut buf).unwrap();
537
538        assert_eq!(view.len(), 0);
539        assert_eq!(view.capacity(), capacity);
540        assert!(view.is_empty());
541
542        // Check that the underlying buffer's length was actually zeroed
543        let length_bytes = &buf[0..len_size];
544        assert_eq!(length_bytes, &[0u8; 4]);
545        // The padding bytes may or may not be zeroed, we don't assert on them.
546    }
547
548    #[test]
549    fn test_init_success_zero_capacity() {
550        // Test initializing a buffer that can only hold the header.
551        // T = u64 (align 8), L = PodU32 (size 4). Header size is 8.
552        let buf_size = ListView::<u64, PodU32>::size_of(0).unwrap();
553        assert_eq!(buf_size, 8);
554        let mut buf = vec![0xFFu8; buf_size];
555
556        let view = ListView::<u64, PodU32>::init(&mut buf).unwrap();
557
558        assert_eq!(view.len(), 0);
559        assert_eq!(view.capacity(), 0);
560        assert!(view.is_empty());
561
562        // Check that the underlying buffer's length was actually zeroed
563        let len_size = size_of::<PodU32>();
564        let length_bytes = &buf[0..len_size];
565        assert_eq!(length_bytes, &[0u8; 4]);
566    }
567
568    #[test]
569    fn test_init_fail_buffer_too_small() {
570        // Header requires 4 bytes (size_of<PodU32>)
571        let mut buf = vec![0u8; 3];
572        let err = ListView::<u32, PodU32>::init(&mut buf).unwrap_err();
573        assert_eq!(err, PodSliceError::BufferTooSmall.into());
574
575        // With padding, header requires 8 bytes (4 for len, 4 for pad)
576        let mut buf_padded = vec![0u8; 7];
577        let err_padded = ListView::<u64, PodU32>::init(&mut buf_padded).unwrap_err();
578        assert_eq!(err_padded, PodSliceError::BufferTooSmall.into());
579    }
580
581    #[test]
582    fn test_init_success_default_length_type() {
583        // This test uses the default L=PodU32 length type by omitting it.
584        // T = u32 (align 4), L = PodU32 (size 4). No padding needed as 4 % 4 == 0.
585        let capacity = 5;
586        let len_size = size_of::<PodU32>(); // Default L is PodU32
587        let buf_size = ListView::<u32>::size_of(capacity).unwrap();
588        let mut buf = vec![0xFFu8; buf_size]; // Pre-fill to ensure init zeroes it
589
590        let view = ListView::<u32>::init(&mut buf).unwrap();
591
592        assert_eq!(view.len(), 0);
593        assert_eq!(view.capacity(), capacity);
594        assert!(view.is_empty());
595
596        // Check that the underlying buffer's length (a u32) was actually zeroed
597        let length_bytes = &buf[0..len_size];
598        assert_eq!(length_bytes, &[0u8; 4]);
599    }
600
601    macro_rules! test_list_view_for_length_type {
602        ($test_name:ident, $LengthType:ty) => {
603            #[test]
604            fn $test_name() {
605                type T = u64;
606
607                let padding = ListView::<T, $LengthType>::header_padding().unwrap();
608                let length_usize = 2usize;
609                let capacity = 3;
610
611                let item_size = size_of::<T>();
612                let len_size = size_of::<$LengthType>();
613                let buf_size = len_size + padding + capacity * item_size;
614                let mut buf = vec![0u8; buf_size];
615
616                // Write length
617                let pod_len = <$LengthType>::try_from(length_usize).unwrap();
618                buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
619
620                // Write data
621                let data_start = len_size + padding;
622                let items = [1000 as T, 2000 as T];
623                let items_bytes = bytemuck::cast_slice(&items);
624                buf[data_start..(data_start + items_bytes.len())].copy_from_slice(items_bytes);
625
626                // Test read-only view
627                let view_ro = ListView::<T, $LengthType>::unpack(&buf).unwrap();
628                assert_eq!(view_ro.len(), length_usize);
629                assert_eq!(view_ro.capacity(), capacity);
630                assert_eq!(*view_ro, items[..]);
631
632                // Test mutable view
633                let mut buf_mut = buf.clone();
634                let view_mut = ListView::<T, $LengthType>::unpack_mut(&mut buf_mut).unwrap();
635                assert_eq!(view_mut.len(), length_usize);
636                assert_eq!(view_mut.capacity(), capacity);
637                assert_eq!(*view_mut, items[..]);
638
639                // Test init
640                let mut init_buf = vec![0xFFu8; buf_size];
641                let init_view = ListView::<T, $LengthType>::init(&mut init_buf).unwrap();
642                assert_eq!(init_view.len(), 0);
643                assert_eq!(init_view.capacity(), capacity);
644                assert_eq!(<$LengthType>::try_from(0usize).unwrap(), *init_view.length);
645            }
646        };
647    }
648
649    test_list_view_for_length_type!(list_view_with_pod_u16, PodU16);
650    test_list_view_for_length_type!(list_view_with_pod_u32, PodU32);
651    test_list_view_for_length_type!(list_view_with_pod_u64, PodU64);
652    #[cfg(not(target_arch = "bpf"))]
653    test_list_view_for_length_type!(list_view_with_pod_u128, PodU128);
654}