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//! ```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//! ```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//! ```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::must_use_candidate,
228    clippy::nursery,
229    missing_debug_implementations,
230    missing_docs,
231    rustdoc::all
232)]
233#![allow(clippy::multiple_crate_versions)]
234// --- END STYLE CHECKS ---
235
236#[cfg_attr(test, macro_use)]
237#[cfg(test)]
238extern crate std;
239
240#[cfg(feature = "alloc")]
241extern crate alloc;
242
243#[allow(unused)]
244pub mod test_utils;
245
246#[cfg(feature = "alloc")]
247mod boxed;
248mod bytes_ref;
249mod iter;
250mod tag;
251
252#[cfg(feature = "alloc")]
253pub use boxed::{clone_dyn, new_boxed};
254pub use bytes_ref::BytesRef;
255pub use iter::TagIter;
256pub use tag::{MaybeDynSized, Tag};
257
258use core::fmt::Debug;
259use core::mem;
260use core::ptr;
261use core::ptr::NonNull;
262use core::slice;
263use thiserror::Error;
264
265/// The alignment of all Multiboot2 data structures.
266pub const ALIGNMENT: usize = 8;
267
268/// A sized header type for [`DynSizedStructure`].
269///
270/// Note that `header` refers to the header pattern. Thus, depending on the use
271/// case, this is not just a tag header. Instead, it refers to all bytes that
272/// are fixed and not part of any optional terminating dynamic `[u8]` slice in a
273/// [`DynSizedStructure`].
274///
275/// The alignment of implementors **must** be the compatible with the demands
276/// for the corresponding structure, which typically is [`ALIGNMENT`].
277pub trait Header: Clone + Sized + PartialEq + Eq + Debug {
278    /// Returns the length of the payload, i.e., the bytes that are additional
279    /// to the header. The value is measured in bytes.
280    #[must_use]
281    fn payload_len(&self) -> usize;
282
283    /// Returns the total size of the struct, thus the size of the header itself
284    /// plus [`Header::payload_len`].
285    #[must_use]
286    fn total_size(&self) -> usize {
287        mem::size_of::<Self>() + self.payload_len()
288    }
289
290    /// Updates the header with the given `total_size`.
291    fn set_size(&mut self, total_size: usize);
292}
293
294/// An C ABI-compatible dynamically sized type with a common sized [`Header`]
295/// and a dynamic amount of bytes without hidden implicit padding.
296///
297/// This structures combines a [`Header`] with the logically owned data by
298/// that header according to the reported [`Header::payload_len`]. Instances
299/// guarantees that the memory requirements promised in the crates description
300/// are respected.
301///
302/// This can be a Multiboot2 header tag, information tag, boot information, or
303/// a Multiboot2 header. It is the base for **same-size casts** to these
304/// corresponding structures using [`DynSizedStructure::cast`]. Depending on the
305/// context, the [`Header`] is different (header header, boot information
306/// header, header tag header, or boot information tag header).
307///
308/// # ABI
309/// This type uses the C ABI. The fixed [`Header`] portion is always there.
310/// Further, there is a variable amount of payload bytes. Thus, this type can
311/// only exist on the heap or references to it can be made by cast via fat
312/// pointers. The main constructor is [`DynSizedStructure::ref_from_bytes`].
313///
314/// As terminating padding might be necessary for the proper Rust type layout,
315/// `size_of_val(&self)` might report additional padding bytes that are not
316/// reflected by the actual payload. These additional padding bytes however
317/// will be reflected in corresponding [`BytesRef`] instances from that this
318/// structure was created.
319#[derive(Debug, PartialEq, Eq, ptr_meta::Pointee)]
320#[repr(C, align(8))]
321pub struct DynSizedStructure<H: Header> {
322    header: H,
323    payload: [u8],
324    // Plus optional padding bytes to next alignment boundary, which are not
325    // reflected here. However, Rustc allocates them anyway and expects them
326    // to be there.
327    // See <https://doc.rust-lang.org/reference/type-layout.html>.
328}
329
330impl<H: Header> DynSizedStructure<H> {
331    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
332    /// from the given [`BytesRef`].
333    pub fn ref_from_bytes(bytes: BytesRef<H>) -> Result<&Self, MemoryError> {
334        let ptr = bytes.as_ptr().cast::<H>();
335        let hdr = unsafe { &*ptr };
336
337        if hdr.payload_len() > bytes.len() {
338            return Err(MemoryError::InvalidReportedTotalSize);
339        }
340
341        // At this point we know that the memory slice fulfills the base
342        // assumptions and requirements. Now, we safety can create the fat
343        // pointer.
344
345        let dst_size = hdr.payload_len();
346        // Create fat pointer for the DST.
347        let ptr = ptr_meta::from_raw_parts(ptr.cast(), dst_size);
348        let reference = unsafe { &*ptr };
349        Ok(reference)
350    }
351
352    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
353    /// from the given `&[u8]`.
354    pub fn ref_from_slice(bytes: &[u8]) -> Result<&Self, MemoryError> {
355        let bytes = BytesRef::<H>::try_from(bytes)?;
356        Self::ref_from_bytes(bytes)
357    }
358
359    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
360    /// from the given thin pointer to the [`Header`]. It reads the total size
361    /// from the header.
362    ///
363    /// # Safety
364    /// The caller must ensure that the function operates on valid memory.
365    pub unsafe fn ref_from_ptr<'a>(ptr: NonNull<H>) -> Result<&'a Self, MemoryError> {
366        let ptr = ptr.as_ptr().cast_const();
367        let hdr = unsafe { &*ptr };
368
369        let slice = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), hdr.total_size()) };
370        Self::ref_from_slice(slice)
371    }
372
373    /// Returns the underlying [`Header`].
374    pub const fn header(&self) -> &H {
375        &self.header
376    }
377
378    /// Returns the underlying payload.
379    pub const fn payload(&self) -> &[u8] {
380        &self.payload
381    }
382
383    /// Performs a memory-safe same-size cast from the base-structure to a
384    /// specific [`MaybeDynSized`]. The idea here is to cast the generic
385    /// mostly semantic-free version to a specific type with fields that have
386    /// a clear semantic.
387    ///
388    /// The provided `T` of type [`MaybeDynSized`] might be may be sized type
389    /// or DST. This depends on the type. However, the source and the target
390    /// both will have the same actual payload size and the same
391    /// [`size_of_val`].
392    ///
393    /// # Panic
394    /// Panics if base assumptions are violated. For example, the
395    /// `T` of type [`MaybeDynSized`] must allow proper same-size casting to it.
396    ///
397    /// # Safety
398    /// This function is safe due to various sanity checks and the overall
399    /// memory assertions done while constructing this type.
400    ///
401    /// # Panics
402    /// This panics if there is a size mismatch. However, this should never be
403    /// the case if all types follow their documented requirements.
404    ///
405    /// [`size_of_val`]: mem::size_of_val
406    pub fn cast<T: MaybeDynSized<Header = H> + ?Sized>(&self) -> &T {
407        // Thin or fat pointer, depending on type.
408        // However, only thin ptr is needed.
409        let base_ptr = ptr::addr_of!(*self);
410
411        // This should be a compile-time assertion. However, this is the best
412        // location to place it for now.
413        assert!(T::BASE_SIZE >= mem::size_of::<H>());
414
415        let t_dst_size = T::dst_len(self.header());
416        // Creates thin or fat pointer, depending on type.
417        let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
418        let t_ref = unsafe { &*t_ptr };
419
420        assert_eq!(mem::size_of_val(self), mem::size_of_val(t_ref));
421
422        t_ref
423    }
424}
425
426/// Errors that may occur when working with memory.
427#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Error)]
428pub enum MemoryError {
429    /// The memory points to null.
430    #[error("memory points to null")]
431    Null,
432    /// The memory must be at least [`ALIGNMENT`]-aligned.
433    #[error("memory is not properly aligned")]
434    WrongAlignment,
435    /// The memory must cover at least the length of the sized structure header
436    /// type.
437    #[error("memory range is shorter than the size of the header structure")]
438    ShorterThanHeader,
439    /// The buffer misses the terminating padding to the next alignment
440    /// boundary. The padding is relevant to satisfy Rustc/Miri, but also the
441    /// spec mandates that the padding is added.
442    #[error("memory is missing required padding")]
443    MissingPadding,
444    /// The size-property has an illegal value that can't be fulfilled with the
445    /// given bytes.
446    #[error("the header reports an invalid total size")]
447    InvalidReportedTotalSize,
448}
449
450/// Increases the given size to the next alignment boundary, if it is not a
451/// multiple of the alignment yet.
452///
453/// This is relevant as in Rust's [type layout], the allocated size of a type is
454/// always a multiple of the alignment, even if the type is smaller.
455///
456/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
457#[must_use]
458pub const fn increase_to_alignment(size: usize) -> usize {
459    let mask = ALIGNMENT - 1;
460    (size + mask) & !mask
461}
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466    use crate::test_utils::{AlignedBytes, DummyTestHeader};
467    use core::borrow::Borrow;
468
469    #[test]
470    fn test_increase_to_alignment() {
471        assert_eq!(increase_to_alignment(0), 0);
472        assert_eq!(increase_to_alignment(1), 8);
473        assert_eq!(increase_to_alignment(7), 8);
474        assert_eq!(increase_to_alignment(8), 8);
475        assert_eq!(increase_to_alignment(9), 16);
476    }
477
478    #[test]
479    fn test_cast_generic_tag_to_sized_tag() {
480        #[repr(C)]
481        struct CustomSizedTag {
482            tag_header: DummyTestHeader,
483            a: u32,
484            b: u32,
485        }
486
487        impl MaybeDynSized for CustomSizedTag {
488            type Header = DummyTestHeader;
489
490            const BASE_SIZE: usize = mem::size_of::<Self>();
491
492            fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
493        }
494
495        let bytes = AlignedBytes([
496            /* id: 0xffff_ffff */
497            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
498            16, 0, 0, 0, /* field a: 0xdead_beef */
499            0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
500            0x37, 0x13, 0x37, 0x13,
501        ]);
502        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
503        let custom_tag = tag.cast::<CustomSizedTag>();
504
505        assert_eq!(mem::size_of_val(custom_tag), 16);
506        assert_eq!(custom_tag.a, 0xdead_beef);
507        assert_eq!(custom_tag.b, 0x1337_1337);
508    }
509
510    #[test]
511    fn test_cast_generic_tag_to_self() {
512        #[rustfmt::skip]
513        let bytes = AlignedBytes::new(
514            [
515                0x37, 0x13, 0, 0,
516                /* Tag size */
517                18, 0, 0, 0,
518                /* Some payload.  */
519                0, 1, 2, 3,
520                4, 5, 6, 7,
521                8, 9,
522                // Padding
523                0, 0, 0, 0, 0, 0
524            ],
525        );
526        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
527
528        // Main objective here is also that this test passes Miri.
529        let tag = tag.cast::<DynSizedStructure<DummyTestHeader>>();
530        assert_eq!(tag.header().typ(), 0x1337);
531        assert_eq!(tag.header().size(), 18);
532    }
533}