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 the function operates on valid memory.
385    pub unsafe fn ref_from_ptr<'a>(ptr: NonNull<H>) -> Result<&'a Self, MemoryError> {
386        let ptr = ptr.as_ptr().cast_const();
387        // SAFETY: `ptr` came from a valid pointer to the header; we only read
388        // the reported total size and immediately re-slice that range.
389        let hdr = unsafe { &*ptr };
390        let total_size = hdr.total_size();
391        let header_size = size_of::<H>();
392        if total_size < header_size {
393            return Err(MemoryError::SizeInsufficient(total_size, header_size));
394        }
395
396        // SAFETY: `total_size` came from the validated header and matches the
397        // readable byte range for the structure.
398        let slice = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), total_size) };
399        Self::ref_from_slice(slice)
400    }
401
402    /// Returns the underlying [`Header`].
403    pub const fn header(&self) -> &H {
404        &self.header
405    }
406
407    /// Returns the underlying payload.
408    pub const fn payload(&self) -> &[u8] {
409        &self.payload
410    }
411
412    /// Performs a memory-safe same-size cast from the base-structure to a
413    /// specific [`MaybeDynSized`]. The idea here is to cast the generic
414    /// mostly semantic-free version to a specific type with fields that have
415    /// a clear semantic.
416    ///
417    /// The provided `T` may be sized or dynamically sized. The source and
418    /// target have the same actual payload size and [`size_of_val`].
419    ///
420    /// # Panics
421    /// Panics if `T` cannot represent the same allocation size. This should not
422    /// happen when all types follow their documented requirements.
423    pub fn cast<T: MaybeDynSized<Header = H> + ?Sized>(&self) -> &T
424    where
425        T::Metadata: Default,
426    {
427        // Thin or fat pointer, depending on type.
428        // However, only thin ptr is needed.
429        let base_ptr = &raw const *self;
430
431        // This should be a compile-time assertion. However, this is the best
432        // location to place it for now.
433        assert!(T::BASE_SIZE >= size_of::<H>());
434
435        let t_dst_size = T::dst_len(self.header());
436        // Creates thin or fat pointer, depending on type.
437        let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
438        // SAFETY: `self` is a valid reference and the cast keeps the same
439        // allocation; `T::dst_len` determines the matching tail length.
440        let t_ref = unsafe { &*t_ptr };
441
442        assert_eq!(size_of_val(self), size_of_val(t_ref));
443
444        t_ref
445    }
446}
447
448/// Validates a sequence of padded Multiboot2 (header) tags.
449///
450/// Both Multiboot2 information tags and Multiboot2 header tags use an 8-byte
451/// tag header with the reported tag size stored in bytes 4..8. The reported
452/// size excludes alignment padding, but each following tag starts at the next
453/// 8-byte boundary.
454///
455/// Returns `Ok(true)` when a valid end tag is present exactly at the end of the
456/// provided byte range, and `Ok(false)` when the byte range ends without an end
457/// tag.
458pub fn validate_tag_sequence(
459    bytes: &[u8],
460    mut is_end_tag: impl FnMut(&[u8]) -> bool,
461) -> Result<bool, MemoryError> {
462    // Common header property for Multiboot2 and Multiboot2 header tags:
463    // The `size` property is always at offset 4..8 (the second u32).
464    const TAG_HEADER_SIZE: usize = size_of::<u32>() * 2;
465
466    if bytes.as_ptr().align_offset(ALIGNMENT) != 0 {
467        return Err(MemoryError::WrongAlignment);
468    }
469
470    let mut offset = 0;
471    while offset < bytes.len() {
472        let remaining = bytes.len() - offset;
473        if remaining < TAG_HEADER_SIZE {
474            return Err(MemoryError::ShorterThanHeader);
475        }
476
477        let tag = &bytes[offset..];
478        let total_size =
479            u32::from_le_bytes(tag[4..8].try_into().expect("slice has exactly 4 bytes")) as usize;
480
481        if total_size < TAG_HEADER_SIZE {
482            return Err(MemoryError::SizeInsufficient(total_size, TAG_HEADER_SIZE));
483        }
484
485        let padded_size = total_size
486            .checked_add(ALIGNMENT - 1)
487            .map(|size| size & !(ALIGNMENT - 1))
488            .ok_or(MemoryError::InvalidReportedTotalSize(total_size, remaining))?;
489        if padded_size > remaining {
490            return Err(MemoryError::InvalidReportedTotalSize(
491                padded_size,
492                remaining,
493            ));
494        }
495
496        offset += padded_size;
497        if is_end_tag(&tag[..total_size]) {
498            if offset == bytes.len() {
499                return Ok(true);
500            }
501            return Err(MemoryError::InvalidReportedTotalSize(offset, bytes.len()));
502        }
503    }
504
505    Ok(false)
506}
507
508/// Errors that may occur when working with memory.
509#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Error)]
510pub enum MemoryError {
511    /// The memory points to null.
512    #[error("memory points to null")]
513    Null,
514    /// The memory must be at least [`ALIGNMENT`]-aligned.
515    #[error("memory is not properly aligned")]
516    WrongAlignment,
517    /// The memory must cover at least the length of the sized structure header
518    /// type.
519    #[error("memory range is shorter than the size of the header structure")]
520    ShorterThanHeader,
521    /// The size is insufficient to contain at least a valid minimal structure.
522    #[error("memory range is shorter than the size of the header structure")]
523    SizeInsufficient(usize /* actual */, usize /* expected */),
524    /// The buffer misses the terminating padding to the next alignment
525    /// boundary. The padding is relevant to satisfy Rustc/Miri, but also the
526    /// spec mandates that the padding is added.
527    #[error("memory is missing required padding")]
528    MissingPadding,
529    /// The size-property has an illegal value that can't be fulfilled with the
530    /// given bytes.
531    #[error(
532        "header reports an invalid total size of 0x{0:x} while only 0x{1:x} bytes are available"
533    )]
534    InvalidReportedTotalSize(usize /* actual */, usize /* expected */),
535}
536
537/// Increases the given size to the next alignment boundary, if it is not a
538/// multiple of the alignment yet.
539///
540/// This is relevant as in Rust's [type layout], the allocated size of a type is
541/// always a multiple of the alignment, even if the type is smaller.
542///
543/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
544#[must_use]
545pub const fn increase_to_alignment(size: usize) -> usize {
546    let mask = ALIGNMENT - 1;
547    (size + mask) & !mask
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553    use crate::test_utils::{AlignedBytes, DummyTestHeader};
554    use core::borrow::Borrow;
555
556    #[test]
557    fn test_increase_to_alignment() {
558        assert_eq!(increase_to_alignment(0), 0);
559        assert_eq!(increase_to_alignment(1), 8);
560        assert_eq!(increase_to_alignment(7), 8);
561        assert_eq!(increase_to_alignment(8), 8);
562        assert_eq!(increase_to_alignment(9), 16);
563    }
564
565    #[test]
566    fn test_cast_generic_tag_to_sized_tag() {
567        #[repr(C)]
568        struct CustomSizedTag {
569            tag_header: DummyTestHeader,
570            a: u32,
571            b: u32,
572        }
573
574        impl MaybeDynSized for CustomSizedTag {
575            type Header = DummyTestHeader;
576
577            const BASE_SIZE: usize = size_of::<Self>();
578
579            fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
580        }
581
582        let bytes = AlignedBytes([
583            /* id: 0xffff_ffff */
584            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
585            16, 0, 0, 0, /* field a: 0xdead_beef */
586            0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
587            0x37, 0x13, 0x37, 0x13,
588        ]);
589        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
590        let custom_tag = tag.cast::<CustomSizedTag>();
591
592        assert_eq!(size_of_val(custom_tag), 16);
593        assert_eq!(custom_tag.a, 0xdead_beef);
594        assert_eq!(custom_tag.b, 0x1337_1337);
595    }
596
597    #[test]
598    fn test_cast_generic_tag_to_self() {
599        #[rustfmt::skip]
600        let bytes = AlignedBytes::new(
601            [
602                0x37, 0x13, 0, 0,
603                /* Tag size */
604                18, 0, 0, 0,
605                /* Some payload.  */
606                0, 1, 2, 3,
607                4, 5, 6, 7,
608                8, 9,
609                // Padding
610                0, 0, 0, 0, 0, 0
611            ],
612        );
613        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
614
615        // Main objective here is also that this test passes Miri.
616        let tag = tag.cast::<DynSizedStructure<DummyTestHeader>>();
617        assert_eq!(tag.header().typ(), 0x1337);
618        assert_eq!(tag.header().size(), 18);
619    }
620
621    #[test]
622    fn test_ref_from_slice_rejects_oversized_header() {
623        #[rustfmt::skip]
624        let bytes = AlignedBytes::new(
625            [
626                0x37, 0x13, 0, 0,
627                /* Tag size */
628                24, 0, 0, 0,
629                /* Only 8 bytes payload plus padding are available. */
630                0, 1, 2, 3,
631                4, 5, 6, 7,
632            ],
633        );
634
635        assert_eq!(
636            DynSizedStructure::<DummyTestHeader>::ref_from_slice(bytes.borrow()),
637            Err(MemoryError::InvalidReportedTotalSize(24, 16))
638        );
639    }
640
641    #[test]
642    fn test_ref_from_slice_rejects_too_small_reported_size() {
643        #[rustfmt::skip]
644        let bytes = AlignedBytes::new(
645            [
646                0x37, 0x13, 0, 0,
647                /* Tag size */
648                4, 0, 0, 0,
649                /* Remaining bytes are irrelevant. */
650                0, 1, 2, 3,
651                0, 0, 0, 0,
652            ],
653        );
654
655        assert_eq!(
656            DynSizedStructure::<DummyTestHeader>::ref_from_slice(bytes.borrow()),
657            Err(MemoryError::SizeInsufficient(4, 8))
658        );
659    }
660}