Skip to main content

multiboot2_common/
lib.rs

1//! Common helpers for the `multiboot2` and `multiboot2-header` crates.
2//!
3//! # Features and `no_std` Compatibility
4//!
5//! This crate is always `no_std`. The `alloc` feature enables heap-allocation
6//! helpers. The default `builder` feature enables `alloc` for consistency with
7//! the two consuming crates. Disable default features for allocator-free
8//! parsing.
9//!
10//! # Value-add
11//!
12//! The main value-add of this crate is to abstract away the parsing and
13//! construction of Multiboot2 structures. This is more complex than it may
14//! sound at first due to the difficulties listed below. It also provides tag
15//! iteration.
16//!
17//! The abstractions provided by this crate serve as the base for the following
18//! related structures:
19//! - multiboot2:
20//!   - boot information
21//!   - boot information header (the fixed-size beginning of boot
22//!     information)
23//!   - boot information tags
24//!   - boot information tag header (the fixed-size beginning of a tag)
25//! - multiboot2-header:
26//!   - Multiboot2 header
27//!   - basic header (the fixed-size beginning of a Multiboot2 header)
28//!   - header tags
29//!   - header tag header (the fixed-size beginning of a tag)
30//!
31//! # TL;DR: Specific Example
32//!
33//! To name a specific example, the `multiboot2` crate just needs the following
34//! types:
35//!
36//! - `BootInformationHeader` implementing [`Header`]
37//! - `BootInformation` wrapping [`DynSizedStructure`]
38//! - `type TagIter<'a> = multiboot2_common::TagIter<'a, TagHeader>`
39//!   ([`TagIter`])
40//! - `TagHeader` implementing [`Header`]
41//! - Structs for each tag, each implementing [`MaybeDynSized`]
42//!
43//! Then, all the magic using the [`TagIter`] and [`DynSizedStructure::cast`]
44//! can easily be utilized.
45//!
46//! The same correspondingly applies to the structures in `multiboot2-header`.
47//!
48//! # Design, Solved Problem, and Difficulties along the Way
49//!
50//! The design choice to have ABI-compatible Rust types in
51//! `multiboot2` and `multiboot2-header` mainly influenced the requirements and
52//! difficulties. These obstacles, in turn, influenced the design. The outcome
53//! is intended to provide a convenient, idiomatic Rust interface.
54//!
55//! ## Architecture Diagrams
56//!
57//! The figures in the [README](https://crates.io/crates/multiboot2-common)
58//! (currently not embeddable in lib.rs unfortunately) provide an overview of
59//! the parsing of Multiboot2 structures and how the definitions from this
60//! crate are used.
61//!
62//! Note that although the diagrams seem complex, most logic is in
63//! `multiboot2-common`. For downstream users, the usage is quite simple.
64//!
65//! ## Multiboot2 Structures
66//!
67//! Multiboot2 structures are a consecutive chunk of bytes in memory. They use
68//! the "header pattern", which means a fixed size and known [`Header`] type
69//! indicates the total size of the structure. This is roughly translated to the
70//! following Rust base type:
71//!
72//! ```rust,ignore
73//! #[repr(C, align(8))]
74//! struct DynStructure {
75//!     header: MyHeader,
76//!     payload: [u8]
77//! }
78//! ```
79//!
80//! Note that these structures can also be nested. So for example, the
81//! Multiboot2 boot information contains Multiboot2 tags, and the Multiboot2
82//! header contains Multiboot2 header tags - both are themselves **dynamically
83//! sized** structures. Their sizes and numbers of elements are known only at
84//! runtime.
85//!
86//! A final `[u8]` field in the structs is the most direct Rust representation.
87//! However, this makes the type a Dynamically Sized Type (DST). To create
88//! references to these types from a byte slice, one needs fat pointers. They
89//! are a language feature currently not constructable with stable Rust.
90//! Luckily, we can utilize [`ptr_meta`].
91//!
92//! Figure 1 in the [README](https://crates.io/crates/multiboot2-common)
93//! (currently not embeddable in lib.rs unfortunately) provides an overview of
94//! Multiboot2 structures.
95//!
96//! ## Dynamic and Sized Structs in Rust
97//!
98//! Note that some Multiboot2 structures (tags) look like this:
99//!
100//! ```rust,ignore
101//! #[repr(C, align(8))]
102//! struct DynStructure {
103//!     header: MyHeader,
104//!     // Not just [`u8`]
105//!     payload: [SomeType]
106//! }
107//! ```
108//!
109//! or
110//!
111//! ```rust,ignore
112//! #[repr(C, align(8))]
113//! struct CommandLineTag {
114//!     header: TagHeader,
115//!     start: u32,
116//!     end: u32,
117//!     // More than just the base header before the dynamic portion
118//!     data: [u8]
119//! }
120//! ```
121//!
122//! ## Chosen Design
123//!
124//! The overall common abstractions needed to solve the problems mentioned in
125//! this section are also mainly influenced by the fact that the `multiboot2`
126//! and `multiboot2-header` crates use a **zero-copy** design by parsing the
127//! corresponding raw bytes as **ABI-compatible types** that represent all of
128//! their memory.
129//!
130//! Further, by having ABI-compatible types that fully represent the reality, we
131//! can use the same type for parsing **and** for construction, as modelled in
132//! the following simplified example:
133//!
134//! ```rust,ignore
135//! /// ABI-compatible tag for parsing.
136//! #[repr(C)]
137//! pub struct MemoryMapTag {
138//!     header: TagHeader,
139//!     entry_size: u32,
140//!     entry_version: u32,
141//!     areas: [MemoryArea],
142//! }
143//!
144//! impl MemoryMapTag {
145//!     // We can also create an ABI-compatible structure of that type.
146//!     pub fn new(areas: &[MemoryArea]) -> Box<Self> {
147//!         // omitted
148//!     }
149//! }
150//! ```
151//!
152//! Hence, the structures can also be built at runtime through the same types
153//! used for parsing.
154//!
155//! ## Creating Fat Pointers with [`ptr_meta`]
156//!
157//! Fat pointers are a language feature and the base for references to
158//! dynamically sized types, such as `&str`, `&[T]`, `dyn T` or
159//! `&DynamicallySizedStruct`.
160//!
161//! Currently, they can't be created using the standard library, but
162//! [`ptr_meta`] can be utilized.
163//!
164//! To create fat pointers with [`ptr_meta`], each tag needs a `Metadata` type
165//! which is either `usize` (for DSTs) or `()`. A trait is needed to abstract
166//! over sized and unsized types. This is done by [`MaybeDynSized`].
167//!
168//! ## Multiboot2 Requirements
169//!
170//! All tags must be 8-byte aligned. The actual payload of tags may be followed
171//! by padding zeroes to fill the gap until the next alignment boundary, if
172//! necessary. These zeroes are not reflected in the tag's size, but for Rust,
173//! must be reflected in the type's memory allocation.
174//!
175//! ## Rustc Requirements
176//!
177//! The required allocation space that Rust uses for types is a multiple of the
178//! alignment. This means that if we cast between byte slices and specific
179//! types, Rust doesn't just see the "trimmed down actual payload" defined by
180//! struct members, but also any necessary hidden padding bytes. If we do not
181//! account for that padding, for example by casting bytes from a `&[u8; 15]`
182//! to an 8-byte-aligned struct, Miri will report an error because Rust expects
183//! the allocation to cover 16 bytes.
184//!
185//! See <https://doc.rust-lang.org/reference/type-layout.html> for information.
186//!
187//! Further, this means that we can't cast references to smaller structs to
188//! larger ones. Once we construct a `Box` using the `new_boxed` helper, we
189//! must also ensure that the default
190//! [`Layout`] for the underlying type equals the one we manually used for the
191//! allocation.
192//!
193//! ## Parsing and Casting
194//!
195//! The general idea of parsing is that the lifetime of the original byte slice
196//! propagates through to references of target types.
197//!
198//! First, we need byte slices which are guaranteed to be aligned and are a
199//! multiple of the alignment. We have [`BytesRef`] for that. With that, we can
200//! create a [`DynSizedStructure`]. This type covers exactly the bytes reported
201//! by its header. With the help of [`MaybeDynSized`], we can call
202//! [`DynSizedStructure::cast`] to cast this to arbitrary sized or unsized
203//! struct types fulfilling the corresponding requirements.
204//!
205//! This way, one can create Rust structs modeling the structure of the
206//! tags, and we only need a single "complicated" type, namely
207//! [`DynSizedStructure`].
208//!
209//! ## Iterating Tags
210//!
211//! To iterate over the tags of a structure, use [`TagIter`].
212//!
213//! # Memory Guarantees and Safety Promises
214//!
215//! The parsing and construction APIs preserve the alignment and padding
216//! guarantees discussed above. Parsing APIs report malformed input with
217//! appropriate error types. Construction APIs establish the same invariants
218//! and may panic when their documented preconditions are violated. Neither
219//! malformed input nor a failed invariant may cause undefined behavior.
220//!
221//! # Stability
222//!
223//! This crate primarily supports `multiboot2` and `multiboot2-header`. Its
224//! public API may evolve with their internals and is not intended as an
225//! independent stable abstraction.
226//!
227//! [`Layout`]: core::alloc::Layout
228
229#![no_std]
230// --- BEGIN STYLE CHECKS ---
231#![deny(
232    clippy::all,
233    clippy::cargo,
234    clippy::nursery,
235    clippy::must_use_candidate,
236    clippy::undocumented_unsafe_blocks,
237    missing_debug_implementations,
238    missing_docs,
239    rustdoc::all
240)]
241#![allow(clippy::multiple_crate_versions)]
242// --- END STYLE CHECKS ---
243
244#[cfg_attr(test, macro_use)]
245#[cfg(test)]
246extern crate std;
247
248#[cfg(feature = "alloc")]
249extern crate alloc;
250
251#[doc(hidden)]
252pub mod test_utils;
253
254#[cfg(feature = "alloc")]
255mod boxed;
256mod bytes_ref;
257mod iter;
258mod raw;
259mod tag;
260
261#[cfg(feature = "alloc")]
262pub use boxed::{clone_dyn, new_boxed};
263pub use bytes_ref::BytesRef;
264pub use iter::TagIter;
265pub use tag::{MaybeDynSized, Tag};
266
267use core::fmt::Debug;
268use core::ptr::NonNull;
269use core::slice;
270use thiserror::Error;
271
272/// The alignment of all Multiboot2 data structures.
273pub const ALIGNMENT: usize = 8;
274
275/// A sized header type for [`DynSizedStructure`].
276///
277/// Note that `header` refers to the header pattern. Thus, depending on the use
278/// case, this is not just a tag header. Instead, it refers to all bytes that
279/// are fixed and not part of any optional terminating dynamic `[u8]` slice in a
280/// [`DynSizedStructure`].
281///
282/// The alignment of implementors **must** be compatible with the requirements
283/// for the corresponding structure, which typically is [`ALIGNMENT`].
284///
285/// # Safety
286///
287/// Implementors must be `#[repr(C)]`, have no padding bytes or interior
288/// mutability, allow every bit pattern, and have an alignment of at most
289/// [`ALIGNMENT`]. Headers are referenced from raw memory and copied byte-wise.
290pub unsafe trait Header: Clone + Sized + PartialEq + Eq + Debug {
291    /// Returns the total size of the structure in bytes, including the fixed
292    /// header and any dynamic payload.
293    #[must_use]
294    fn total_size(&self) -> usize;
295
296    /// Returns the length of the payload, i.e., the bytes that are additional
297    /// to the header. The value is measured in bytes.
298    #[must_use]
299    fn payload_len(&self) -> usize {
300        let total_size = self.total_size();
301        assert!(total_size >= size_of::<Self>());
302        total_size - size_of::<Self>()
303    }
304
305    /// Updates the header with the given `total_size`.
306    fn set_size(&mut self, total_size: usize);
307}
308
309/// A C ABI-compatible dynamically sized type with a common sized [`Header`]
310/// and a dynamic amount of bytes without hidden implicit padding.
311///
312/// This structure combines a [`Header`] with the data described by that header
313/// according to [`Header::total_size`]. Instances guarantee that the memory
314/// requirements promised in the crate description are respected.
315///
316/// This can be a Multiboot2 header tag, information tag, boot information, or
317/// a Multiboot2 header. It is the base for **same-size casts** to these
318/// corresponding structures using [`DynSizedStructure::cast`]. Depending on the
319/// context, the [`Header`] is different (basic header, boot information header,
320/// header tag header, or boot information tag header).
321///
322/// # ABI
323/// This type uses the C ABI. The fixed [`Header`] portion is always there.
324/// Further, there is a variable amount of payload bytes. Thus, this type can
325/// only exist on the heap or references to it can be made by cast via fat
326/// pointers. The main constructor is [`DynSizedStructure::ref_from_bytes`].
327///
328/// As terminating padding might be necessary for the proper Rust type layout,
329/// `size_of_val(&self)` might report additional padding bytes that are not
330/// reflected by the actual payload. These additional padding bytes however
331/// will be reflected in corresponding [`BytesRef`] instances from that this
332/// structure was created.
333#[derive(Debug, PartialEq, Eq, ptr_meta::Pointee)]
334#[repr(C, align(8))]
335pub struct DynSizedStructure<H: Header> {
336    header: H,
337    payload: [u8],
338    // Plus optional padding bytes to next alignment boundary, which are not
339    // reflected here. However, Rustc allocates them anyway and expects them
340    // to be there.
341    // See <https://doc.rust-lang.org/reference/type-layout.html>.
342}
343
344impl<H: Header> DynSizedStructure<H> {
345    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
346    /// from the given [`BytesRef`].
347    pub fn ref_from_bytes(bytes: BytesRef<'_, H>) -> Result<&Self, MemoryError> {
348        let ptr = bytes.as_ptr().cast::<H>();
349        // SAFETY: `BytesRef` guarantees alignment and that the buffer covers
350        // at least the fixed header size.
351        let hdr = unsafe { &*ptr };
352
353        let total_size = hdr.total_size();
354        let header_size = size_of::<H>();
355        if total_size < header_size {
356            return Err(MemoryError::SizeInsufficient(total_size, header_size));
357        }
358        if total_size > bytes.len() {
359            return Err(MemoryError::InvalidReportedTotalSize(
360                total_size,
361                bytes.len(),
362            ));
363        }
364        let payload_len = total_size - header_size;
365
366        // At this point we know that the memory slice fulfills the base
367        // assumptions and requirements. We can now safely create the fat
368        // pointer.
369
370        let dst_size = payload_len;
371        // Create fat pointer for the DST.
372        let ptr = ptr_meta::from_raw_parts(ptr.cast(), dst_size);
373        // SAFETY: The allocation was sized from the validated reported total
374        // size, so the fat pointer refers to initialized memory.
375        let reference = unsafe { &*ptr };
376        Ok(reference)
377    }
378
379    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
380    /// from the given `&[u8]`.
381    pub fn ref_from_slice(bytes: &[u8]) -> Result<&Self, MemoryError> {
382        let bytes = BytesRef::<H>::try_from(bytes)?;
383        Self::ref_from_bytes(bytes)
384    }
385
386    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
387    /// from the given thin pointer to the [`Header`]. It reads the total size
388    /// from the header.
389    ///
390    /// # Safety
391    /// The caller must ensure that `ptr` is readable for at least the size of
392    /// [`Header`], and, once its reported total size is known, for that whole
393    /// range.
394    pub unsafe fn ref_from_ptr<'a>(ptr: NonNull<H>) -> Result<&'a Self, MemoryError> {
395        let ptr = ptr.as_ptr().cast_const();
396
397        // Alignment check. All headers are `align(8)`.
398        if ptr.cast::<u8>().align_offset(ALIGNMENT) != 0 {
399            return Err(MemoryError::WrongAlignment);
400        }
401
402        // SAFETY: `ptr` is non-null (from `NonNull`) and now known to be
403        // aligned; we only read the reported total size and immediately
404        // re-slice that range.
405        let hdr = unsafe { &*ptr };
406        let total_size = hdr.total_size();
407        let header_size = size_of::<H>();
408        if total_size < header_size {
409            return Err(MemoryError::SizeInsufficient(total_size, header_size));
410        }
411
412        // SAFETY: `total_size` came from the validated header and matches the
413        // readable byte range for the structure.
414        let slice = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), total_size) };
415        Self::ref_from_slice(slice)
416    }
417
418    /// Returns the underlying [`Header`].
419    pub const fn header(&self) -> &H {
420        &self.header
421    }
422
423    /// Returns the underlying payload.
424    pub const fn payload(&self) -> &[u8] {
425        &self.payload
426    }
427
428    /// Performs a memory-safe same-size cast from the base-structure to a
429    /// specific [`MaybeDynSized`]. The idea here is to cast the generic
430    /// mostly semantic-free version to a specific type with fields that have
431    /// a clear semantic.
432    ///
433    /// The provided `T` may be sized or dynamically sized. The source and
434    /// target have the same actual payload size and [`size_of_val`].
435    ///
436    /// # Panics
437    /// Panics if `T` cannot represent the same allocation size. This should not
438    /// happen when all types follow their documented requirements.
439    pub fn cast<T: MaybeDynSized<Header = H> + ?Sized>(&self) -> &T
440    where
441        T::Metadata: Default,
442    {
443        // Thin or fat pointer, depending on type.
444        // However, only thin ptr is needed.
445        let base_ptr = &raw const *self;
446
447        // This should be a compile-time assertion. However, this is the best
448        // location to place it for now.
449        assert!(T::BASE_SIZE >= size_of::<H>());
450
451        // Check the size of the allocation is big enough.
452        assert!(
453            size_of_val(self) >= T::BASE_SIZE,
454            "source is too small to be cast to the target type"
455        );
456
457        let t_dst_size = T::dst_len(self.header());
458        // Creates thin or fat pointer, depending on type.
459        let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
460        // SAFETY: `self` is a valid reference and the cast keeps the same
461        // allocation; `T::dst_len` determines the matching tail length. The
462        // assertion above guarantees the retagged extent stays in bounds.
463        let t_ref = unsafe { &*t_ptr };
464
465        assert_eq!(size_of_val(self), size_of_val(t_ref));
466
467        t_ref
468    }
469}
470
471/// Validates a sequence of padded Multiboot2 (header) tags.
472///
473/// Both Multiboot2 information tags and Multiboot2 header tags use an 8-byte
474/// tag header with the reported tag size stored in bytes 4..8. The reported
475/// size excludes alignment padding, but each following tag starts at the next
476/// 8-byte boundary.
477///
478/// Returns `Ok(true)` when a valid end tag is present exactly at the end of the
479/// provided byte range, and `Ok(false)` when the byte range ends without an end
480/// tag.
481pub fn validate_tag_sequence(
482    bytes: &[u8],
483    mut is_end_tag: impl FnMut(&[u8]) -> bool,
484) -> Result<bool, MemoryError> {
485    // Common header property for Multiboot2 and Multiboot2 header tags:
486    // The `size` property is always at offset 4..8 (the second u32).
487    const TAG_HEADER_SIZE: usize = size_of::<u32>() * 2;
488
489    if bytes.as_ptr().align_offset(ALIGNMENT) != 0 {
490        return Err(MemoryError::WrongAlignment);
491    }
492
493    let mut offset = 0;
494    while offset < bytes.len() {
495        let remaining = bytes.len() - offset;
496        if remaining < TAG_HEADER_SIZE {
497            return Err(MemoryError::ShorterThanHeader);
498        }
499
500        let tag = &bytes[offset..];
501        let total_size =
502            u32::from_le_bytes(tag[4..8].try_into().expect("slice has exactly 4 bytes")) as usize;
503
504        if total_size < TAG_HEADER_SIZE {
505            return Err(MemoryError::SizeInsufficient(total_size, TAG_HEADER_SIZE));
506        }
507
508        let padded_size = total_size
509            .checked_add(ALIGNMENT - 1)
510            .map(|size| size & !(ALIGNMENT - 1))
511            .ok_or(MemoryError::InvalidReportedTotalSize(total_size, remaining))?;
512        if padded_size > remaining {
513            return Err(MemoryError::InvalidReportedTotalSize(
514                padded_size,
515                remaining,
516            ));
517        }
518
519        offset += padded_size;
520        if is_end_tag(&tag[..total_size]) {
521            if offset == bytes.len() {
522                return Ok(true);
523            }
524            return Err(MemoryError::InvalidReportedTotalSize(offset, bytes.len()));
525        }
526    }
527
528    Ok(false)
529}
530
531/// Errors that may occur when working with memory.
532#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Error)]
533pub enum MemoryError {
534    /// The memory points to null.
535    #[error("memory points to null")]
536    Null,
537    /// The memory must be at least [`ALIGNMENT`]-aligned.
538    #[error("memory is not properly aligned")]
539    WrongAlignment,
540    /// The memory must cover at least the length of the sized structure header
541    /// type.
542    #[error("memory range is shorter than the size of the header structure")]
543    ShorterThanHeader,
544    /// The size is insufficient to contain at least a valid minimal structure.
545    #[error("memory range is shorter than the size of the header structure")]
546    SizeInsufficient(usize /* actual */, usize /* expected */),
547    /// The buffer misses the terminating padding to the next alignment
548    /// boundary. The padding is relevant to satisfy Rustc/Miri, but also the
549    /// spec mandates that the padding is added.
550    #[error("memory is missing required padding")]
551    MissingPadding,
552    /// The size-property has an illegal value that can't be fulfilled with the
553    /// given bytes.
554    #[error(
555        "header reports an invalid total size of 0x{0:x} while only 0x{1:x} bytes are available"
556    )]
557    InvalidReportedTotalSize(usize /* actual */, usize /* expected */),
558}
559
560/// Increases the given size to the next alignment boundary, if it is not a
561/// multiple of the alignment yet.
562///
563/// This is relevant as in Rust's [type layout], the allocated size of a type is
564/// always a multiple of the alignment, even if the type is smaller.
565///
566/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
567#[must_use]
568pub const fn increase_to_alignment(size: usize) -> usize {
569    let mask = ALIGNMENT - 1;
570    (size + mask) & !mask
571}
572
573#[cfg(test)]
574mod tests {
575    use super::*;
576    use crate::test_utils::{AlignedBytes, DummyTestHeader};
577    use core::borrow::Borrow;
578
579    #[test]
580    fn test_increase_to_alignment() {
581        assert_eq!(increase_to_alignment(0), 0);
582        assert_eq!(increase_to_alignment(1), 8);
583        assert_eq!(increase_to_alignment(7), 8);
584        assert_eq!(increase_to_alignment(8), 8);
585        assert_eq!(increase_to_alignment(9), 16);
586    }
587
588    #[test]
589    fn test_cast_generic_tag_to_sized_tag() {
590        #[repr(C)]
591        struct CustomSizedTag {
592            tag_header: DummyTestHeader,
593            a: u32,
594            b: u32,
595        }
596
597        // SAFETY: The tag is repr(C) with the header as first field, any
598        // bit pattern is valid, and `BASE_SIZE` matches the ABI.
599        unsafe impl MaybeDynSized for CustomSizedTag {
600            type Header = DummyTestHeader;
601
602            const BASE_SIZE: usize = size_of::<Self>();
603
604            fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
605        }
606
607        let bytes = AlignedBytes([
608            /* id: 0xffff_ffff */
609            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
610            16, 0, 0, 0, /* field a: 0xdead_beef */
611            0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
612            0x37, 0x13, 0x37, 0x13,
613        ]);
614        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
615        let custom_tag = tag.cast::<CustomSizedTag>();
616
617        assert_eq!(size_of_val(custom_tag), 16);
618        assert_eq!(custom_tag.a, 0xdead_beef);
619        assert_eq!(custom_tag.b, 0x1337_1337);
620    }
621
622    #[test]
623    fn test_cast_generic_tag_to_self() {
624        #[rustfmt::skip]
625        let bytes = AlignedBytes::new(
626            [
627                0x37, 0x13, 0, 0,
628                /* Tag size */
629                18, 0, 0, 0,
630                /* Some payload.  */
631                0, 1, 2, 3,
632                4, 5, 6, 7,
633                8, 9,
634                // Padding
635                0, 0, 0, 0, 0, 0
636            ],
637        );
638        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
639
640        // Main objective here is also that this test passes Miri.
641        let tag = tag.cast::<DynSizedStructure<DummyTestHeader>>();
642        assert_eq!(tag.header().typ(), 0x1337);
643        assert_eq!(tag.header().size(), 18);
644    }
645
646    #[test]
647    fn test_ref_from_ptr_rejects_misaligned() {
648        // A misaligned pointer must be reported as an error, not dereferenced
649        // (which would be UB, caught by Miri).
650        let bytes = AlignedBytes([0x37, 0x13, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
651        // Guaranteed misaligned: offset 4 into an 8-byte-aligned buffer.
652        let misaligned = (&raw const bytes.0[4]).cast::<DummyTestHeader>();
653        let ptr = NonNull::new(misaligned.cast_mut()).unwrap();
654        // SAFETY: `ptr` is non-null and the constructor will reject the misalignment..
655        let result = unsafe { DynSizedStructure::<DummyTestHeader>::ref_from_ptr(ptr) };
656        assert_eq!(result, Err(MemoryError::WrongAlignment));
657    }
658
659    #[test]
660    #[should_panic(expected = "source is too small to be cast to the target type")]
661    fn test_cast_rejects_too_small_source() {
662        // A sized target larger than the (validly terminated but truncated)
663        // source must be rejected before the reference is created, rather
664        // than retagging out of bounds (which would be UB under Miri).
665        #[repr(C, align(8))]
666        struct CustomSizedTag {
667            tag_header: DummyTestHeader,
668            a: u32,
669            b: u32,
670        }
671
672        // SAFETY: The tag is repr(C) with the header as first field, any
673        // bit pattern is valid, and `BASE_SIZE` matches the ABI.
674        unsafe impl MaybeDynSized for CustomSizedTag {
675            type Header = DummyTestHeader;
676
677            const BASE_SIZE: usize = size_of::<Self>();
678
679            fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
680        }
681
682        // Reports a total size of only 8 bytes, i.e., just the header.
683        let bytes = AlignedBytes([0x37, 0x13, 0, 0, 8, 0, 0, 0]);
684        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
685        // `CustomSizedTag` needs 16 bytes; casting must panic, not read OOB.
686        let _ = tag.cast::<CustomSizedTag>();
687    }
688
689    #[test]
690    fn test_ref_from_slice_rejects_oversized_header() {
691        #[rustfmt::skip]
692        let bytes = AlignedBytes::new(
693            [
694                0x37, 0x13, 0, 0,
695                /* Tag size */
696                24, 0, 0, 0,
697                /* Only 8 bytes payload plus padding are available. */
698                0, 1, 2, 3,
699                4, 5, 6, 7,
700            ],
701        );
702
703        assert_eq!(
704            DynSizedStructure::<DummyTestHeader>::ref_from_slice(bytes.borrow()),
705            Err(MemoryError::InvalidReportedTotalSize(24, 16))
706        );
707    }
708
709    #[test]
710    fn test_ref_from_slice_rejects_too_small_reported_size() {
711        #[rustfmt::skip]
712        let bytes = AlignedBytes::new(
713            [
714                0x37, 0x13, 0, 0,
715                /* Tag size */
716                4, 0, 0, 0,
717                /* Remaining bytes are irrelevant. */
718                0, 1, 2, 3,
719                0, 0, 0, 0,
720            ],
721        );
722
723        assert_eq!(
724            DynSizedStructure::<DummyTestHeader>::ref_from_slice(bytes.borrow()),
725            Err(MemoryError::SizeInsufficient(4, 8))
726        );
727    }
728}