use crate::{ALIGNMENT, Header, MaybeDynSized, increase_to_alignment};
use alloc::boxed::Box;
use core::alloc::Layout;
use core::ops::Deref;
use core::ptr;
#[must_use]
pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(
mut header: T::Header,
additional_bytes_slices: &[&[u8]],
) -> Box<T> {
let additional_size = additional_bytes_slices
.iter()
.map(|b| b.len())
.sum::<usize>();
let tag_size = size_of::<T::Header>() + additional_size;
header.set_size(tag_size);
assert_eq!(
header.total_size(),
tag_size,
"the reported size should round-trip through the header"
);
let alloc_size = increase_to_alignment(tag_size);
let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap();
let heap_ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
assert!(!heap_ptr.is_null());
{
let len = size_of::<T::Header>();
let ptr = &raw const header;
unsafe {
ptr::copy_nonoverlapping(ptr.cast::<u8>(), heap_ptr, len);
}
}
{
let mut write_offset = size_of::<T::Header>();
for &bytes in additional_bytes_slices {
let len = bytes.len();
let src = bytes.as_ptr();
let dst = heap_ptr.wrapping_add(write_offset);
unsafe {
ptr::copy_nonoverlapping(src, dst, len);
}
write_offset += len;
}
}
let ptr: *mut T = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(&header));
let reference = unsafe { Box::from_raw(ptr) };
assert_eq!(
size_of_val(reference.deref()),
alloc_size,
"Allocation should match Rusts expectation"
);
reference
}
#[must_use]
pub fn clone_dyn<T: MaybeDynSized<Metadata = usize> + ?Sized>(tag: &T) -> Box<T> {
new_boxed(tag.header().clone(), &[tag.payload()])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Tag;
use crate::test_utils::{DummyDstTag, DummyTestHeader};
use core::slice;
#[test]
fn test_new_boxed() {
let header = DummyTestHeader::new(DummyDstTag::ID, 0);
let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
assert_eq!(tag.header().typ(), 42);
assert_eq!(tag.payload(), &[0, 1, 2, 3]);
let header = DummyTestHeader::new(0xdead_beef, 0);
let tag = new_boxed::<DummyDstTag>(header, &[&[0], &[1], &[2, 3]]);
assert_eq!(tag.header().typ(), 0xdead_beef);
assert_eq!(tag.payload(), &[0, 1, 2, 3]);
}
#[test]
fn test_new_boxed_zeroes_padding() {
let header = DummyTestHeader::new(DummyDstTag::ID, 0);
let tag = new_boxed::<DummyDstTag>(header, &[&[0xff]]);
assert_eq!(tag.as_bytes().len(), 9);
let ptr = (&raw const *tag).cast::<u8>();
let all_bytes = unsafe { slice::from_raw_parts(ptr, size_of_val(&*tag)) };
assert_eq!(all_bytes.len(), 16);
assert_eq!(&all_bytes[9..16], &[0, 0, 0, 0, 0, 0, 0]);
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
struct TinySizeHeader {
size: u8,
_pad: [u8; 7],
}
unsafe impl crate::Header for TinySizeHeader {
fn total_size(&self) -> usize {
self.size as usize
}
fn set_size(&mut self, total_size: usize) {
self.size = total_size as u8;
}
}
#[test]
#[should_panic(expected = "round-trip")]
fn test_new_boxed_rejects_lossy_set_size() {
let header = TinySizeHeader {
size: 0,
_pad: [0; 7],
};
let _ = new_boxed::<crate::DynSizedStructure<TinySizeHeader>>(header, &[&[0_u8; 256]]);
}
#[test]
fn test_clone_tag() {
let header = DummyTestHeader::new(DummyDstTag::ID, 0);
let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3, 4]]);
assert_eq!(tag.header().typ(), 42);
assert_eq!(tag.payload(), &[0, 1, 2, 3, 4]);
let cloned = clone_dyn(tag.as_ref());
assert_eq!(cloned.header(), tag.header());
assert_eq!(cloned.payload(), tag.payload());
}
}