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}