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}