Skip to main content

multiboot2_common/
lib.rs

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