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