multiboot2_common/
boxed.rs

1//! Module for [`new_boxed`].
2
3use crate::{ALIGNMENT, Header, MaybeDynSized, increase_to_alignment};
4use alloc::boxed::Box;
5use core::alloc::Layout;
6use core::mem;
7use core::ops::Deref;
8use core::ptr;
9
10/// Creates a new tag implementing [`MaybeDynSized`] on the heap.
11///
12/// This works for sized and unsized tags. However, it only makes sense to use
13/// this for tags that are DSTs (unsized). For regular sized structs, you can
14/// just create a typical constructor and box the result.
15///
16/// The provided `header`' total size (see [`Header`]) will be set dynamically
17/// by this function using [`Header::set_size`]. However, it must contain all
18/// other relevant metadata or update it in the `set_size` callback.
19///
20/// # Parameters
21/// - `additional_bytes_slices`: Array of byte slices that should be included
22///   without additional padding in-between. You don't need to add the bytes
23///   for [`Header`], but only additional payload.
24#[must_use]
25pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(
26    mut header: T::Header,
27    additional_bytes_slices: &[&[u8]],
28) -> Box<T> {
29    let additional_size = additional_bytes_slices
30        .iter()
31        .map(|b| b.len())
32        .sum::<usize>();
33
34    let tag_size = mem::size_of::<T::Header>() + additional_size;
35    header.set_size(tag_size);
36
37    // Allocation size is multiple of alignment.
38    // See <https://doc.rust-lang.org/reference/type-layout.html>
39    let alloc_size = increase_to_alignment(tag_size);
40    let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap();
41    let heap_ptr = unsafe { alloc::alloc::alloc(layout) };
42    assert!(!heap_ptr.is_null());
43
44    // write header
45    {
46        let len = mem::size_of::<T::Header>();
47        let ptr = core::ptr::addr_of!(header);
48        unsafe {
49            ptr::copy_nonoverlapping(ptr.cast::<u8>(), heap_ptr, len);
50        }
51    }
52
53    // write body
54    {
55        let mut write_offset = mem::size_of::<T::Header>();
56        for &bytes in additional_bytes_slices {
57            let len = bytes.len();
58            let src = bytes.as_ptr();
59            unsafe {
60                let dst = heap_ptr.add(write_offset);
61                ptr::copy_nonoverlapping(src, dst, len);
62                write_offset += len;
63            }
64        }
65    }
66
67    // This is a fat pointer for DSTs and a thin pointer for sized `T`s.
68    let ptr: *mut T = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(&header));
69    let reference = unsafe { Box::from_raw(ptr) };
70
71    // If this panic triggers, there is a fundamental flaw in my logic. This is
72    // not the fault of an API user.
73    assert_eq!(
74        mem::size_of_val(reference.deref()),
75        alloc_size,
76        "Allocation should match Rusts expectation"
77    );
78
79    reference
80}
81
82/// Clones a [`MaybeDynSized`] by calling [`new_boxed`].
83#[must_use]
84pub fn clone_dyn<T: MaybeDynSized<Metadata = usize> + ?Sized>(tag: &T) -> Box<T> {
85    new_boxed(tag.header().clone(), &[tag.payload()])
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::Tag;
92    use crate::test_utils::{DummyDstTag, DummyTestHeader};
93
94    #[test]
95    fn test_new_boxed() {
96        let header = DummyTestHeader::new(DummyDstTag::ID, 0);
97        let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
98        assert_eq!(tag.header().typ(), 42);
99        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
100
101        // Test that bytes are added consecutively without gaps.
102        let header = DummyTestHeader::new(0xdead_beef, 0);
103        let tag = new_boxed::<DummyDstTag>(header, &[&[0], &[1], &[2, 3]]);
104        assert_eq!(tag.header().typ(), 0xdead_beef);
105        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
106    }
107
108    #[test]
109    fn test_clone_tag() {
110        let header = DummyTestHeader::new(DummyDstTag::ID, 0);
111        let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
112        assert_eq!(tag.header().typ(), 42);
113        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
114
115        let _cloned = clone_dyn(tag.as_ref());
116    }
117}