blob: 847b78f9a9641e0adcdba4f842442fd385054aef [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#![no_std]
use core::cmp::min;
use core::ptr::NonNull;
// NOTE: we assume a rust slice is a fat pointer consisting of a pointer to data and a length.
/// A Slice<T> is a struct with identical layout to a Rust slice.
pub struct Slice<T> {
/// `addr` is a non-null pointer to the slice.
pub addr: NonNull<T>,
/// `length` is the number of elements in the slice.
pub length: usize,
}
impl<T> Slice<T> {
#[must_use]
pub fn as_slice(&self) -> &[T] {
// SAFETY: The `addr` is a non-null pointer to a slice of `length` elements.
unsafe { core::slice::from_raw_parts(self.addr.as_ptr(), self.length) }
}
}
/// Vectord buffer copy allows copying data between arbitrarily shaped vectors of slices.
/// - `src_offset` allows starting the copy at an arbitrary item (regardless of the shape of the slice).
/// - 'copy_len' specifies the number of items to be copied.
/// - The `src` and `dst` are vectors of slices.
///
/// Returns the number of items copied.
#[must_use]
pub fn vectored_buffer_copy<T: Sized>(
mut src_offset: usize,
copy_len: usize,
src: &[Slice<T>],
dst: &[Slice<T>],
) -> usize {
if src.is_empty() || dst.is_empty() || copy_len == 0 {
return 0;
}
let mut copied = 0;
// src and dst indices into the slice of slices.
let mut src_slice_index = 0;
let mut dst_slice_index = 0;
// Advance src index to the src slice that contains the actual start.
while src_offset >= src[src_slice_index].length {
src_offset -= src[src_slice_index].length;
src_slice_index += 1;
if src_slice_index >= src.len() {
return copied;
}
}
// src and dst offsets of the slice indexed by src_slice_index or dst_slice_index.
let mut src_slice_offset = src_offset;
let mut dst_slice_offset = 0;
// Number of elements left to copy.
let mut remaining_elements = copy_len;
while let Some(src_item) = src.get(src_slice_index) {
// Determine how many elements we can copy.
let n = min(
src_item.length - src_slice_offset,
dst[dst_slice_index].length - dst_slice_offset,
);
let n = min(n, remaining_elements);
unsafe {
// SAFETY: The src_item and dst are valid slices.
src_item
.addr
.add(src_slice_offset)
.copy_to(dst[dst_slice_index].addr.add(dst_slice_offset), n);
}
copied += n;
remaining_elements -= n;
if remaining_elements == 0 {
// Exit if we're done.
break;
}
// Advance to the next src slice if we've exhausted this one.
src_slice_offset += n;
if done_with_slice(src, &mut src_slice_index, &mut src_slice_offset) {
break;
}
// Advance to the next dst slice if we've exhausted this one.
dst_slice_offset += n;
if done_with_slice(dst, &mut dst_slice_index, &mut dst_slice_offset) {
break;
}
}
copied
}
// Update the index and offset. Returns `true` if we reach the end of the slice of slices.
#[inline(always)]
fn done_with_slice<T>(slice: &[Slice<T>], index: &mut usize, offset: &mut usize) -> bool {
if *offset == slice[*index].length {
*index += 1;
*offset = 0;
if *index >= slice.len() {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use unittest::{Result, assert_eq, test};
use super::*;
#[allow(dead_code)]
impl<T> Slice<T> {
fn new<'a>(s: &'a [&'a [T]]) -> &'a [Slice<T>] {
// SAFETY: &[&[T]] has the same layout as &[Slice<T>].
unsafe { core::mem::transmute::<&[&[T]], &[Slice<T>]>(s) }
}
fn new_mut<'a>(s: &'a mut [&'a mut [T]]) -> &'a [Slice<T>] {
// SAFETY: &[&[T]] has the same layout as &[Slice<T>].
unsafe { core::mem::transmute::<&mut [&mut [T]], &[Slice<T>]>(s) }
}
}
// Checks that the layout of `Slice<u8>` is the same as `&[u8]`.
#[test]
fn vecbuf_slice_layout_u8() -> Result<()> {
let a = &mut [0u8, 1, 2, 3];
// SAFETY: Transmute to verify that Slice<T> has identical layout to &[T].
let b = unsafe { core::mem::transmute::<&[u8], Slice<u8>>(a) };
let a_ptr = NonNull::new(a.as_mut_ptr()).expect("ptr is not null");
let a_len = a.len();
assert_eq!(a_ptr, b.addr);
assert_eq!(a_len, b.length);
Ok(())
}
// Checks that the layout of `Slice<u16>` is the same as `&[u16]`.
#[test]
fn vecbuf_slice_layout_u16() -> Result<()> {
let a = &mut [0u16, 1, 2, 3];
// SAFETY: Transmute to verify that Slice<T> has identical layout to &[T].
let b = unsafe { core::mem::transmute::<&[u16], Slice<u16>>(a) };
let a_ptr = NonNull::new(a.as_mut_ptr()).expect("ptr is not null");
let a_len = a.len();
assert_eq!(a_ptr, b.addr);
assert_eq!(a_len, b.length);
Ok(())
}
// Checks that the layout of `Slice<u32>` is the same as `&[u32]`.
#[test]
fn vecbuf_slice_layout_u32() -> Result<()> {
let a = &mut [0u32, 1, 2, 3];
// SAFETY: Transmute to verify that Slice<T> has identical layout to &[T].
let b = unsafe { core::mem::transmute::<&[u32], Slice<u32>>(a) };
let a_ptr = NonNull::new(a.as_mut_ptr()).expect("ptr is not null");
let a_len = a.len();
assert_eq!(a_ptr, b.addr);
assert_eq!(a_len, b.length);
Ok(())
}
#[test]
fn vecbuf_copy_one_buffer() -> Result<()> {
let a = [0u8, 1, 2, 3];
let mut b = [0u8; 4];
let len = vectored_buffer_copy(
0,
b.len(),
Slice::new(&[a.as_slice()]),
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 4);
assert_eq!(a, b);
Ok(())
}
#[test]
fn vecbuf_copy_buffers() -> Result<()> {
let a1 = [0u8, 1, 2, 3];
let a2 = [4u8, 5];
let a3 = [6u8, 7];
let mut b = [0u8; 8];
let len = vectored_buffer_copy(
0,
b.len(),
Slice::new(&[a1.as_slice(), a2.as_slice(), a3.as_slice()]),
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 8);
assert_eq!(b, [0u8, 1, 2, 3, 4, 5, 6, 7]);
Ok(())
}
// 1. Basic Boundary and Edge Cases
#[test]
fn vecbuf_copy_zero_len() -> Result<()> {
let a = [1u8, 2, 3];
let mut b = [0u8; 3];
let len = vectored_buffer_copy(
0,
0, // copy_len is 0
Slice::new(&[a.as_slice()]),
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 0);
assert_eq!(b, [0, 0, 0]);
Ok(())
}
#[test]
fn vecbuf_empty_src_array() -> Result<()> {
let mut b = [0u8; 4];
let len = vectored_buffer_copy(
0,
4,
Slice::new(&[]), // Empty source vector
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 0);
Ok(())
}
#[test]
fn vecbuf_empty_dst_array() -> Result<()> {
let a = [1u8, 2];
let len = vectored_buffer_copy(
0,
2,
Slice::new(&[a.as_slice()]),
Slice::new_mut(&mut []), // Empty dest vector
);
assert_eq!(len, 0);
Ok(())
}
// 2. Source Offsets
#[test]
fn vecbuf_src_offset_skips_slice() -> Result<()> {
let a1 = [1u8, 2];
let a2 = [3u8, 4, 5];
let mut b = [0u8; 5];
let len = vectored_buffer_copy(
3, // Skip first slice entirely, and skip 1 element of the second slice
5,
Slice::new(&[a1.as_slice(), a2.as_slice()]),
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 2); // Should only copy [4, 5]
assert_eq!(b, [4, 5, 0, 0, 0]);
Ok(())
}
#[test]
fn vecbuf_src_offset_exact_boundary() -> Result<()> {
let a1 = [1u8, 2];
let a2 = [3u8, 4];
let mut b = [0u8; 4];
let len = vectored_buffer_copy(
2, // Starts exactly at the beginning of a2
4,
Slice::new(&[a1.as_slice(), a2.as_slice()]),
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 2);
assert_eq!(b, [3, 4, 0, 0]);
Ok(())
}
#[test]
fn vecbuf_src_offset_out_of_bounds() -> Result<()> {
let a = [1u8, 2];
let mut b = [0u8; 2];
let len = vectored_buffer_copy(
5, // Out of bounds
2,
Slice::new(&[a.as_slice()]),
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 0);
assert_eq!(b, [0, 0]);
Ok(())
}
// 3. Lengths and Truncations
#[test]
fn vecbuf_copy_len_less_than_available() -> Result<()> {
let a = [1u8, 2, 3, 4];
let mut b = [0u8; 4];
let len = vectored_buffer_copy(
0,
2, // Restrict to copying only 2 elements
Slice::new(&[a.as_slice()]),
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 2);
assert_eq!(b, [1, 2, 0, 0]);
Ok(())
}
#[test]
fn vecbuf_dst_shorter_than_src() -> Result<()> {
let a = [1u8, 2, 3, 4];
let mut b = [0u8; 2];
let len = vectored_buffer_copy(
0,
10, // Try to copy more than dst can hold
Slice::new(&[a.as_slice()]),
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 2); // Truncates at dst bounds
assert_eq!(b, [1, 2]);
Ok(())
}
#[test]
fn vecbuf_src_shorter_than_dst() -> Result<()> {
let a = [1u8, 2];
let mut b = [0u8; 4];
let len = vectored_buffer_copy(
0,
10, // Try to copy more than src has
Slice::new(&[a.as_slice()]),
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 2); // Truncates at src bounds
assert_eq!(b, [1, 2, 0, 0]);
Ok(())
}
// 4. Shape Mismatch.
#[test]
fn vecbuf_scatter() -> Result<()> {
let a = [1u8, 2, 3, 4];
let mut b1 = [0u8; 1];
let mut b2 = [0u8; 2];
let mut b3 = [0u8; 1];
let len = vectored_buffer_copy(
0,
4,
Slice::new(&[a.as_slice()]), // 1 large src slice
Slice::new_mut(&mut [b1.as_mut_slice(), b2.as_mut_slice(), b3.as_mut_slice()]), // Spread over 3 dst slices
);
assert_eq!(len, 4);
assert_eq!(b1, [1]);
assert_eq!(b2, [2, 3]);
assert_eq!(b3, [4]);
Ok(())
}
#[test]
fn vecbuf_misaligned_slices() -> Result<()> {
let a1 = [1u8, 2, 3];
let a2 = [4u8, 5];
let mut b1 = [0u8; 2];
let mut b2 = [0u8; 4];
let len = vectored_buffer_copy(
0,
5,
Slice::new(&[a1.as_slice(), a2.as_slice()]), // Boundaries at [3, 2]
Slice::new_mut(&mut [b1.as_mut_slice(), b2.as_mut_slice()]), // Boundaries at [2, 4]
);
assert_eq!(len, 5);
assert_eq!(b1, [1, 2]);
assert_eq!(b2, [3, 4, 5, 0]);
Ok(())
}
// 5. Zero-Length Inner Slices
#[test]
fn vecbuf_empty_slices_in_src() -> Result<()> {
let a1 = [1u8, 2];
let a2: [u8; 0] = [];
let a3 = [3u8, 4];
let mut b = [0u8; 4];
let len = vectored_buffer_copy(
0,
4,
Slice::new(&[a1.as_slice(), a2.as_slice(), a3.as_slice()]), // inner slice a2 has length 0
Slice::new_mut(&mut [b.as_mut_slice()]),
);
assert_eq!(len, 4);
assert_eq!(b, [1, 2, 3, 4]); // Zero length slice should be cleanly skipped
Ok(())
}
#[test]
fn vecbuf_empty_slices_in_dst() -> Result<()> {
let a = [1u8, 2, 3, 4];
let mut b1 = [0u8; 2];
let mut b2: [u8; 0] = [];
let mut b3 = [0u8; 2];
let len = vectored_buffer_copy(
0,
4,
Slice::new(&[a.as_slice()]),
Slice::new_mut(&mut [b1.as_mut_slice(), b2.as_mut_slice(), b3.as_mut_slice()]), // inner slice b2 has length 0
);
assert_eq!(len, 4);
assert_eq!(b1, [1, 2]);
assert_eq!(b2, []); // Zero length slice should be cleanly skipped
assert_eq!(b3, [3, 4]);
Ok(())
}
}