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 tag;
259
260#[cfg(feature = "alloc")]
261pub use boxed::{clone_dyn, new_boxed};
262pub use bytes_ref::BytesRef;
263pub use iter::TagIter;
264pub use tag::{MaybeDynSized, Tag};
265
266use core::fmt::Debug;
267use core::ptr::NonNull;
268use core::slice;
269use thiserror::Error;
270
271/// The alignment of all Multiboot2 data structures.
272pub const ALIGNMENT: usize = 8;
273
274/// A sized header type for [`DynSizedStructure`].
275///
276/// Note that `header` refers to the header pattern. Thus, depending on the use
277/// case, this is not just a tag header. Instead, it refers to all bytes that
278/// are fixed and not part of any optional terminating dynamic `[u8]` slice in a
279/// [`DynSizedStructure`].
280///
281/// The alignment of implementors **must** be compatible with the requirements
282/// for the corresponding structure, which typically is [`ALIGNMENT`].
283pub trait Header: Clone + Sized + PartialEq + Eq + Debug {
284    /// Returns the total size of the structure in bytes, including the fixed
285    /// header and any dynamic payload.
286    #[must_use]
287    fn total_size(&self) -> usize;
288
289    /// Returns the length of the payload, i.e., the bytes that are additional
290    /// to the header. The value is measured in bytes.
291    #[must_use]
292    fn payload_len(&self) -> usize {
293        let total_size = self.total_size();
294        assert!(total_size >= size_of::<Self>());
295        total_size - size_of::<Self>()
296    }
297
298    /// Updates the header with the given `total_size`.
299    fn set_size(&mut self, total_size: usize);
300}
301
302/// A C ABI-compatible dynamically sized type with a common sized [`Header`]
303/// and a dynamic amount of bytes without hidden implicit padding.
304///
305/// This structure combines a [`Header`] with the data described by that header
306/// according to [`Header::total_size`]. Instances guarantee that the memory
307/// requirements promised in the crate description are respected.
308///
309/// This can be a Multiboot2 header tag, information tag, boot information, or
310/// a Multiboot2 header. It is the base for **same-size casts** to these
311/// corresponding structures using [`DynSizedStructure::cast`]. Depending on the
312/// context, the [`Header`] is different (basic header, boot information header,
313/// header tag header, or boot information tag header).
314///
315/// # ABI
316/// This type uses the C ABI. The fixed [`Header`] portion is always there.
317/// Further, there is a variable amount of payload bytes. Thus, this type can
318/// only exist on the heap or references to it can be made by cast via fat
319/// pointers. The main constructor is [`DynSizedStructure::ref_from_bytes`].
320///
321/// As terminating padding might be necessary for the proper Rust type layout,
322/// `size_of_val(&self)` might report additional padding bytes that are not
323/// reflected by the actual payload. These additional padding bytes however
324/// will be reflected in corresponding [`BytesRef`] instances from that this
325/// structure was created.
326#[derive(Debug, PartialEq, Eq, ptr_meta::Pointee)]
327#[repr(C, align(8))]
328pub struct DynSizedStructure<H: Header> {
329    header: H,
330    payload: [u8],
331    // Plus optional padding bytes to next alignment boundary, which are not
332    // reflected here. However, Rustc allocates them anyway and expects them
333    // to be there.
334    // See <https://doc.rust-lang.org/reference/type-layout.html>.
335}
336
337impl<H: Header> DynSizedStructure<H> {
338    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
339    /// from the given [`BytesRef`].
340    pub fn ref_from_bytes(bytes: BytesRef<'_, H>) -> Result<&Self, MemoryError> {
341        let ptr = bytes.as_ptr().cast::<H>();
342        // SAFETY: `BytesRef` guarantees alignment and that the buffer covers
343        // at least the fixed header size.
344        let hdr = unsafe { &*ptr };
345
346        let total_size = hdr.total_size();
347        let header_size = size_of::<H>();
348        if total_size < header_size {
349            return Err(MemoryError::SizeInsufficient(total_size, header_size));
350        }
351        if total_size > bytes.len() {
352            return Err(MemoryError::InvalidReportedTotalSize(
353                total_size,
354                bytes.len(),
355            ));
356        }
357        let payload_len = total_size - header_size;
358
359        // At this point we know that the memory slice fulfills the base
360        // assumptions and requirements. We can now safely create the fat
361        // pointer.
362
363        let dst_size = payload_len;
364        // Create fat pointer for the DST.
365        let ptr = ptr_meta::from_raw_parts(ptr.cast(), dst_size);
366        // SAFETY: The allocation was sized from the validated reported total
367        // size, so the fat pointer refers to initialized memory.
368        let reference = unsafe { &*ptr };
369        Ok(reference)
370    }
371
372    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
373    /// from the given `&[u8]`.
374    pub fn ref_from_slice(bytes: &[u8]) -> Result<&Self, MemoryError> {
375        let bytes = BytesRef::<H>::try_from(bytes)?;
376        Self::ref_from_bytes(bytes)
377    }
378
379    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
380    /// from the given thin pointer to the [`Header`]. It reads the total size
381    /// from the header.
382    ///
383    /// # Safety
384    /// The caller must ensure that `ptr` is readable for at least the size of
385    /// [`Header`], and, once its reported total size is known, for that whole
386    /// range.
387    pub unsafe fn ref_from_ptr<'a>(ptr: NonNull<H>) -> Result<&'a Self, MemoryError> {
388        let ptr = ptr.as_ptr().cast_const();
389
390        // Alignment check. All headers are `align(8)`.
391        if ptr.cast::<u8>().align_offset(ALIGNMENT) != 0 {
392            return Err(MemoryError::WrongAlignment);
393        }
394
395        // SAFETY: `ptr` is non-null (from `NonNull`) and now known to be
396        // aligned; we only read the reported total size and immediately
397        // re-slice that range.
398        let hdr = unsafe { &*ptr };
399        let total_size = hdr.total_size();
400        let header_size = size_of::<H>();
401        if total_size < header_size {
402            return Err(MemoryError::SizeInsufficient(total_size, header_size));
403        }
404
405        // SAFETY: `total_size` came from the validated header and matches the
406        // readable byte range for the structure.
407        let slice = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), total_size) };
408        Self::ref_from_slice(slice)
409    }
410
411    /// Returns the underlying [`Header`].
412    pub const fn header(&self) -> &H {
413        &self.header
414    }
415
416    /// Returns the underlying payload.
417    pub const fn payload(&self) -> &[u8] {
418        &self.payload
419    }
420
421    /// Performs a memory-safe same-size cast from the base-structure to a
422    /// specific [`MaybeDynSized`]. The idea here is to cast the generic
423    /// mostly semantic-free version to a specific type with fields that have
424    /// a clear semantic.
425    ///
426    /// The provided `T` may be sized or dynamically sized. The source and
427    /// target have the same actual payload size and [`size_of_val`].
428    ///
429    /// # Panics
430    /// Panics if `T` cannot represent the same allocation size. This should not
431    /// happen when all types follow their documented requirements.
432    pub fn cast<T: MaybeDynSized<Header = H> + ?Sized>(&self) -> &T
433    where
434        T::Metadata: Default,
435    {
436        // Thin or fat pointer, depending on type.
437        // However, only thin ptr is needed.
438        let base_ptr = &raw const *self;
439
440        // This should be a compile-time assertion. However, this is the best
441        // location to place it for now.
442        assert!(T::BASE_SIZE >= size_of::<H>());
443
444        // Check the size of the allocation is big enough.
445        assert!(
446            size_of_val(self) >= T::BASE_SIZE,
447            "source is too small to be cast to the target type"
448        );
449
450        let t_dst_size = T::dst_len(self.header());
451        // Creates thin or fat pointer, depending on type.
452        let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
453        // SAFETY: `self` is a valid reference and the cast keeps the same
454        // allocation; `T::dst_len` determines the matching tail length. The
455        // assertion above guarantees the retagged extent stays in bounds.
456        let t_ref = unsafe { &*t_ptr };
457
458        assert_eq!(size_of_val(self), size_of_val(t_ref));
459
460        t_ref
461    }
462}
463
464/// Validates a sequence of padded Multiboot2 (header) tags.
465///
466/// Both Multiboot2 information tags and Multiboot2 header tags use an 8-byte
467/// tag header with the reported tag size stored in bytes 4..8. The reported
468/// size excludes alignment padding, but each following tag starts at the next
469/// 8-byte boundary.
470///
471/// Returns `Ok(true)` when a valid end tag is present exactly at the end of the
472/// provided byte range, and `Ok(false)` when the byte range ends without an end
473/// tag.
474pub fn validate_tag_sequence(
475    bytes: &[u8],
476    mut is_end_tag: impl FnMut(&[u8]) -> bool,
477) -> Result<bool, MemoryError> {
478    // Common header property for Multiboot2 and Multiboot2 header tags:
479    // The `size` property is always at offset 4..8 (the second u32).
480    const TAG_HEADER_SIZE: usize = size_of::<u32>() * 2;
481
482    if bytes.as_ptr().align_offset(ALIGNMENT) != 0 {
483        return Err(MemoryError::WrongAlignment);
484    }
485
486    let mut offset = 0;
487    while offset < bytes.len() {
488        let remaining = bytes.len() - offset;
489        if remaining < TAG_HEADER_SIZE {
490            return Err(MemoryError::ShorterThanHeader);
491        }
492
493        let tag = &bytes[offset..];
494        let total_size =
495            u32::from_le_bytes(tag[4..8].try_into().expect("slice has exactly 4 bytes")) as usize;
496
497        if total_size < TAG_HEADER_SIZE {
498            return Err(MemoryError::SizeInsufficient(total_size, TAG_HEADER_SIZE));
499        }
500
501        let padded_size = total_size
502            .checked_add(ALIGNMENT - 1)
503            .map(|size| size & !(ALIGNMENT - 1))
504            .ok_or(MemoryError::InvalidReportedTotalSize(total_size, remaining))?;
505        if padded_size > remaining {
506            return Err(MemoryError::InvalidReportedTotalSize(
507                padded_size,
508                remaining,
509            ));
510        }
511
512        offset += padded_size;
513        if is_end_tag(&tag[..total_size]) {
514            if offset == bytes.len() {
515                return Ok(true);
516            }
517            return Err(MemoryError::InvalidReportedTotalSize(offset, bytes.len()));
518        }
519    }
520
521    Ok(false)
522}
523
524/// Errors that may occur when working with memory.
525#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Error)]
526pub enum MemoryError {
527    /// The memory points to null.
528    #[error("memory points to null")]
529    Null,
530    /// The memory must be at least [`ALIGNMENT`]-aligned.
531    #[error("memory is not properly aligned")]
532    WrongAlignment,
533    /// The memory must cover at least the length of the sized structure header
534    /// type.
535    #[error("memory range is shorter than the size of the header structure")]
536    ShorterThanHeader,
537    /// The size is insufficient to contain at least a valid minimal structure.
538    #[error("memory range is shorter than the size of the header structure")]
539    SizeInsufficient(usize /* actual */, usize /* expected */),
540    /// The buffer misses the terminating padding to the next alignment
541    /// boundary. The padding is relevant to satisfy Rustc/Miri, but also the
542    /// spec mandates that the padding is added.
543    #[error("memory is missing required padding")]
544    MissingPadding,
545    /// The size-property has an illegal value that can't be fulfilled with the
546    /// given bytes.
547    #[error(
548        "header reports an invalid total size of 0x{0:x} while only 0x{1:x} bytes are available"
549    )]
550    InvalidReportedTotalSize(usize /* actual */, usize /* expected */),
551}
552
553/// Increases the given size to the next alignment boundary, if it is not a
554/// multiple of the alignment yet.
555///
556/// This is relevant as in Rust's [type layout], the allocated size of a type is
557/// always a multiple of the alignment, even if the type is smaller.
558///
559/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
560#[must_use]
561pub const fn increase_to_alignment(size: usize) -> usize {
562    let mask = ALIGNMENT - 1;
563    (size + mask) & !mask
564}
565
566#[cfg(test)]
567mod tests {
568    use super::*;
569    use crate::test_utils::{AlignedBytes, DummyTestHeader};
570    use core::borrow::Borrow;
571
572    #[test]
573    fn test_increase_to_alignment() {
574        assert_eq!(increase_to_alignment(0), 0);
575        assert_eq!(increase_to_alignment(1), 8);
576        assert_eq!(increase_to_alignment(7), 8);
577        assert_eq!(increase_to_alignment(8), 8);
578        assert_eq!(increase_to_alignment(9), 16);
579    }
580
581    #[test]
582    fn test_cast_generic_tag_to_sized_tag() {
583        #[repr(C)]
584        struct CustomSizedTag {
585            tag_header: DummyTestHeader,
586            a: u32,
587            b: u32,
588        }
589
590        impl MaybeDynSized for CustomSizedTag {
591            type Header = DummyTestHeader;
592
593            const BASE_SIZE: usize = size_of::<Self>();
594
595            fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
596        }
597
598        let bytes = AlignedBytes([
599            /* id: 0xffff_ffff */
600            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
601            16, 0, 0, 0, /* field a: 0xdead_beef */
602            0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
603            0x37, 0x13, 0x37, 0x13,
604        ]);
605        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
606        let custom_tag = tag.cast::<CustomSizedTag>();
607
608        assert_eq!(size_of_val(custom_tag), 16);
609        assert_eq!(custom_tag.a, 0xdead_beef);
610        assert_eq!(custom_tag.b, 0x1337_1337);
611    }
612
613    #[test]
614    fn test_cast_generic_tag_to_self() {
615        #[rustfmt::skip]
616        let bytes = AlignedBytes::new(
617            [
618                0x37, 0x13, 0, 0,
619                /* Tag size */
620                18, 0, 0, 0,
621                /* Some payload.  */
622                0, 1, 2, 3,
623                4, 5, 6, 7,
624                8, 9,
625                // Padding
626                0, 0, 0, 0, 0, 0
627            ],
628        );
629        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
630
631        // Main objective here is also that this test passes Miri.
632        let tag = tag.cast::<DynSizedStructure<DummyTestHeader>>();
633        assert_eq!(tag.header().typ(), 0x1337);
634        assert_eq!(tag.header().size(), 18);
635    }
636
637    #[test]
638    fn test_ref_from_ptr_rejects_misaligned() {
639        // A misaligned pointer must be reported as an error, not dereferenced
640        // (which would be UB, caught by Miri).
641        let bytes = AlignedBytes([0x37, 0x13, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
642        // Guaranteed misaligned: offset 4 into an 8-byte-aligned buffer.
643        let misaligned = (&raw const bytes.0[4]).cast::<DummyTestHeader>();
644        let ptr = NonNull::new(misaligned.cast_mut()).unwrap();
645        // SAFETY: `ptr` is non-null and the constructor will reject the misalignment..
646        let result = unsafe { DynSizedStructure::<DummyTestHeader>::ref_from_ptr(ptr) };
647        assert_eq!(result, Err(MemoryError::WrongAlignment));
648    }
649
650    #[test]
651    #[should_panic(expected = "source is too small to be cast to the target type")]
652    fn test_cast_rejects_too_small_source() {
653        // A sized target larger than the (validly terminated but truncated)
654        // source must be rejected before the reference is created, rather
655        // than retagging out of bounds (which would be UB under Miri).
656        #[repr(C, align(8))]
657        struct CustomSizedTag {
658            tag_header: DummyTestHeader,
659            a: u32,
660            b: u32,
661        }
662
663        impl MaybeDynSized for CustomSizedTag {
664            type Header = DummyTestHeader;
665
666            const BASE_SIZE: usize = size_of::<Self>();
667
668            fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
669        }
670
671        // Reports a total size of only 8 bytes, i.e., just the header.
672        let bytes = AlignedBytes([0x37, 0x13, 0, 0, 8, 0, 0, 0]);
673        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
674        // `CustomSizedTag` needs 16 bytes; casting must panic, not read OOB.
675        let _ = tag.cast::<CustomSizedTag>();
676    }
677
678    #[test]
679    fn test_ref_from_slice_rejects_oversized_header() {
680        #[rustfmt::skip]
681        let bytes = AlignedBytes::new(
682            [
683                0x37, 0x13, 0, 0,
684                /* Tag size */
685                24, 0, 0, 0,
686                /* Only 8 bytes payload plus padding are available. */
687                0, 1, 2, 3,
688                4, 5, 6, 7,
689            ],
690        );
691
692        assert_eq!(
693            DynSizedStructure::<DummyTestHeader>::ref_from_slice(bytes.borrow()),
694            Err(MemoryError::InvalidReportedTotalSize(24, 16))
695        );
696    }
697
698    #[test]
699    fn test_ref_from_slice_rejects_too_small_reported_size() {
700        #[rustfmt::skip]
701        let bytes = AlignedBytes::new(
702            [
703                0x37, 0x13, 0, 0,
704                /* Tag size */
705                4, 0, 0, 0,
706                /* Remaining bytes are irrelevant. */
707                0, 1, 2, 3,
708                0, 0, 0, 0,
709            ],
710        );
711
712        assert_eq!(
713            DynSizedStructure::<DummyTestHeader>::ref_from_slice(bytes.borrow()),
714            Err(MemoryError::SizeInsufficient(4, 8))
715        );
716    }
717}