Browse Source

Merge pull request #227 from rust-osdev/mb-hdr

init multiboot2-common and use same safe memory abstractions also in multiboot2-header crate
Philipp Schuster 7 months ago
parent
commit
a08365efb4
59 changed files with 2753 additions and 2466 deletions
  1. 14 2
      Cargo.lock
  2. 6 3
      Cargo.toml
  3. 14 2
      integration-test/bins/Cargo.lock
  4. 2 0
      integration-test/bins/Cargo.toml
  5. 13 13
      integration-test/bins/multiboot2_chainloader/src/loader.rs
  6. 1 1
      integration-test/bins/multiboot2_payload/src/verify/mod.rs
  7. 37 0
      multiboot2-common/Cargo.toml
  8. 5 0
      multiboot2-common/Changelog.md
  9. 1 0
      multiboot2-common/README.md
  10. 116 0
      multiboot2-common/src/boxed.rs
  11. 87 0
      multiboot2-common/src/bytes_ref.rs
  12. 125 0
      multiboot2-common/src/iter.rs
  13. 406 0
      multiboot2-common/src/lib.rs
  14. 94 0
      multiboot2-common/src/tag.rs
  15. 84 27
      multiboot2-common/src/test_utils.rs
  16. 6 2
      multiboot2-header/Cargo.toml
  17. 13 1
      multiboot2-header/Changelog.md
  18. 18 11
      multiboot2-header/examples/minimal.rs
  19. 15 1
      multiboot2-header/src/address.rs
  20. 238 0
      multiboot2-header/src/builder.rs
  21. 0 425
      multiboot2-header/src/builder/header.rs
  22. 0 138
      multiboot2-header/src/builder/information_request.rs
  23. 0 8
      multiboot2-header/src/builder/mod.rs
  24. 0 75
      multiboot2-header/src/builder/traits.rs
  25. 14 10
      multiboot2-header/src/console.rs
  26. 16 2
      multiboot2-header/src/end.rs
  27. 14 10
      multiboot2-header/src/entry_address.rs
  28. 14 10
      multiboot2-header/src/entry_efi_32.rs
  29. 14 10
      multiboot2-header/src/entry_efi_64.rs
  30. 14 16
      multiboot2-header/src/framebuffer.rs
  31. 101 234
      multiboot2-header/src/header.rs
  32. 70 110
      multiboot2-header/src/information_request.rs
  33. 20 27
      multiboot2-header/src/lib.rs
  34. 21 4
      multiboot2-header/src/module_align.rs
  35. 21 4
      multiboot2-header/src/relocatable.rs
  36. 13 0
      multiboot2-header/src/tags.rs
  37. 18 3
      multiboot2-header/src/uefi_bs.rs
  38. 7 7
      multiboot2/Cargo.toml
  39. 38 5
      multiboot2/Changelog.md
  40. 91 95
      multiboot2/src/boot_information.rs
  41. 27 18
      multiboot2/src/boot_loader_name.rs
  42. 346 0
      multiboot2/src/builder.rs
  43. 0 391
      multiboot2/src/builder/information.rs
  44. 0 18
      multiboot2/src/builder/mod.rs
  45. 27 18
      multiboot2/src/command_line.rs
  46. 58 17
      multiboot2/src/efi.rs
  47. 20 9
      multiboot2/src/elf_sections.rs
  48. 13 3
      multiboot2/src/end.rs
  49. 191 77
      multiboot2/src/framebuffer.rs
  50. 14 5
      multiboot2/src/image_load_addr.rs
  51. 54 27
      multiboot2/src/lib.rs
  52. 53 20
      multiboot2/src/memory_map.rs
  53. 28 19
      multiboot2/src/module.rs
  54. 38 19
      multiboot2/src/rsdp.rs
  55. 23 15
      multiboot2/src/smbios.rs
  56. 12 409
      multiboot2/src/tag.rs
  57. 0 47
      multiboot2/src/tag_trait.rs
  58. 0 92
      multiboot2/src/util.rs
  59. 68 6
      multiboot2/src/vbe_info.rs

+ 14 - 2
Cargo.lock

@@ -27,21 +27,33 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
 
 [[package]]
 name = "multiboot2"
-version = "0.21.0"
+version = "0.22.0"
 dependencies = [
  "bitflags",
  "derive_more",
  "log",
+ "multiboot2-common",
  "ptr_meta",
  "uefi-raw",
 ]
 
+[[package]]
+name = "multiboot2-common"
+version = "0.1.0"
+dependencies = [
+ "derive_more",
+ "ptr_meta",
+]
+
 [[package]]
 name = "multiboot2-header"
-version = "0.4.0"
+version = "0.5.0"
 dependencies = [
  "derive_more",
+ "log",
  "multiboot2",
+ "multiboot2-common",
+ "ptr_meta",
 ]
 
 [[package]]

+ 6 - 3
Cargo.toml

@@ -2,6 +2,7 @@
 resolver = "2"
 members = [
     "multiboot2",
+    "multiboot2-common",
     "multiboot2-header",
 ]
 exclude = [
@@ -12,9 +13,11 @@ exclude = [
 bitflags = "2.6.0"
 derive_more = { version = "~0.99.18", default-features = false, features = ["display"] }
 log = { version = "~0.4", default-features = false }
+ptr_meta = { version = "~0.2", default-features = false }
 
-# This way, the "multiboot2" dependency in the multiboot2-header crate can be
-# referenced by version, while still the repository version is used
-# transparently during local development.
+# This way, the corresponding crate dependency can be normalley referenced by
+# version, while still the repository version is used transparently during local
+# development.
 [patch.crates-io]
 multiboot2 = { path = "multiboot2" }
+multiboot2-common = { path = "multiboot2-common" }

+ 14 - 2
integration-test/bins/Cargo.lock

@@ -96,21 +96,33 @@ dependencies = [
 
 [[package]]
 name = "multiboot2"
-version = "0.21.0"
+version = "0.22.0"
 dependencies = [
  "bitflags 2.6.0",
  "derive_more",
  "log",
+ "multiboot2-common",
  "ptr_meta",
  "uefi-raw",
 ]
 
+[[package]]
+name = "multiboot2-common"
+version = "0.1.0"
+dependencies = [
+ "derive_more",
+ "ptr_meta",
+]
+
 [[package]]
 name = "multiboot2-header"
-version = "0.4.0"
+version = "0.5.0"
 dependencies = [
  "derive_more",
+ "log",
  "multiboot2",
+ "multiboot2-common",
+ "ptr_meta",
 ]
 
 [[package]]

+ 2 - 0
integration-test/bins/Cargo.toml

@@ -24,3 +24,5 @@ util = { path = "./util" }
 # transparently during local development.
 [patch.crates-io]
 multiboot2 = { path = "../../multiboot2" }
+multiboot2-common = { path = "../../multiboot2-common" }
+multiboot2-header = { path = "../../multiboot2-header" }

+ 13 - 13
integration-test/bins/multiboot2_chainloader/src/loader.rs

@@ -1,8 +1,8 @@
-use core::ops::Deref;
+use alloc::boxed::Box;
 use elf_rs::{ElfFile, ProgramHeaderEntry, ProgramType};
 use multiboot2::{
-    BootLoaderNameTag, CommandLineTag, MemoryArea, MemoryAreaType, MemoryMapTag, ModuleTag,
-    SmbiosTag,
+    BootLoaderNameTag, CommandLineTag, MaybeDynSized, MemoryArea, MemoryAreaType, MemoryMapTag,
+    ModuleTag, SmbiosTag,
 };
 
 /// Loads the first module into memory. Assumes that the module is a ELF file.
@@ -43,27 +43,27 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
     // that the basic data structures are usable.
 
     // build MBI
-    let mbi = multiboot2::builder::InformationBuilder::new()
-        .bootloader_name_tag(&BootLoaderNameTag::new("mb2_integrationtest_chainloader"))
-        .command_line_tag(&CommandLineTag::new("chainloaded YEAH"))
+    let mbi = multiboot2::Builder::new()
+        .bootloader(BootLoaderNameTag::new("mb2_integrationtest_chainloader"))
+        .cmdline(CommandLineTag::new("chainloaded YEAH"))
         // random non-sense memory map
-        .memory_map_tag(&MemoryMapTag::new(&[MemoryArea::new(
+        .mmap(MemoryMapTag::new(&[MemoryArea::new(
             0,
             0xffffffff,
             MemoryAreaType::Reserved,
         )]))
-        .add_module_tag(&ModuleTag::new(
+        .add_module(ModuleTag::new(
             elf_mod.start as u32,
             elf_mod.end as u32,
             elf_mod.string.unwrap(),
         ))
         // Test that we can add SmbiosTag multiple times.
-        .add_tag(SmbiosTag::new(1, 1, &[1, 2, 3]).deref())
-        .unwrap()
-        .add_tag(SmbiosTag::new(1, 2, &[1, 2, 3]).deref())
-        .expect("should allow tag multiple times")
+        .add_smbios(SmbiosTag::new(1, 1, &[1, 2, 3]))
+        .add_smbios(SmbiosTag::new(2, 3, &[4, 5, 6]))
         .build();
 
+    let mbi = Box::leak(mbi);
+
     log::info!(
         "Handing over to ELF: {}",
         elf_mod.string.unwrap_or("<unknown>")
@@ -74,7 +74,7 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
         core::arch::asm!(
         "jmp *%ecx",
         in("eax") multiboot2::MAGIC,
-        in("ebx") mbi.as_ptr() as u32,
+        in("ebx") mbi.as_ptr(),
         in("ecx") elf.entry_point() as u32,
         options(noreturn, att_syntax));
     }

+ 1 - 1
integration-test/bins/multiboot2_payload/src/verify/mod.rs

@@ -6,7 +6,7 @@ use alloc::vec::Vec;
 use multiboot2::BootInformation;
 
 pub fn run(mbi: &BootInformation) -> anyhow::Result<()> {
-    println!("{mbi:#x?}");
+    println!("MBI: {mbi:#x?}");
     println!();
 
     let bootloader = mbi

+ 37 - 0
multiboot2-common/Cargo.toml

@@ -0,0 +1,37 @@
+[package]
+name = "multiboot2-common"
+description = """
+Common helpers for the `multiboot2` and `multiboot2-header` crates.
+"""
+version = "0.1.0"
+authors = [
+    "Philipp Schuster <phip1611@gmail.com>"
+]
+license = "MIT/Apache-2.0"
+edition = "2021"
+categories = [
+    "no-std",
+    "no-std::no-alloc",
+]
+keywords = [
+    "Multiboot2"
+]
+readme = "README.md"
+homepage = "https://github.com/rust-osdev/multiboot2"
+repository = "https://github.com/rust-osdev/multiboot2"
+documentation = "https://docs.rs/multiboot2-common"
+rust-version = "1.70"
+
+[features]
+default = ["builder"]
+alloc = []
+builder = ["alloc"]
+unstable = []
+
+
+[dependencies]
+derive_more.workspace = true
+ptr_meta.workspace = true
+
+[package.metadata.docs.rs]
+all-features = true

+ 5 - 0
multiboot2-common/Changelog.md

@@ -0,0 +1,5 @@
+# CHANGELOG for crate `multiboot2`
+
+## 0.1.0 (2024-08-20)
+
+Initial release.

+ 1 - 0
multiboot2-common/README.md

@@ -0,0 +1 @@
+# multiboot2-common

+ 116 - 0
multiboot2-common/src/boxed.rs

@@ -0,0 +1,116 @@
+//! Module for [`new_boxed`].
+
+use crate::{increase_to_alignment, Header, MaybeDynSized, ALIGNMENT};
+use alloc::boxed::Box;
+use core::alloc::Layout;
+use core::mem;
+use core::ops::Deref;
+use core::ptr;
+
+/// Creates a new tag implementing [`MaybeDynSized`] on the heap. This works for
+/// sized and unsized tags. However, it only makes sense to use this for tags
+/// that are DSTs (unsized). For regular sized structs, you can just create a
+/// typical constructor and box the result.
+///
+/// The provided `header`' total size (see [`Header`]) will be set dynamically
+/// by this function using [`Header::set_size`]. However, it must contain all
+/// other relevant metadata or update it in the `set_size` callback.
+///
+/// # Parameters
+/// - `additional_bytes_slices`: Array of byte slices that should be included
+///   without additional padding in-between. You don't need to add the bytes
+///   for [`Header`], but only additional payload.
+#[must_use]
+pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(
+    mut header: T::Header,
+    additional_bytes_slices: &[&[u8]],
+) -> Box<T> {
+    let additional_size = additional_bytes_slices
+        .iter()
+        .map(|b| b.len())
+        .sum::<usize>();
+
+    let tag_size = mem::size_of::<T::Header>() + additional_size;
+    header.set_size(tag_size);
+
+    // Allocation size is multiple of alignment.
+    // See <https://doc.rust-lang.org/reference/type-layout.html>
+    let alloc_size = increase_to_alignment(tag_size);
+    let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap();
+    let heap_ptr = unsafe { alloc::alloc::alloc(layout) };
+    assert!(!heap_ptr.is_null());
+
+    // write header
+    {
+        let len = mem::size_of::<T::Header>();
+        let ptr = core::ptr::addr_of!(header);
+        unsafe {
+            ptr::copy_nonoverlapping(ptr.cast::<u8>(), heap_ptr, len);
+        }
+    }
+
+    // write body
+    {
+        let mut write_offset = mem::size_of::<T::Header>();
+        for &bytes in additional_bytes_slices {
+            let len = bytes.len();
+            let src = bytes.as_ptr();
+            unsafe {
+                let dst = heap_ptr.add(write_offset);
+                ptr::copy_nonoverlapping(src, dst, len);
+                write_offset += len;
+            }
+        }
+    }
+
+    // This is a fat pointer for DSTs and a thin pointer for sized `T`s.
+    let ptr: *mut T = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(&header));
+    let reference = unsafe { Box::from_raw(ptr) };
+
+    // If this panic triggers, there is a fundamental flaw in my logic. This is
+    // not the fault of an API user.
+    assert_eq!(
+        mem::size_of_val(reference.deref()),
+        alloc_size,
+        "Allocation should match Rusts expectation"
+    );
+
+    reference
+}
+
+/// Clones a [`MaybeDynSized`] by calling [`new_boxed`].
+#[must_use]
+pub fn clone_dyn<T: MaybeDynSized<Metadata = usize> + ?Sized>(tag: &T) -> Box<T> {
+    new_boxed(tag.header().clone(), &[tag.payload()])
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test_utils::{DummyDstTag, DummyTestHeader};
+    use crate::Tag;
+
+    #[test]
+    fn test_new_boxed() {
+        let header = DummyTestHeader::new(DummyDstTag::ID, 0);
+        let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
+        assert_eq!(tag.header().typ(), 42);
+        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
+
+        // Test that bytes are added consecutively without gaps.
+        let header = DummyTestHeader::new(0xdead_beef, 0);
+        let tag = new_boxed::<DummyDstTag>(header, &[&[0], &[1], &[2, 3]]);
+        assert_eq!(tag.header().typ(), 0xdead_beef);
+        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
+    }
+
+    #[test]
+    fn test_clone_tag() {
+        let header = DummyTestHeader::new(DummyDstTag::ID, 0);
+        let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
+        assert_eq!(tag.header().typ(), 42);
+        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
+
+        let _cloned = clone_dyn(tag.as_ref());
+    }
+}

+ 87 - 0
multiboot2-common/src/bytes_ref.rs

@@ -0,0 +1,87 @@
+//! Module for [`BytesRef`].
+
+use crate::{Header, MemoryError, ALIGNMENT};
+use core::marker::PhantomData;
+use core::mem;
+use core::ops::Deref;
+
+/// Wraps a byte slice representing a Multiboot2 structure including an optional
+/// terminating padding, if necessary. It guarantees that the memory
+/// requirements promised in the crates description are respected.
+#[derive(Clone, Debug, PartialEq, Eq)]
+#[repr(transparent)]
+pub struct BytesRef<'a, H: Header> {
+    bytes: &'a [u8],
+    // Ensure that consumers can rely on the size properties for `H` that
+    // already have been verified when this type was constructed.
+    _h: PhantomData<H>,
+}
+
+impl<'a, H: Header> TryFrom<&'a [u8]> for BytesRef<'a, H> {
+    type Error = MemoryError;
+
+    fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
+        if bytes.len() < mem::size_of::<H>() {
+            return Err(MemoryError::ShorterThanHeader);
+        }
+        // Doesn't work as expected: if align_of_val(&value[0]) < ALIGNMENT {
+        if bytes.as_ptr().align_offset(ALIGNMENT) != 0 {
+            return Err(MemoryError::WrongAlignment);
+        }
+        let padding_bytes = bytes.len() % ALIGNMENT;
+        if padding_bytes != 0 {
+            return Err(MemoryError::MissingPadding);
+        }
+        Ok(Self {
+            bytes,
+            _h: PhantomData,
+        })
+    }
+}
+
+impl<'a, H: Header> Deref for BytesRef<'a, H> {
+    type Target = &'a [u8];
+
+    fn deref(&self) -> &Self::Target {
+        &self.bytes
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test_utils::{AlignedBytes, DummyTestHeader};
+
+    #[test]
+    fn test_bytes_ref() {
+        let empty: &[u8] = &[];
+        assert_eq!(
+            BytesRef::<'_, DummyTestHeader>::try_from(empty),
+            Err(MemoryError::ShorterThanHeader)
+        );
+
+        let slice = &[0_u8, 1, 2, 3, 4, 5, 6];
+        assert_eq!(
+            BytesRef::<'_, DummyTestHeader>::try_from(&slice[..]),
+            Err(MemoryError::ShorterThanHeader)
+        );
+
+        let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0]);
+        // Guaranteed wrong alignment
+        let unaligned_slice = &slice[3..];
+        assert_eq!(
+            BytesRef::<'_, DummyTestHeader>::try_from(unaligned_slice),
+            Err(MemoryError::WrongAlignment)
+        );
+
+        let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7]);
+        let slice = &slice[..];
+        assert_eq!(
+            BytesRef::try_from(slice),
+            Ok(BytesRef {
+                bytes: slice,
+                _h: PhantomData::<DummyTestHeader>
+            })
+        );
+    }
+}

+ 125 - 0
multiboot2-common/src/iter.rs

@@ -0,0 +1,125 @@
+//! Iterator over Multiboot2 structures. Technically, the process for iterating
+//! Multiboot2 information tags and iterating Multiboot2 header tags is the
+//! same.
+
+use crate::{increase_to_alignment, DynSizedStructure, Header, ALIGNMENT};
+use core::marker::PhantomData;
+use core::mem;
+
+/// Iterates over the tags (modelled by [`DynSizedStructure`]) of the underlying
+/// byte slice. Each tag is expected to have the same common [`Header`].
+///
+/// As the iterator emits elements of type [`DynSizedStructure`], users should
+/// casted them to specific [`Tag`]s using [`DynSizedStructure::cast`] following
+/// a user policy. This can for example happen on the basis of some ID.
+///
+/// This type ensures the memory safety guarantees promised by this crates
+/// documentation.
+///
+/// [`Tag`]: crate::Tag
+#[derive(Clone, Debug)]
+pub struct TagIter<'a, H: Header> {
+    /// Absolute offset to next tag and updated in each iteration.
+    next_tag_offset: usize,
+    buffer: &'a [u8],
+    // Ensure that all instances are bound to a specific `Header`.
+    // Otherwise, UB can happen.
+    _t: PhantomData<H>,
+}
+
+impl<'a, H: Header> TagIter<'a, H> {
+    /// Creates a new iterator.
+    #[must_use]
+    pub fn new(mem: &'a [u8]) -> Self {
+        // Assert alignment.
+        assert_eq!(mem.as_ptr().align_offset(ALIGNMENT), 0);
+
+        TagIter {
+            next_tag_offset: 0,
+            buffer: mem,
+            _t: PhantomData,
+        }
+    }
+}
+
+impl<'a, H: Header + 'a> Iterator for TagIter<'a, H> {
+    type Item = &'a DynSizedStructure<H>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.next_tag_offset == self.buffer.len() {
+            return None;
+        }
+        assert!(self.next_tag_offset < self.buffer.len());
+
+        let ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) }.cast::<H>();
+        let tag_hdr = unsafe { &*ptr };
+
+        // Get relevant byte portion for the next tag. This includes padding
+        // bytes to fulfill Rust memory guarantees. Otherwise, Miri complains.
+        // See <https://doc.rust-lang.org/reference/type-layout.html>.
+        let slice = {
+            let from = self.next_tag_offset;
+            let len = mem::size_of::<H>() + tag_hdr.payload_len();
+            let to = from + len;
+
+            // The size of (the allocation for) a value is always a multiple of
+            // its alignment.
+            // https://doc.rust-lang.org/reference/type-layout.html
+            let to = increase_to_alignment(to);
+
+            // Update ptr for next iteration.
+            self.next_tag_offset += to - from;
+
+            &self.buffer[from..to]
+        };
+
+        // unwrap: We should not fail at this point.
+        let tag = DynSizedStructure::ref_from_slice(slice).unwrap();
+        Some(tag)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::test_utils::{AlignedBytes, DummyTestHeader};
+    use crate::TagIter;
+    use core::borrow::Borrow;
+
+    #[test]
+    fn test_tag_iter() {
+        #[rustfmt::skip]
+        let bytes = AlignedBytes::new(
+            [
+                /* Some minimal tag.  */
+                0xff, 0, 0, 0,
+                8, 0, 0, 0,
+                /* Some tag with payload.  */
+                0xfe, 0, 0, 0,
+                12, 0, 0, 0,
+                1, 2, 3, 4,
+                // Padding
+                0, 0, 0, 0,
+                /* End tag */
+                0, 0, 0, 0,
+                8, 0, 0, 0,
+            ],
+        );
+        let mut iter = TagIter::<DummyTestHeader>::new(bytes.borrow());
+        let first = iter.next().unwrap();
+        assert_eq!(first.header().typ(), 0xff);
+        assert_eq!(first.header().size(), 8);
+        assert!(first.payload().is_empty());
+
+        let second = iter.next().unwrap();
+        assert_eq!(second.header().typ(), 0xfe);
+        assert_eq!(second.header().size(), 12);
+        assert_eq!(&second.payload(), &[1, 2, 3, 4]);
+
+        let third = iter.next().unwrap();
+        assert_eq!(third.header().typ(), 0);
+        assert_eq!(third.header().size(), 8);
+        assert!(first.payload().is_empty());
+
+        assert_eq!(iter.next(), None);
+    }
+}

+ 406 - 0
multiboot2-common/src/lib.rs

@@ -0,0 +1,406 @@
+//! Common helpers for the `multiboot2` and `multiboot2-header` crates.
+//!
+//! # Value-add
+//!
+//! The main value-add of this crate is to abstract away the parsing and
+//! construction of Multiboot2 structures. This is more complex as it may sound
+//! at first due to the difficulties listed below.
+//!
+//! The abstractions provided by this crate serve as the base to work with the
+//! following structures:
+//! - multiboot2:
+//!   - boot information structure (whole)
+//!   - boot information tags
+//! - multiboot2-header:
+//!   - header structure (whole)
+//!   - header tags
+//!
+//! # The Problem / Difficulties
+//!
+//! ## Multiboot2 Structures
+//!
+//! Multiboot2 structures are a consecutive chunk of bytes in memory. They use
+//! the "header pattern", which means a fixed size and known [`Header`] type
+//! indicates the total size of the structure. This is roughly translated to the
+//! following rusty base type:
+//!
+//! ```ignore
+//! #[repr(C, align(8))]
+//! struct DynStructure {
+//!     header: MyHeader,
+//!     payload: [u8]
+//! }
+//! ```
+//!
+//! Note that these structures can also be nested. So for example, the
+//! Multiboot2 boot information contains Multiboot2 tags, and the Multiboot2
+//! header contains Multiboot2 header tags - both are itself dynamic structures.
+//!
+//! A final `[u8]` field in the structs is the most rusty way to model this.
+//! However, this makes the type a Dynamically Sized Type (DST). To create
+//! references to these types from a byte slice, one needs fat pointers. They
+//! are a language feature currently not constructable with stable Rust.
+//! Luckily, we can utilize [`ptr_meta`].
+//!
+//! ## Dynamic and Sized Structs
+//!
+//! Note that we also have structures (tags) in Multiboot2 that looks like this:
+//!
+//! ```ignore
+//! #[repr(C, align(8))]
+//! struct DynStructure {
+//!     header: MyHeader,
+//!     // Not just [`u8`]
+//!     payload: [SomeType]
+//! }
+//! ```
+//!
+//! or
+//!
+//! ```ignore
+//! #[repr(C, align(8))]
+//! struct CommandLineTag {
+//!     header: TagHeader,
+//!     start: u32,
+//!     end: u32,
+//!     // More than just the base header before the dynamic portion
+//!     data: [u8]
+//! }
+//! ```
+//!
+//! ## Fat Pointer Requirements
+//!
+//! To create fat pointers with [`ptr_meta`], each tag needs a `Metadata` type
+//! which is either `usize` (for DSTs) or `()`. A trait is needed to abstract
+//! above sized or unsized types.
+//!
+//! ## Multiboot2 Requirements
+//!
+//! All tags must be 8-byte aligned. Space between multiple tags may be
+//! filled with zeroes if necessary. These zeroes are not reflected in the
+//! previous tag's size.
+//!
+//! ## Rustc Requirements
+//!
+//! The allocation space that Rust requires for types is a multiple of the
+//! alignment. This means that if we cast between byte slices and specific
+//! types, Rust doesn't just see the size reported by the header but also
+//! any necessary padding bytes. If this is not the case, for example we
+//! cast to a structure from a `&[u8; 15]`, Miri will complain as it expects
+//! `&[u8; 16]`
+//!
+//! See <https://doc.rust-lang.org/reference/type-layout.html> for information.
+//!
+//! # Provided Abstractions
+//!
+//! ## Parsing and Casting
+//!
+//! First, we need byte slices which are guaranteed to be aligned and are a
+//! multiple of the alignment. We have [`BytesRef`] for that. With that, we can
+//! create a [`DynSizedStructure`]. This is a rusty type that owns all the bytes
+//! it owns, according to the size reported by its header. Using this type
+//! and with the help of [`MaybeDynSized`], we can call
+//! [`DynSizedStructure::cast`] to cast this to arbitrary sized or unsized
+//! struct types fulfilling the corresponding requirements.
+//!
+//! This way, one can create nice rusty structs modeling the structure of the
+//! tags, and we only need a single "complicated" type, namely
+//! [`DynSizedStructure`].
+//!
+//! ## Iterating Tags
+//!
+//! To iterate over the tags of a structure, use [`TagIter`].
+//!
+//! # Memory Guarantees and Safety Promises
+//!
+//! For the parsing and construction of Multiboot2 structures, the alignment
+//! and necessary padding bytes as discussed above are guaranteed. When types
+//! are constructed, they return Results with appropriate error types. If
+//! during runtime something goes wrong, for example due to malformed tags,
+//! panics guarantee that no UB will happen.
+//!
+//! # No Public API
+//!
+//! Not meant as stable public API for others outside Multiboot2.
+
+#![no_std]
+#![cfg_attr(feature = "unstable", feature(error_in_core))]
+// --- BEGIN STYLE CHECKS ---
+#![deny(
+    clippy::all,
+    clippy::cargo,
+    clippy::must_use_candidate,
+    clippy::nursery,
+    missing_debug_implementations,
+    missing_docs,
+    rustdoc::all
+)]
+#![allow(clippy::multiple_crate_versions)]
+// --- END STYLE CHECKS ---
+
+#[cfg_attr(test, macro_use)]
+#[cfg(test)]
+extern crate std;
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+#[allow(unused)]
+pub mod test_utils;
+
+#[cfg(feature = "alloc")]
+mod boxed;
+mod bytes_ref;
+mod iter;
+mod tag;
+
+#[cfg(feature = "alloc")]
+pub use boxed::{clone_dyn, new_boxed};
+pub use bytes_ref::BytesRef;
+pub use iter::TagIter;
+pub use tag::{MaybeDynSized, Tag};
+
+use core::fmt::Debug;
+use core::mem;
+use core::ptr;
+use core::ptr::NonNull;
+use core::slice;
+
+/// The alignment of all Multiboot2 data structures.
+pub const ALIGNMENT: usize = 8;
+
+/// A sized header type for [`DynSizedStructure`]. Note that `header` refers to
+/// the header pattern. Thus, depending on the use case, this is not just a
+/// tag header. Instead, it refers to all bytes that are fixed and not part of
+/// any optional terminating dynamic `[u8]` slice in a [`DynSizedStructure`].
+///
+/// The alignment of implementors **must** be the compatible with the demands
+/// for the corresponding structure, which typically is [`ALIGNMENT`].
+pub trait Header: Clone + Sized + PartialEq + Eq + Debug {
+    /// Returns the length of the payload, i.e., the bytes that are additional
+    /// to the header. The value is measured in bytes.
+    #[must_use]
+    fn payload_len(&self) -> usize;
+
+    /// Returns the total size of the struct, thus the size of the header itself
+    /// plus [`Header::payload_len`].
+    #[must_use]
+    fn total_size(&self) -> usize {
+        mem::size_of::<Self>() + self.payload_len()
+    }
+
+    /// Updates the header with the given `total_size`.
+    fn set_size(&mut self, total_size: usize);
+}
+
+/// An C ABI-compatible dynamically sized type with a common sized [`Header`]
+/// and a dynamic amount of bytes. This structures owns all its bytes, unlike
+/// [`Header`]. Instances guarantees that the memory requirements promised in
+/// the crates description are respected.
+///
+/// This can be a Multiboot2 header tag, information tag, boot information, or
+/// a Multiboot2 header. Depending on the context, the [`Header`] is different.
+///
+/// # ABI
+/// This type uses the C ABI. The fixed [`Header`] portion is always there.
+/// Further, there is a variable amount of payload bytes. Thus, this type can
+/// only exist on the heap or references to it can be made by cast via fat
+/// pointers.
+#[derive(Debug, PartialEq, Eq, ptr_meta::Pointee)]
+#[repr(C, align(8))]
+pub struct DynSizedStructure<H: Header> {
+    header: H,
+    payload: [u8],
+    // Plus optional padding bytes to next alignment boundary, which are not
+    // reflected here. However, Rustc allocates them anyway and expects them
+    // to be there.
+    // See <https://doc.rust-lang.org/reference/type-layout.html>.
+}
+
+impl<H: Header> DynSizedStructure<H> {
+    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
+    /// from the given [`BytesRef`].
+    pub fn ref_from_bytes(bytes: BytesRef<H>) -> Result<&Self, MemoryError> {
+        let ptr = bytes.as_ptr().cast::<H>();
+        let hdr = unsafe { &*ptr };
+
+        if hdr.payload_len() > bytes.len() {
+            return Err(MemoryError::InvalidReportedTotalSize);
+        }
+
+        // At this point we know that the memory slice fulfills the base
+        // assumptions and requirements. Now, we safety can create the fat
+        // pointer.
+
+        let dst_size = hdr.payload_len();
+        // Create fat pointer for the DST.
+        let ptr = ptr_meta::from_raw_parts(ptr.cast(), dst_size);
+        let reference = unsafe { &*ptr };
+        Ok(reference)
+    }
+
+    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
+    /// from the given `&[u8]`.
+    pub fn ref_from_slice(bytes: &[u8]) -> Result<&Self, MemoryError> {
+        let bytes = BytesRef::<H>::try_from(bytes)?;
+        Self::ref_from_bytes(bytes)
+    }
+
+    /// Creates a new fat-pointer backed reference to a [`DynSizedStructure`]
+    /// from the given thin pointer to the [`Header`]. It reads the total size
+    /// from the header.
+    ///
+    /// # Safety
+    /// The caller must ensure that the function operates on valid memory.
+    pub unsafe fn ref_from_ptr<'a>(ptr: NonNull<H>) -> Result<&'a Self, MemoryError> {
+        let ptr = ptr.as_ptr().cast_const();
+        let hdr = unsafe { &*ptr };
+
+        let slice = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), hdr.total_size()) };
+        Self::ref_from_slice(slice)
+    }
+
+    /// Returns the underlying [`Header`].
+    pub const fn header(&self) -> &H {
+        &self.header
+    }
+
+    /// Returns the underlying payload.
+    pub const fn payload(&self) -> &[u8] {
+        &self.payload
+    }
+
+    /// Casts the structure tag to a specific [`MaybeDynSized`] implementation which
+    /// may be a ZST or DST typed tag. The output type will have the exact same
+    /// size as `*self`. The target type must be sufficient for that. If not,
+    /// the function will panic.
+    ///
+    /// # Safety
+    /// This function is safe due to various sanity checks and the overall
+    /// memory assertions done while constructing this type.
+    ///
+    /// # Panics
+    /// This panics if there is a size mismatch. However, this should never be
+    /// the case if all types follow their documented requirements.
+    pub fn cast<T: MaybeDynSized<Header = H> + ?Sized>(&self) -> &T {
+        let base_ptr = ptr::addr_of!(*self);
+
+        // This should be a compile-time assertion. However, this is the best
+        // location to place it for now.
+        assert!(T::BASE_SIZE >= mem::size_of::<H>());
+
+        let t_dst_size = T::dst_len(self.header());
+        let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
+        let t_ref = unsafe { &*t_ptr };
+
+        assert_eq!(mem::size_of_val(self), mem::size_of_val(t_ref));
+
+        t_ref
+    }
+}
+
+/// Errors that may occur when working with memory.
+#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, derive_more::Display)]
+pub enum MemoryError {
+    /// The memory points to null.
+    Null,
+    /// The memory must be at least [`ALIGNMENT`]-aligned.
+    WrongAlignment,
+    /// The memory must cover at least the length of the sized structure header
+    /// type.
+    ShorterThanHeader,
+    /// The buffer misses the terminating padding to the next alignment
+    /// boundary. The padding is relevant to satisfy Rustc/Miri, but also the
+    /// spec mandates that the padding is added.
+    MissingPadding,
+    /// The size-property has an illegal value that can't be fulfilled with the
+    /// given bytes.
+    InvalidReportedTotalSize,
+}
+
+#[cfg(feature = "unstable")]
+impl core::error::Error for MemoryError {}
+
+/// Increases the given size to the next alignment boundary, if it is not a
+/// multiple of the alignment yet. This is relevant as in Rust's [type layout],
+/// the allocated size of a type is always a multiple of the alignment, even
+/// if the type is smaller.
+///
+/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
+#[must_use]
+pub const fn increase_to_alignment(size: usize) -> usize {
+    let mask = ALIGNMENT - 1;
+    (size + mask) & !mask
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test_utils::{AlignedBytes, DummyTestHeader};
+    use core::borrow::Borrow;
+
+    #[test]
+    fn test_increase_to_alignment() {
+        assert_eq!(increase_to_alignment(0), 0);
+        assert_eq!(increase_to_alignment(1), 8);
+        assert_eq!(increase_to_alignment(7), 8);
+        assert_eq!(increase_to_alignment(8), 8);
+        assert_eq!(increase_to_alignment(9), 16);
+    }
+
+    #[test]
+    fn test_cast_generic_tag_to_sized_tag() {
+        #[repr(C)]
+        struct CustomSizedTag {
+            tag_header: DummyTestHeader,
+            a: u32,
+            b: u32,
+        }
+
+        impl MaybeDynSized for CustomSizedTag {
+            type Header = DummyTestHeader;
+
+            const BASE_SIZE: usize = mem::size_of::<Self>();
+
+            fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
+        }
+
+        let bytes = AlignedBytes([
+            /* id: 0xffff_ffff */
+            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
+            16, 0, 0, 0, /* field a: 0xdead_beef */
+            0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
+            0x37, 0x13, 0x37, 0x13,
+        ]);
+        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
+        let custom_tag = tag.cast::<CustomSizedTag>();
+
+        assert_eq!(mem::size_of_val(custom_tag), 16);
+        assert_eq!(custom_tag.a, 0xdead_beef);
+        assert_eq!(custom_tag.b, 0x1337_1337);
+    }
+
+    #[test]
+    fn test_cast_generic_tag_to_self() {
+        #[rustfmt::skip]
+        let bytes = AlignedBytes::new(
+            [
+                0x37, 0x13, 0, 0,
+                /* Tag size */
+                18, 0, 0, 0,
+                /* Some payload.  */
+                0, 1, 2, 3,
+                4, 5, 6, 7,
+                8, 9,
+                // Padding
+                0, 0, 0, 0, 0, 0
+            ],
+        );
+        let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
+
+        // Main objective here is also that this test passes Miri.
+        let tag = tag.cast::<DynSizedStructure<DummyTestHeader>>();
+        assert_eq!(tag.header().typ(), 0x1337);
+        assert_eq!(tag.header().size(), 18);
+    }
+}

+ 94 - 0
multiboot2-common/src/tag.rs

@@ -0,0 +1,94 @@
+//! Module for the traits [`MaybeDynSized`] and [`Tag`].
+
+use crate::{BytesRef, DynSizedStructure, Header};
+use core::mem;
+use core::slice;
+use ptr_meta::Pointee;
+
+/// A trait to abstract sized and unsized structures (DSTs). It enables
+/// casting a [`DynSizedStructure`] to sized or unsized structures using
+/// [`DynSizedStructure::cast`].
+///
+/// Structs that are a DST must provide a **correct**
+/// [`MaybeDynSized::dst_len`] implementation.
+///
+/// [`ID`]: Tag::ID
+/// [`DynSizedStructure`]: crate::DynSizedStructure
+pub trait MaybeDynSized: Pointee {
+    /// The associated [`Header`] of this tag.
+    type Header: Header;
+
+    /// The true base size of the struct without any implicit or additional
+    /// padding. Note that `size_of::<T>()` isn't sufficient, as for example
+    /// the type could have three `u32` fields, which would add an implicit
+    /// `u32` padding. However, this constant **must always** fulfill
+    /// `BASE_SIZE >= size_of::<Self::Header>()`.
+    ///
+    /// The main purpose of this constant is to create awareness when you
+    /// implement [`Self::dst_len`], where you should use this. If this value
+    /// is correct, we prevent situations where we read uninitialized bytes,
+    /// especially when creating tags in builders.
+    const BASE_SIZE: usize;
+
+    /// Returns the amount of items in the dynamically sized portion of the
+    /// DST. Note that this is not the amount of bytes. So if the dynamically
+    /// sized portion is 16 bytes in size and each element is 4 bytes big, then
+    /// this function must return 4.
+    ///
+    /// For sized tags, this just returns `()`. For DSTs, this returns an
+    /// `usize`.
+    fn dst_len(header: &Self::Header) -> Self::Metadata;
+
+    /// Returns the corresponding [`Header`].
+    fn header(&self) -> &Self::Header {
+        let ptr = core::ptr::addr_of!(*self);
+        unsafe { &*ptr.cast::<Self::Header>() }
+    }
+
+    /// Returns the payload, i.e., all memory that is not occupied by the
+    /// [`Header`] of the type.
+    fn payload(&self) -> &[u8] {
+        let from = mem::size_of::<Self::Header>();
+        &self.as_bytes()[from..]
+    }
+
+    /// Returns the whole allocated bytes for this structure encapsulated in
+    /// [`BytesRef`]. This includes padding bytes. To only get the "true" tag
+    /// data, read the tag size from [`Self::header`] and create a sub slice.
+    fn as_bytes(&self) -> BytesRef<Self::Header> {
+        let ptr = core::ptr::addr_of!(*self);
+        // Actual tag size, optionally with terminating padding.
+        let size = mem::size_of_val(self);
+        let slice = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), size) };
+        // Unwrap is fine as this type can't exist without the underlying memory
+        // guarantees.
+        BytesRef::try_from(slice).unwrap()
+    }
+
+    /// Returns a pointer to this structure.
+    fn as_ptr(&self) -> *const Self::Header {
+        self.as_bytes().as_ptr().cast()
+    }
+}
+
+/// Extension of [`MaybeDynSized`] for Tags.
+pub trait Tag: MaybeDynSized {
+    /// The ID type that identifies the tag.
+    type IDType: PartialEq + Eq;
+
+    /// The ID of this tag. This should be unique across all implementors.
+    ///
+    /// Although the ID is not yet used in `multiboot2-common`, it ensures
+    /// a consistent API in consumer crates.
+    const ID: Self::IDType;
+}
+
+impl<H: Header> MaybeDynSized for DynSizedStructure<H> {
+    type Header = H;
+
+    const BASE_SIZE: usize = mem::size_of::<H>();
+
+    fn dst_len(header: &Self::Header) -> Self::Metadata {
+        header.payload_len()
+    }
+}

+ 84 - 27
multiboot2/src/test_util.rs → multiboot2-common/src/test_utils.rs

@@ -1,17 +1,22 @@
 //! Various test utilities.
 
-use crate::ALIGNMENT;
+#![allow(missing_docs)]
+
+use crate::{Header, MaybeDynSized, Tag};
 use core::borrow::Borrow;
+use core::mem;
 use core::ops::Deref;
 
 /// Helper to 8-byte align the underlying bytes, as mandated in the Multiboot2
 /// spec. With this type, one can create manual and raw Multiboot2 boot
 /// information or just the bytes for simple tags, in a manual and raw approach.
-#[cfg(test)]
+#[derive(Debug)]
 #[repr(C, align(8))]
 pub struct AlignedBytes<const N: usize>(pub [u8; N]);
 
 impl<const N: usize> AlignedBytes<N> {
+    /// Creates a new type.
+    #[must_use]
     pub const fn new(bytes: [u8; N]) -> Self {
         Self(bytes)
     }
@@ -37,51 +42,103 @@ impl<const N: usize> Deref for AlignedBytes<N> {
     }
 }
 
-// The tests down below are all Miri-approved.
+/// Dummy test header.
+#[derive(Clone, Debug, PartialEq, Eq)]
+#[repr(C, align(8))]
+pub struct DummyTestHeader {
+    typ: u32,
+    size: u32,
+}
+
+impl DummyTestHeader {
+    #[must_use]
+    pub const fn new(typ: u32, size: u32) -> Self {
+        Self { typ, size }
+    }
+
+    #[must_use]
+    pub const fn typ(&self) -> u32 {
+        self.typ
+    }
+
+    #[must_use]
+    pub const fn size(&self) -> u32 {
+        self.size
+    }
+}
+
+impl Header for DummyTestHeader {
+    fn payload_len(&self) -> usize {
+        self.size as usize - mem::size_of::<Self>()
+    }
+
+    fn set_size(&mut self, total_size: usize) {
+        self.size = total_size as u32;
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, ptr_meta::Pointee)]
+#[repr(C, align(8))]
+pub struct DummyDstTag {
+    header: DummyTestHeader,
+    payload: [u8],
+}
+
+impl DummyDstTag {
+    const BASE_SIZE: usize = mem::size_of::<DummyTestHeader>();
+
+    #[must_use]
+    pub const fn header(&self) -> &DummyTestHeader {
+        &self.header
+    }
+
+    #[must_use]
+    pub const fn payload(&self) -> &[u8] {
+        &self.payload
+    }
+}
+
+impl MaybeDynSized for DummyDstTag {
+    type Header = DummyTestHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<DummyTestHeader>();
+
+    fn dst_len(header: &Self::Header) -> Self::Metadata {
+        header.size as usize - Self::BASE_SIZE
+    }
+}
+
+impl Tag for DummyDstTag {
+    type IDType = u32;
+    const ID: Self::IDType = 42;
+}
+
 #[cfg(test)]
 mod tests {
-    use super::*;
-    use crate::tag::TagBytesRef;
     use core::mem;
     use core::ptr::addr_of;
 
+    use crate::ALIGNMENT;
+
+    use super::*;
+
     #[test]
     fn abi() {
+        assert_eq!(mem::align_of::<AlignedBytes<0>>(), ALIGNMENT);
+
         let bytes = AlignedBytes([0]);
-        assert_eq!(mem::align_of_val(&bytes), ALIGNMENT);
         assert_eq!(bytes.as_ptr().align_offset(8), 0);
         assert_eq!((addr_of!(bytes[0])).align_offset(8), 0);
 
         let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7]);
-        assert_eq!(mem::align_of_val(&bytes), ALIGNMENT);
         assert_eq!(bytes.as_ptr().align_offset(8), 0);
         assert_eq!((addr_of!(bytes[0])).align_offset(8), 0);
 
         let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
-        assert_eq!(mem::align_of_val(&bytes), ALIGNMENT);
         assert_eq!(bytes.as_ptr().align_offset(8), 0);
         assert_eq!((addr_of!(bytes[0])).align_offset(8), 0);
         assert_eq!((addr_of!(bytes[7])).align_offset(8), 1);
         assert_eq!((addr_of!(bytes[8])).align_offset(8), 0);
         assert_eq!((addr_of!(bytes[9])).align_offset(8), 7);
     }
-
-    #[test]
-    fn compatible_with_tag_bytes_ref_type() {
-        #[rustfmt::skip]
-        let bytes = AlignedBytes(
-            [
-                /* tag id */
-                0, 0, 0, 0,
-                /* size */
-                14, 0, 0, 0,
-                /* arbitrary payload */
-                1, 2, 3, 4,
-                5, 6,
-                /* padding */
-                0, 0,
-            ]
-        );
-        let _a = TagBytesRef::try_from(bytes.borrow()).unwrap();
-    }
 }

+ 6 - 2
multiboot2-header/Cargo.toml

@@ -4,7 +4,7 @@ description = """
 Library with type definitions and parsing functions for Multiboot2 headers.
 This library is `no_std` and can be used in bootloaders.
 """
-version = "0.4.0"
+version = "0.5.0"
 authors = [
     "Philipp Schuster <phip1611@gmail.com>"
 ]
@@ -12,6 +12,7 @@ license = "MIT/Apache-2.0"
 edition = "2021"
 categories = [
     "no-std",
+    "no-std::no-alloc",
     "parsing",
 ]
 keywords = [
@@ -41,7 +42,10 @@ unstable = []
 
 [dependencies]
 derive_more.workspace = true
-multiboot2 = { version = "0.21.0", default-features = false }
+log.workspace = true
+ptr_meta.workspace = true
+multiboot2 = { version = "0.22.0", default-features = false }
+multiboot2-common = "0.1.0"
 
 [package.metadata.docs.rs]
 all-features = true

+ 13 - 1
multiboot2-header/Changelog.md

@@ -1,8 +1,20 @@
 # CHANGELOG for crate `multiboot2-header`
 
-## Unreleased
+## v0.5.0
+
+This release contains a major refactoring of the internals, guaranteeing
+even more sanity checks for correct behaviour and lack of UB. In this release,
+the `Builder` was rewritten and lots of corresponding UB in certain
+corer-cases removed. Further, the builder's API was streamlined.
+
+If you are interested in the internals of the major refactorings recently taken
+place, please head to the documentation of `multiboot2-common`.
 
 - **Breaking** All functions that returns something useful are now `#[must_use]`
+- **Breaking** The builder type is now just called `Builder`. This needs the
+  `builder` feature.
+- **Breaking:** The error type returned by `Multiboot2Header::load` has been
+  changed.
 - Updated to latest `multiboot2` dependency
 
 ## 0.4.0 (2024-05-01)

+ 18 - 11
multiboot2-header/examples/minimal.rs

@@ -1,14 +1,16 @@
-use multiboot2_header::builder::{HeaderBuilder, InformationRequestHeaderTagBuilder};
+use multiboot2_common::MaybeDynSized;
+use multiboot2_header::Builder;
 use multiboot2_header::{
-    HeaderTagFlag, HeaderTagISA, MbiTagType, Multiboot2Header, RelocatableHeaderTag,
-    RelocatableHeaderTagPreference,
+    HeaderTagFlag, HeaderTagISA, InformationRequestHeaderTag, MbiTagType, Multiboot2Header,
+    RelocatableHeaderTag, RelocatableHeaderTagPreference,
 };
 
 /// Small example that creates a Multiboot2 header and parses it afterwards.
 fn main() {
-    // We create a Multiboot2 header during runtime here. A practical example is that your
-    // program gets the header from a file and parses it afterwards.
-    let mb2_hdr_bytes = HeaderBuilder::new(HeaderTagISA::I386)
+    // We create a Multiboot2 header during runtime here. A more practical
+    // example, however, would be that you parse the header from kernel binary
+    // at runtime.
+    let mb2_hdr_bytes = Builder::new(HeaderTagISA::I386)
         .relocatable_tag(RelocatableHeaderTag::new(
             HeaderTagFlag::Required,
             0x1337,
@@ -16,13 +18,18 @@ fn main() {
             4096,
             RelocatableHeaderTagPreference::None,
         ))
-        .information_request_tag(
-            InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required)
-                .add_irs(&[MbiTagType::Cmdline, MbiTagType::BootLoaderName]),
-        )
+        .information_request_tag(InformationRequestHeaderTag::new(
+            HeaderTagFlag::Required,
+            &[
+                MbiTagType::Cmdline.into(),
+                MbiTagType::BootLoaderName.into(),
+            ],
+        ))
         .build();
 
     // Cast bytes in vector to Multiboot2 information structure
-    let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr_bytes.as_ptr().cast()) };
+    let ptr = mb2_hdr_bytes.as_bytes().as_ptr();
+    let mb2_hdr = unsafe { Multiboot2Header::load(ptr.cast()) };
+    let mb2_hdr = mb2_hdr.unwrap();
     println!("{:#?}", mb2_hdr);
 }

+ 15 - 1
multiboot2-header/src/address.rs

@@ -1,12 +1,13 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
 use core::mem::size_of;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// This information does not need to be provided if the kernel image is in ELF
 /// format, but it must be provided if the image is in a.out format or in some
 /// other format. Required for legacy boot (BIOS).
 /// Determines load addresses.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct AddressHeaderTag {
     header: HeaderTagHeader,
     /// Contains the address corresponding to the beginning of the Multiboot2 header — the physical memory location at which the magic value is supposed to be loaded. This field serves to synchronize the mapping between OS image offsets and physical memory addresses.
@@ -84,6 +85,19 @@ impl AddressHeaderTag {
     }
 }
 
+impl MaybeDynSized for AddressHeaderTag {
+    type Header = HeaderTagHeader;
+
+    const BASE_SIZE: usize = size_of::<Self>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for AddressHeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::Address;
+}
+
 #[cfg(test)]
 mod tests {
     use crate::AddressHeaderTag;

+ 238 - 0
multiboot2-header/src/builder.rs

@@ -0,0 +1,238 @@
+//! Exports a builder [`Builder`].
+
+use crate::{
+    AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EntryAddressHeaderTag,
+    EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, HeaderTagISA,
+    InformationRequestHeaderTag, ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag,
+};
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use multiboot2_common::{new_boxed, DynSizedStructure, MaybeDynSized};
+
+/// Builder for a Multiboot2 header information.
+#[derive(Debug)]
+pub struct Builder {
+    arch: HeaderTagISA,
+    information_request_tag: Option<Box<InformationRequestHeaderTag>>,
+    address_tag: Option<AddressHeaderTag>,
+    entry_tag: Option<EntryAddressHeaderTag>,
+    console_tag: Option<ConsoleHeaderTag>,
+    framebuffer_tag: Option<FramebufferHeaderTag>,
+    module_align_tag: Option<ModuleAlignHeaderTag>,
+    efi_bs_tag: Option<EfiBootServiceHeaderTag>,
+    efi_32_tag: Option<EntryEfi32HeaderTag>,
+    efi_64_tag: Option<EntryEfi64HeaderTag>,
+    relocatable_tag: Option<RelocatableHeaderTag>,
+    // TODO add support for custom tags once someone requests it.
+}
+
+impl Builder {
+    /// Set the [`RelocatableHeaderTag`] tag.
+    #[must_use]
+    pub const fn new(arch: HeaderTagISA) -> Self {
+        Self {
+            arch,
+            information_request_tag: None,
+            address_tag: None,
+            entry_tag: None,
+            console_tag: None,
+            framebuffer_tag: None,
+            module_align_tag: None,
+            efi_bs_tag: None,
+            efi_32_tag: None,
+            efi_64_tag: None,
+            relocatable_tag: None,
+        }
+    }
+
+    /// Set the [`InformationRequestHeaderTag`] tag.
+    #[must_use]
+    pub fn information_request_tag(
+        mut self,
+        information_request_tag: Box<InformationRequestHeaderTag>,
+    ) -> Self {
+        self.information_request_tag = Some(information_request_tag);
+        self
+    }
+
+    /// Set the [`AddressHeaderTag`] tag.
+    #[must_use]
+    pub const fn address_tag(mut self, address_tag: AddressHeaderTag) -> Self {
+        self.address_tag = Some(address_tag);
+        self
+    }
+
+    /// Set the [`EntryAddressHeaderTag`] tag.
+    #[must_use]
+    pub const fn entry_tag(mut self, entry_tag: EntryAddressHeaderTag) -> Self {
+        self.entry_tag = Some(entry_tag);
+        self
+    }
+
+    /// Set the [`ConsoleHeaderTag`] tag.
+    #[must_use]
+    pub const fn console_tag(mut self, console_tag: ConsoleHeaderTag) -> Self {
+        self.console_tag = Some(console_tag);
+        self
+    }
+
+    /// Set the [`FramebufferHeaderTag`] tag.
+    #[must_use]
+    pub const fn framebuffer_tag(mut self, framebuffer_tag: FramebufferHeaderTag) -> Self {
+        self.framebuffer_tag = Some(framebuffer_tag);
+        self
+    }
+
+    /// Set the [`ModuleAlignHeaderTag`] tag.
+    #[must_use]
+    pub const fn module_align_tag(mut self, module_align_tag: ModuleAlignHeaderTag) -> Self {
+        self.module_align_tag = Some(module_align_tag);
+        self
+    }
+
+    /// Set the [`EfiBootServiceHeaderTag`] tag.
+    #[must_use]
+    pub const fn efi_bs_tag(mut self, efi_bs_tag: EfiBootServiceHeaderTag) -> Self {
+        self.efi_bs_tag = Some(efi_bs_tag);
+        self
+    }
+
+    /// Set the [`EntryEfi32HeaderTag`] tag.
+    #[must_use]
+    pub const fn efi_32_tag(mut self, efi_32_tag: EntryEfi32HeaderTag) -> Self {
+        self.efi_32_tag = Some(efi_32_tag);
+        self
+    }
+
+    /// Set the [`EntryEfi64HeaderTag`] tag.
+    #[must_use]
+    pub const fn efi_64_tag(mut self, efi_64_tag: EntryEfi64HeaderTag) -> Self {
+        self.efi_64_tag = Some(efi_64_tag);
+        self
+    }
+
+    /// Set the [`RelocatableHeaderTag`] tag.
+    #[must_use]
+    pub const fn relocatable_tag(mut self, relocatable_tag: RelocatableHeaderTag) -> Self {
+        self.relocatable_tag = Some(relocatable_tag);
+        self
+    }
+
+    /// Returns properly aligned bytes on the heap representing a valid
+    /// Multiboot2 header structure.
+    #[must_use]
+    pub fn build(self) -> Box<DynSizedStructure<Multiboot2BasicHeader>> {
+        let header = Multiboot2BasicHeader::new(self.arch, 0);
+        let mut byte_refs = Vec::new();
+        if let Some(tag) = self.information_request_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.address_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.entry_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.console_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.framebuffer_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.module_align_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.efi_bs_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.efi_32_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.efi_64_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.relocatable_tag.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        // TODO add support for custom tags once someone requests it.
+        new_boxed(header, byte_refs.as_slice())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::ConsoleHeaderTagFlags::ConsoleRequired;
+    use crate::HeaderTagFlag::{Optional, Required};
+    use crate::RelocatableHeaderTagPreference::High;
+    use crate::{MbiTagType, Multiboot2Header};
+
+    #[test]
+    fn build_and_parse() {
+        let builder = Builder::new(HeaderTagISA::I386)
+            .information_request_tag(InformationRequestHeaderTag::new(
+                Optional,
+                &[
+                    MbiTagType::Cmdline.into(),
+                    MbiTagType::BootLoaderName.into(),
+                    MbiTagType::Module.into(),
+                    MbiTagType::BasicMeminfo.into(),
+                    MbiTagType::Bootdev.into(),
+                    MbiTagType::Mmap.into(),
+                    MbiTagType::Vbe.into(),
+                    MbiTagType::Framebuffer.into(),
+                    MbiTagType::ElfSections.into(),
+                    MbiTagType::Apm.into(),
+                    MbiTagType::Efi32.into(),
+                    MbiTagType::Efi64.into(),
+                    MbiTagType::Smbios.into(),
+                    MbiTagType::AcpiV1.into(),
+                    MbiTagType::AcpiV2.into(),
+                    MbiTagType::Network.into(),
+                    MbiTagType::EfiMmap.into(),
+                    MbiTagType::EfiBs.into(),
+                    MbiTagType::Efi32Ih.into(),
+                    MbiTagType::Efi64Ih.into(),
+                    MbiTagType::LoadBaseAddr.into(),
+                    MbiTagType::Custom(0x1337).into(),
+                ],
+            ))
+            .address_tag(AddressHeaderTag::new(
+                Required, 0x1000, 0x2000, 0x3000, 0x4000,
+            ))
+            .entry_tag(EntryAddressHeaderTag::new(Required, 0x5000))
+            .console_tag(ConsoleHeaderTag::new(Required, ConsoleRequired))
+            .framebuffer_tag(FramebufferHeaderTag::new(Optional, 720, 1024, 8))
+            .module_align_tag(ModuleAlignHeaderTag::new(Required))
+            .efi_bs_tag(EfiBootServiceHeaderTag::new(Optional))
+            .efi_32_tag(EntryEfi32HeaderTag::new(Required, 0x7000))
+            .efi_64_tag(EntryEfi64HeaderTag::new(Required, 0x8000))
+            .relocatable_tag(RelocatableHeaderTag::new(
+                Required, 0x9000, 0x10000, 4096, High,
+            ));
+
+        let structure = builder.build();
+        let header =
+            unsafe { Multiboot2Header::load(structure.as_bytes().as_ref().as_ptr().cast()) }
+                .unwrap();
+
+        assert!(header.verify_checksum());
+
+        for tag in header.iter() {
+            dbg!(tag);
+        }
+
+        dbg!(header.arch());
+        dbg!(header.checksum());
+        dbg!(header.information_request_tag());
+        dbg!(header.address_tag());
+        dbg!(header.entry_address_tag());
+        dbg!(header.console_flags_tag());
+        dbg!(header.framebuffer_tag());
+        dbg!(header.module_align_tag());
+        dbg!(header.efi_boot_services_tag());
+        dbg!(header.entry_address_efi32_tag());
+        dbg!(header.entry_address_efi64_tag());
+        dbg!(header.relocatable_tag());
+    }
+}

+ 0 - 425
multiboot2-header/src/builder/header.rs

@@ -1,425 +0,0 @@
-//! Exports item [`HeaderBuilder`].
-
-use crate::builder::information_request::InformationRequestHeaderTagBuilder;
-use crate::builder::traits::StructAsBytes;
-use crate::HeaderTagISA;
-use crate::{
-    AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag,
-    EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag,
-    ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag,
-};
-use alloc::vec::Vec;
-use core::mem::size_of;
-use core::ops::Deref;
-
-/// Holds the raw bytes of a boot information built with [`HeaderBuilder`]
-/// on the heap. The bytes returned by [`HeaderBytes::as_bytes`] are
-/// guaranteed to be properly aligned.
-#[derive(Clone, Debug)]
-pub struct HeaderBytes {
-    // Offset into the bytes where the header starts. This is necessary to
-    // guarantee alignment at the moment.
-    offset: usize,
-    structure_len: usize,
-    bytes: Vec<u8>,
-}
-
-impl HeaderBytes {
-    /// Returns the bytes. They are guaranteed to be correctly aligned.
-    pub fn as_bytes(&self) -> &[u8] {
-        let slice = &self.bytes[self.offset..self.offset + self.structure_len];
-        // At this point, the alignment is guaranteed. If not, something is
-        // broken fundamentally.
-        assert_eq!(slice.as_ptr().align_offset(8), 0);
-        slice
-    }
-}
-
-impl Deref for HeaderBytes {
-    type Target = [u8];
-
-    fn deref(&self) -> &Self::Target {
-        self.as_bytes()
-    }
-}
-
-/// Builder to construct a valid Multiboot2 header dynamically at runtime.
-/// The tags will appear in the order of their corresponding enumeration,
-/// except for the END tag.
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct HeaderBuilder {
-    arch: HeaderTagISA,
-    // first
-    information_request_tag: Option<InformationRequestHeaderTagBuilder>,
-    // second
-    address_tag: Option<AddressHeaderTag>,
-    // third
-    entry_tag: Option<EntryAddressHeaderTag>,
-    // fourth
-    console_tag: Option<ConsoleHeaderTag>,
-    // fifth
-    framebuffer_tag: Option<FramebufferHeaderTag>,
-    // sixth
-    module_align_tag: Option<ModuleAlignHeaderTag>,
-    // seventh
-    efi_bs_tag: Option<EfiBootServiceHeaderTag>,
-    // eighth
-    efi_32_tag: Option<EntryEfi32HeaderTag>,
-    // ninth
-    efi_64_tag: Option<EntryEfi64HeaderTag>,
-    // tenth (last)
-    relocatable_tag: Option<RelocatableHeaderTag>,
-}
-
-impl HeaderBuilder {
-    /// Creates a new builder.
-    #[must_use]
-    pub const fn new(arch: HeaderTagISA) -> Self {
-        Self {
-            arch,
-            information_request_tag: None,
-            address_tag: None,
-            entry_tag: None,
-            console_tag: None,
-            framebuffer_tag: None,
-            module_align_tag: None,
-            efi_bs_tag: None,
-            efi_32_tag: None,
-            efi_64_tag: None,
-            relocatable_tag: None,
-        }
-    }
-
-    /// Returns the size, if the value is a multiple of 8 or returns
-    /// the next number that is a multiple of 8. With this, one can
-    /// easily calculate the size of a Multiboot2 header, where
-    /// all the tags are 8-byte aligned.
-    const fn size_or_up_aligned(size: usize) -> usize {
-        (size + 7) & !7
-    }
-
-    /// Returns the expected length of the Multiboot2 header, when the
-    /// [`Self::build`]-method gets called.
-    #[must_use]
-    pub fn expected_len(&self) -> usize {
-        let base_len = size_of::<Multiboot2BasicHeader>();
-        // size_or_up_aligned not required, because basic header length is 16 and the
-        // begin is 8 byte aligned => first tag automatically 8 byte aligned
-        let mut len = Self::size_or_up_aligned(base_len);
-        if let Some(tag_builder) = self.information_request_tag.as_ref() {
-            // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address.
-            // Attention: expected len from builder, not the size of the builder itself!
-            len += Self::size_or_up_aligned(tag_builder.expected_len())
-        }
-        if self.address_tag.is_some() {
-            // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address
-            len += Self::size_or_up_aligned(size_of::<AddressHeaderTag>())
-        }
-        if self.entry_tag.is_some() {
-            len += Self::size_or_up_aligned(size_of::<EntryAddressHeaderTag>())
-        }
-        if self.console_tag.is_some() {
-            len += Self::size_or_up_aligned(size_of::<ConsoleHeaderTag>())
-        }
-        if self.framebuffer_tag.is_some() {
-            len += Self::size_or_up_aligned(size_of::<FramebufferHeaderTag>())
-        }
-        if self.module_align_tag.is_some() {
-            len += Self::size_or_up_aligned(size_of::<ModuleAlignHeaderTag>())
-        }
-        if self.efi_bs_tag.is_some() {
-            len += Self::size_or_up_aligned(size_of::<EfiBootServiceHeaderTag>())
-        }
-        if self.efi_32_tag.is_some() {
-            len += Self::size_or_up_aligned(size_of::<EntryEfi32HeaderTag>())
-        }
-        if self.efi_64_tag.is_some() {
-            len += Self::size_or_up_aligned(size_of::<EntryEfi64HeaderTag>())
-        }
-        if self.relocatable_tag.is_some() {
-            len += Self::size_or_up_aligned(size_of::<RelocatableHeaderTag>())
-        }
-        // only here size_or_up_aligned is not important, because it is the last tag
-        len += size_of::<EndHeaderTag>();
-        len
-    }
-
-    /// Adds the bytes of a tag to the final Multiboot2 header byte vector.
-    fn build_add_bytes(dest: &mut Vec<u8>, source: &[u8], is_end_tag: bool) {
-        let vec_next_write_ptr = unsafe { dest.as_ptr().add(dest.len()) };
-        // At this point, the alignment is guaranteed. If not, something is
-        // broken fundamentally.
-        assert_eq!(vec_next_write_ptr.align_offset(8), 0);
-
-        dest.extend(source);
-        if !is_end_tag {
-            let size = source.len();
-            let size_to_8_align = Self::size_or_up_aligned(size);
-            let size_to_8_align_diff = size_to_8_align - size;
-            // fill zeroes so that next data block is 8-byte aligned
-            dest.extend([0].repeat(size_to_8_align_diff));
-        }
-    }
-
-    /// Constructs the bytes for a valid Multiboot2 header with the given properties.
-    #[must_use]
-    pub fn build(self) -> HeaderBytes {
-        const ALIGN: usize = 8;
-
-        // PHASE 1/2: Prepare Vector
-
-        // We allocate more than necessary so that we can ensure an correct
-        // alignment within this data.
-        let expected_len = self.expected_len();
-        let alloc_len = expected_len + 7;
-        let mut bytes = Vec::<u8>::with_capacity(alloc_len);
-        // Pointer to check that no relocation happened.
-        let alloc_ptr = bytes.as_ptr();
-
-        // As long as there is no nice way in stable Rust to guarantee the
-        // alignment of a vector, I add zero bytes at the beginning and the
-        // header might not start at the start of the allocation.
-        //
-        // Unfortunately, it is not possible to reliably test this in a unit
-        // test as long as the allocator_api feature is not stable.
-        // Due to my manual testing, however, it works.
-        let offset = bytes.as_ptr().align_offset(ALIGN);
-        bytes.extend([0].repeat(offset));
-
-        // -----------------------------------------------
-        // PHASE 2/2: Add Tags
-        self.build_add_tags(&mut bytes);
-
-        assert_eq!(
-            alloc_ptr,
-            bytes.as_ptr(),
-            "Vector was reallocated. Alignment of header probably broken!"
-        );
-        assert_eq!(
-            bytes[0..offset].iter().sum::<u8>(),
-            0,
-            "The offset to alignment area should be zero."
-        );
-
-        HeaderBytes {
-            offset,
-            bytes,
-            structure_len: expected_len,
-        }
-    }
-
-    /// Helper method that adds all the tags to the given vector.
-    fn build_add_tags(&self, bytes: &mut Vec<u8>) {
-        Self::build_add_bytes(
-            bytes,
-            // important that we write the correct expected length into the header!
-            &Multiboot2BasicHeader::new(self.arch, self.expected_len() as u32).struct_as_bytes(),
-            false,
-        );
-
-        if let Some(irs) = self.information_request_tag.clone() {
-            Self::build_add_bytes(bytes, &irs.build(), false)
-        }
-        if let Some(tag) = self.address_tag.as_ref() {
-            Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
-        }
-        if let Some(tag) = self.entry_tag.as_ref() {
-            Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
-        }
-        if let Some(tag) = self.console_tag.as_ref() {
-            Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
-        }
-        if let Some(tag) = self.framebuffer_tag.as_ref() {
-            Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
-        }
-        if let Some(tag) = self.module_align_tag.as_ref() {
-            Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
-        }
-        if let Some(tag) = self.efi_bs_tag.as_ref() {
-            Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
-        }
-        if let Some(tag) = self.efi_32_tag.as_ref() {
-            Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
-        }
-        if let Some(tag) = self.efi_64_tag.as_ref() {
-            Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
-        }
-        if let Some(tag) = self.relocatable_tag.as_ref() {
-            Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
-        }
-        Self::build_add_bytes(bytes, &EndHeaderTag::new().struct_as_bytes(), true);
-    }
-
-    // clippy thinks this can be a const fn but the compiler denies it
-    // #[allow(clippy::missing_const_for_fn)]
-    /// Adds information requests from the
-    /// [`InformationRequestHeaderTagBuilder`] to the builder.
-    #[must_use]
-    #[allow(clippy::missing_const_for_fn)] // only in Rust 1.70 necessary
-    pub fn information_request_tag(
-        mut self,
-        information_request_tag: InformationRequestHeaderTagBuilder,
-    ) -> Self {
-        self.information_request_tag = Some(information_request_tag);
-        self
-    }
-
-    /// Adds a [`AddressHeaderTag`] to the builder.
-    #[must_use]
-    pub const fn address_tag(mut self, address_tag: AddressHeaderTag) -> Self {
-        self.address_tag = Some(address_tag);
-        self
-    }
-
-    /// Adds a [`EntryAddressHeaderTag`] to the builder.
-    #[must_use]
-    pub const fn entry_tag(mut self, entry_tag: EntryAddressHeaderTag) -> Self {
-        self.entry_tag = Some(entry_tag);
-        self
-    }
-
-    /// Adds a [`ConsoleHeaderTag`] to the builder.
-    #[must_use]
-    pub const fn console_tag(mut self, console_tag: ConsoleHeaderTag) -> Self {
-        self.console_tag = Some(console_tag);
-        self
-    }
-
-    /// Adds a [`FramebufferHeaderTag`] to the builder.
-    #[must_use]
-    pub const fn framebuffer_tag(mut self, framebuffer_tag: FramebufferHeaderTag) -> Self {
-        self.framebuffer_tag = Some(framebuffer_tag);
-        self
-    }
-
-    /// Adds a [`ModuleAlignHeaderTag`] to the builder.
-    #[must_use]
-    pub const fn module_align_tag(mut self, module_align_tag: ModuleAlignHeaderTag) -> Self {
-        self.module_align_tag = Some(module_align_tag);
-        self
-    }
-
-    /// Adds a [`EfiBootServiceHeaderTag`] to the builder.
-    #[must_use]
-    pub const fn efi_bs_tag(mut self, efi_bs_tag: EfiBootServiceHeaderTag) -> Self {
-        self.efi_bs_tag = Some(efi_bs_tag);
-        self
-    }
-
-    /// Adds a [`EntryEfi32HeaderTag`] to the builder.
-    #[must_use]
-    pub const fn efi_32_tag(mut self, efi_32_tag: EntryEfi32HeaderTag) -> Self {
-        self.efi_32_tag = Some(efi_32_tag);
-        self
-    }
-
-    /// Adds a [`EntryEfi64HeaderTag`] to the builder.
-    #[must_use]
-    pub const fn efi_64_tag(mut self, efi_64_tag: EntryEfi64HeaderTag) -> Self {
-        self.efi_64_tag = Some(efi_64_tag);
-        self
-    }
-
-    /// Adds a [`RelocatableHeaderTag`] to the builder.
-    #[must_use]
-    pub const fn relocatable_tag(mut self, relocatable_tag: RelocatableHeaderTag) -> Self {
-        self.relocatable_tag = Some(relocatable_tag);
-        self
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::builder::header::HeaderBuilder;
-    use crate::builder::information_request::InformationRequestHeaderTagBuilder;
-    use crate::{
-        HeaderTagFlag, HeaderTagISA, MbiTagType, Multiboot2Header, RelocatableHeaderTag,
-        RelocatableHeaderTagPreference,
-    };
-
-    fn create_builder() -> HeaderBuilder {
-        let builder = HeaderBuilder::new(HeaderTagISA::I386);
-        // Multiboot2 basic header + end tag
-        let mut expected_len = 16 + 8;
-        assert_eq!(builder.expected_len(), expected_len);
-
-        // add information request tag
-        let ifr_builder =
-            InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required).add_irs(&[
-                MbiTagType::EfiMmap,
-                MbiTagType::Cmdline,
-                MbiTagType::ElfSections,
-            ]);
-        let ifr_tag_size_with_padding = ifr_builder.expected_len() + 4;
-        assert_eq!(
-            ifr_tag_size_with_padding % 8,
-            0,
-            "the length of the IFR tag with padding must be a multiple of 8"
-        );
-        expected_len += ifr_tag_size_with_padding;
-        let builder = builder.information_request_tag(ifr_builder);
-        assert_eq!(builder.expected_len(), expected_len);
-
-        let builder = builder.relocatable_tag(RelocatableHeaderTag::new(
-            HeaderTagFlag::Required,
-            0x1337,
-            0xdeadbeef,
-            4096,
-            RelocatableHeaderTagPreference::None,
-        ));
-        expected_len += 0x18;
-        assert_eq!(builder.expected_len(), expected_len);
-
-        builder
-    }
-
-    #[test]
-    fn test_size_or_up_aligned() {
-        assert_eq!(0, HeaderBuilder::size_or_up_aligned(0));
-        assert_eq!(8, HeaderBuilder::size_or_up_aligned(1));
-        assert_eq!(8, HeaderBuilder::size_or_up_aligned(8));
-        assert_eq!(16, HeaderBuilder::size_or_up_aligned(9));
-    }
-
-    /// Test of the `build` method in isolation specifically for miri to check
-    /// for memory issues.
-    #[test]
-    fn test_builder_miri() {
-        let builder = create_builder();
-        let expected_len = builder.expected_len();
-        assert_eq!(builder.build().as_bytes().len(), expected_len);
-    }
-
-    #[test]
-    #[cfg_attr(miri, ignore)]
-    fn test_builder() {
-        // Step 1/2: Build Header
-        let mb2_hdr_data = create_builder().build();
-
-        // Step 2/2: Test the built Header
-        let mb2_hdr = mb2_hdr_data.as_ptr().cast();
-        let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr) }
-            .expect("the generated header to be loadable");
-        println!("{:#?}", mb2_hdr);
-        assert_eq!(
-            mb2_hdr.relocatable_tag().unwrap().flags(),
-            HeaderTagFlag::Required
-        );
-        assert_eq!(mb2_hdr.relocatable_tag().unwrap().min_addr(), 0x1337);
-        assert_eq!(mb2_hdr.relocatable_tag().unwrap().max_addr(), 0xdeadbeef);
-        assert_eq!(mb2_hdr.relocatable_tag().unwrap().align(), 4096);
-        assert_eq!(
-            mb2_hdr.relocatable_tag().unwrap().preference(),
-            RelocatableHeaderTagPreference::None
-        );
-
-        // Printing the header transitively ensures that a lot of stuff works.
-        println!("{:#?}", mb2_hdr);
-
-        /* you can write the binary to a file and a tool such as crate "bootinfo"
-           will be able to fully parse the MB2 header
-        let mut file = std::file::File::create("mb2_hdr.bin").unwrap();
-        use std::io::Write;
-        file.write_all(mb2_hdr_data.as_slice()).unwrap();*/
-    }
-}

+ 0 - 138
multiboot2-header/src/builder/information_request.rs

@@ -1,138 +0,0 @@
-use super::traits::StructAsBytes;
-use crate::{HeaderTagFlag, MbiTagType};
-use crate::{InformationRequestHeaderTag, MbiTagTypeId};
-use alloc::collections::BTreeSet;
-use alloc::vec::Vec;
-use core::fmt::Debug;
-use core::mem::size_of;
-use multiboot2::TagTypeId;
-
-/// Helper to build the dynamically sized [`InformationRequestHeaderTag`]
-/// at runtime. The information request tag has a dedicated builder because this way one
-/// can dynamically attach several requests to it. Otherwise, the number of requested tags
-/// must be known at compile time.
-#[derive(Clone, Debug, PartialEq, Eq)]
-#[cfg(feature = "builder")]
-pub struct InformationRequestHeaderTagBuilder {
-    flag: HeaderTagFlag,
-    // information requests (irs)
-    irs: BTreeSet<MbiTagType>,
-}
-
-#[cfg(feature = "builder")]
-impl InformationRequestHeaderTagBuilder {
-    /// New builder.
-    #[must_use]
-    pub const fn new(flag: HeaderTagFlag) -> Self {
-        Self {
-            irs: BTreeSet::new(),
-            flag,
-        }
-    }
-
-    /// Returns the expected length of the information request tag,
-    /// when the `build`-method gets called.
-    #[must_use]
-    pub fn expected_len(&self) -> usize {
-        let basic_header_size = size_of::<InformationRequestHeaderTag<0>>();
-        let req_tags_size = self.irs.len() * size_of::<MbiTagTypeId>();
-        basic_header_size + req_tags_size
-    }
-
-    /// Adds an [`MbiTagType`] to the information request.
-    #[must_use]
-    pub fn add_ir(mut self, tag: MbiTagType) -> Self {
-        self.irs.insert(tag);
-        self
-    }
-
-    /// Adds multiple [`MbiTagType`] to the information request.
-    #[must_use]
-    pub fn add_irs(mut self, tags: &[MbiTagType]) -> Self {
-        self.irs.extend(tags);
-        self
-    }
-
-    /// Builds the bytes of the dynamically sized information request header.
-    pub fn build(self) -> Vec<u8> {
-        let expected_len = self.expected_len();
-        let mut data = Vec::with_capacity(expected_len);
-
-        let basic_tag = InformationRequestHeaderTag::<0>::new(
-            self.flag,
-            [],
-            // we put the expected length here already, because in the next step we write
-            // all the tags into the byte array. We can't know this during compile time,
-            // therefore N is 0.
-            Some(expected_len as u32),
-        );
-        data.extend(basic_tag.struct_as_bytes());
-        #[cfg(debug_assertions)]
-        {
-            let basic_tag_size = size_of::<InformationRequestHeaderTag<0>>();
-            assert_eq!(
-                data.len(),
-                basic_tag_size,
-                "the vector must be as long as the basic tag!"
-            );
-        }
-
-        for tag_type in self
-            .irs
-            .into_iter()
-            // Transform to the ABI-compatible type
-            .map(TagTypeId::from)
-        {
-            let bytes: [u8; 4] = (u32::from(tag_type)).to_le_bytes();
-            data.extend(&bytes);
-        }
-
-        debug_assert_eq!(
-            data.len(),
-            expected_len,
-            "the byte vector must be as long as the expected size of the struct"
-        );
-
-        data
-    }
-}
-#[cfg(test)]
-mod tests {
-    use crate::builder::information_request::InformationRequestHeaderTagBuilder;
-    use crate::{HeaderTagFlag, InformationRequestHeaderTag, MbiTagType, MbiTagTypeId};
-
-    #[test]
-    #[cfg_attr(miri, ignore)]
-    fn test_builder() {
-        let builder = InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required)
-            .add_ir(MbiTagType::EfiMmap)
-            .add_ir(MbiTagType::BootLoaderName)
-            .add_ir(MbiTagType::Cmdline);
-        // type(u16) + flags(u16) + size(u32) + 3 tags (u32)
-        assert_eq!(builder.expected_len(), 2 + 2 + 4 + 3 * 4);
-        let tag = builder.build();
-        let tag = unsafe {
-            (tag.as_ptr() as *const InformationRequestHeaderTag<3>)
-                .as_ref()
-                .unwrap()
-        };
-        assert_eq!(tag.flags(), HeaderTagFlag::Required);
-        // type(u16) + flags(u16) + size(u32) + 3 tags (u32)
-        assert_eq!(tag.size(), 2 + 2 + 4 + 3 * 4);
-        assert_eq!(tag.dynamic_requests_size(), 3);
-        assert!(tag
-            .requests()
-            .contains(&MbiTagTypeId::from(MbiTagType::EfiMmap)));
-        assert!(tag
-            .requests()
-            .contains(&MbiTagTypeId::from(MbiTagType::BootLoaderName)));
-        assert!(tag
-            .requests()
-            .contains(&MbiTagTypeId::from(MbiTagType::Cmdline)));
-        assert_eq!(tag.requests().len(), 3);
-        assert!(!tag
-            .requests()
-            .contains(&MbiTagTypeId::from(MbiTagType::AcpiV1)));
-        println!("{:#?}", tag);
-    }
-}

+ 0 - 8
multiboot2-header/src/builder/mod.rs

@@ -1,8 +0,0 @@
-//! Module for the builder-feature.
-
-mod header;
-mod information_request;
-pub(crate) mod traits;
-
-pub use header::HeaderBuilder;
-pub use information_request::InformationRequestHeaderTagBuilder;

+ 0 - 75
multiboot2-header/src/builder/traits.rs

@@ -1,75 +0,0 @@
-//! Module for the helper trait [`StructAsBytes`].
-
-use crate::{
-    AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag,
-    EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag,
-    InformationRequestHeaderTag, ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag,
-};
-use alloc::vec::Vec;
-use core::mem::size_of;
-
-/// Trait for all tags that helps to create a byte array from the tag.
-/// Useful in builders to construct a byte vector that
-/// represents the Multiboot2 header with all its tags.
-pub trait StructAsBytes: Sized {
-    /// Returns the size in bytes of the struct, as known during compile
-    /// time. This doesn't use read the "size" field of tags.
-    fn byte_size(&self) -> usize {
-        size_of::<Self>()
-    }
-
-    /// Returns a byte pointer to the begin of the struct.
-    fn as_ptr(&self) -> *const u8 {
-        self as *const Self as *const u8
-    }
-
-    /// Returns the structure as a vector of its bytes.
-    /// The length is determined by [`Self::byte_size`].
-    fn struct_as_bytes(&self) -> Vec<u8> {
-        let ptr = self.as_ptr();
-        let bytes = unsafe { core::slice::from_raw_parts(ptr, self.byte_size()) };
-        Vec::from(bytes)
-    }
-}
-
-impl StructAsBytes for AddressHeaderTag {}
-impl StructAsBytes for ConsoleHeaderTag {}
-impl StructAsBytes for EndHeaderTag {}
-impl StructAsBytes for EntryEfi32HeaderTag {}
-impl StructAsBytes for EntryEfi64HeaderTag {}
-impl StructAsBytes for EntryAddressHeaderTag {}
-impl StructAsBytes for FramebufferHeaderTag {}
-impl StructAsBytes for InformationRequestHeaderTag<0> {}
-impl StructAsBytes for ModuleAlignHeaderTag {}
-impl StructAsBytes for RelocatableHeaderTag {}
-impl StructAsBytes for EfiBootServiceHeaderTag {}
-
-impl StructAsBytes for Multiboot2BasicHeader {}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[cfg_attr(miri, ignore)]
-    fn test_as_bytes() {
-        struct Foobar {
-            a: u32,
-            b: u8,
-            c: u128,
-        }
-        impl StructAsBytes for Foobar {}
-        #[allow(clippy::disallowed_names)]
-        let foo = Foobar {
-            a: 11,
-            b: 22,
-            c: 33,
-        };
-        let bytes = foo.struct_as_bytes();
-        let foo_from_bytes = unsafe { (bytes.as_ptr() as *const Foobar).as_ref().unwrap() };
-        assert_eq!(bytes.len(), size_of::<Foobar>());
-        assert_eq!(foo.a, foo_from_bytes.a);
-        assert_eq!(foo.b, foo_from_bytes.b);
-        assert_eq!(foo.c, foo_from_bytes.c);
-    }
-}

+ 14 - 10
multiboot2-header/src/console.rs

@@ -1,5 +1,6 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
-use core::mem::size_of;
+use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// Possible flags for [`ConsoleHeaderTag`].
 #[repr(u32)]
@@ -14,7 +15,7 @@ pub enum ConsoleHeaderTagFlags {
 /// Tells that a console must be available in MBI.
 /// Only relevant for legacy BIOS.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct ConsoleHeaderTag {
     header: HeaderTagHeader,
     console_flags: ConsoleHeaderTagFlags,
@@ -25,7 +26,7 @@ impl ConsoleHeaderTag {
     #[must_use]
     pub const fn new(flags: HeaderTagFlag, console_flags: ConsoleHeaderTagFlags) -> Self {
         let header =
-            HeaderTagHeader::new(HeaderTagType::ConsoleFlags, flags, size_of::<Self>() as u32);
+            HeaderTagHeader::new(HeaderTagType::ConsoleFlags, flags, Self::BASE_SIZE as u32);
         Self {
             header,
             console_flags,
@@ -57,12 +58,15 @@ impl ConsoleHeaderTag {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use crate::ConsoleHeaderTag;
+impl MaybeDynSized for ConsoleHeaderTag {
+    type Header = HeaderTagHeader;
 
-    #[test]
-    fn test_assert_size() {
-        assert_eq!(core::mem::size_of::<ConsoleHeaderTag>(), 2 + 2 + 4 + 4);
-    }
+    const BASE_SIZE: usize = mem::size_of::<HeaderTagHeader>() + mem::size_of::<u32>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for ConsoleHeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::ConsoleFlags;
 }

+ 16 - 2
multiboot2-header/src/end.rs

@@ -1,5 +1,6 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
-use core::mem::size_of;
+use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// Terminates a list of optional tags in a Multiboot2 header.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -21,7 +22,7 @@ impl EndHeaderTag {
         let header = HeaderTagHeader::new(
             HeaderTagType::EntryAddress,
             HeaderTagFlag::Required,
-            size_of::<Self>() as u32,
+            mem::size_of::<Self>() as u32,
         );
         Self { header }
     }
@@ -45,6 +46,19 @@ impl EndHeaderTag {
     }
 }
 
+impl MaybeDynSized for EndHeaderTag {
+    type Header = HeaderTagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<Self>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for EndHeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::End;
+}
+
 #[cfg(test)]
 mod tests {
     use crate::EndHeaderTag;

+ 14 - 10
multiboot2-header/src/entry_address.rs

@@ -1,12 +1,13 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
 use core::fmt;
 use core::fmt::{Debug, Formatter};
-use core::mem::size_of;
+use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// Specifies the physical address to which the boot loader should jump in
 /// order to start running the operating system. Not needed for ELF files.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct EntryAddressHeaderTag {
     header: HeaderTagHeader,
     entry_addr: u32,
@@ -17,7 +18,7 @@ impl EntryAddressHeaderTag {
     #[must_use]
     pub const fn new(flags: HeaderTagFlag, entry_addr: u32) -> Self {
         let header =
-            HeaderTagHeader::new(HeaderTagType::EntryAddress, flags, size_of::<Self>() as u32);
+            HeaderTagHeader::new(HeaderTagType::EntryAddress, flags, Self::BASE_SIZE as u32);
         Self { header, entry_addr }
     }
 
@@ -57,12 +58,15 @@ impl Debug for EntryAddressHeaderTag {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use crate::EntryAddressHeaderTag;
+impl MaybeDynSized for EntryAddressHeaderTag {
+    type Header = HeaderTagHeader;
 
-    #[test]
-    fn test_assert_size() {
-        assert_eq!(core::mem::size_of::<EntryAddressHeaderTag>(), 2 + 2 + 4 + 4);
-    }
+    const BASE_SIZE: usize = mem::size_of::<HeaderTagHeader>() + mem::size_of::<u32>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for EntryAddressHeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::EntryAddress;
 }

+ 14 - 10
multiboot2-header/src/entry_efi_32.rs

@@ -1,7 +1,8 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
 use core::fmt;
 use core::fmt::{Debug, Formatter};
-use core::mem::size_of;
+use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// This tag is taken into account only on EFI i386 platforms when Multiboot2 image header
 /// contains EFI boot services tag. Then entry point specified in ELF header and the entry address
@@ -10,7 +11,7 @@ use core::mem::size_of;
 /// Technically, this is equivalent to the [`crate::EntryAddressHeaderTag`] but with a different
 /// [`crate::HeaderTagType`].
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct EntryEfi32HeaderTag {
     header: HeaderTagHeader,
     entry_addr: u32,
@@ -23,7 +24,7 @@ impl EntryEfi32HeaderTag {
         let header = HeaderTagHeader::new(
             HeaderTagType::EntryAddressEFI32,
             flags,
-            size_of::<Self>() as u32,
+            Self::BASE_SIZE as u32,
         );
         Self { header, entry_addr }
     }
@@ -64,12 +65,15 @@ impl Debug for EntryEfi32HeaderTag {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use crate::EntryEfi32HeaderTag;
+impl MaybeDynSized for EntryEfi32HeaderTag {
+    type Header = HeaderTagHeader;
 
-    #[test]
-    fn test_assert_size() {
-        assert_eq!(core::mem::size_of::<EntryEfi32HeaderTag>(), 2 + 2 + 4 + 4);
-    }
+    const BASE_SIZE: usize = mem::size_of::<HeaderTagHeader>() + mem::size_of::<u32>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for EntryEfi32HeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::EntryAddressEFI32;
 }

+ 14 - 10
multiboot2-header/src/entry_efi_64.rs

@@ -1,7 +1,8 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
 use core::fmt;
 use core::fmt::{Debug, Formatter};
-use core::mem::size_of;
+use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// This tag is taken into account only on EFI amd64 platforms when Multiboot2 image header
 /// contains EFI boot services tag. Then entry point specified in ELF header and the entry address
@@ -10,7 +11,7 @@ use core::mem::size_of;
 /// Technically, this is equivalent to the [`crate::EntryAddressHeaderTag`] but with a different
 /// [`crate::HeaderTagType`].
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct EntryEfi64HeaderTag {
     header: HeaderTagHeader,
     entry_addr: u32,
@@ -23,7 +24,7 @@ impl EntryEfi64HeaderTag {
         let header = HeaderTagHeader::new(
             HeaderTagType::EntryAddressEFI64,
             flags,
-            size_of::<Self>() as u32,
+            Self::BASE_SIZE as u32,
         );
         Self { header, entry_addr }
     }
@@ -64,12 +65,15 @@ impl Debug for EntryEfi64HeaderTag {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use crate::EntryEfi64HeaderTag;
+impl MaybeDynSized for EntryEfi64HeaderTag {
+    type Header = HeaderTagHeader;
 
-    #[test]
-    fn test_assert_size() {
-        assert_eq!(core::mem::size_of::<EntryEfi64HeaderTag>(), 2 + 2 + 4 + 4);
-    }
+    const BASE_SIZE: usize = mem::size_of::<HeaderTagHeader>() + mem::size_of::<u32>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for EntryEfi64HeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::EntryAddressEFI64;
 }

+ 14 - 16
multiboot2-header/src/framebuffer.rs

@@ -1,12 +1,13 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
 use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// Specifies the preferred graphics mode. If this tag
 /// is present the bootloader assumes that the payload
 /// has framebuffer support. Note: This is only a
 /// recommended mode. Only relevant on legacy BIOS.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct FramebufferHeaderTag {
     header: HeaderTagHeader,
     width: u32,
@@ -18,11 +19,8 @@ impl FramebufferHeaderTag {
     /// Constructs a new tag.
     #[must_use]
     pub const fn new(flags: HeaderTagFlag, width: u32, height: u32, depth: u32) -> Self {
-        let header = HeaderTagHeader::new(
-            HeaderTagType::Framebuffer,
-            flags,
-            mem::size_of::<Self>() as u32,
-        );
+        let header =
+            HeaderTagHeader::new(HeaderTagType::Framebuffer, flags, Self::BASE_SIZE as u32);
         Self {
             header,
             width,
@@ -68,15 +66,15 @@ impl FramebufferHeaderTag {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use super::*;
+impl MaybeDynSized for FramebufferHeaderTag {
+    type Header = HeaderTagHeader;
 
-    #[test]
-    fn test_assert_size() {
-        assert_eq!(
-            mem::size_of::<FramebufferHeaderTag>(),
-            2 + 2 + 4 + 4 + 4 + 4
-        );
-    }
+    const BASE_SIZE: usize = mem::size_of::<HeaderTagHeader>() + 3 * mem::size_of::<u32>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for FramebufferHeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::Framebuffer;
 }

+ 101 - 234
multiboot2-header/src/header.rs

@@ -1,11 +1,15 @@
 use crate::{
-    AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag,
-    EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag,
-    HeaderTagHeader, HeaderTagISA, HeaderTagType, InformationRequestHeaderTag,
-    ModuleAlignHeaderTag, RelocatableHeaderTag,
+    AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EntryAddressHeaderTag,
+    EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, HeaderTagHeader, HeaderTagISA,
+    HeaderTagType, InformationRequestHeaderTag, ModuleAlignHeaderTag, RelocatableHeaderTag,
+    TagIter,
 };
+#[cfg(feature = "unstable")]
+use core::error::Error;
 use core::fmt::{Debug, Formatter};
 use core::mem::size_of;
+use core::ptr::NonNull;
+use multiboot2_common::{DynSizedStructure, Header, MemoryError, Tag, ALIGNMENT};
 
 /// Magic value for a [`Multiboot2Header`], as defined by the spec.
 pub const MAGIC: u32 = 0xe85250d6;
@@ -16,9 +20,8 @@ pub const MAGIC: u32 = 0xe85250d6;
 /// Use this if you get a pointer to the header and just want
 /// to parse it. If you want to construct the type by yourself,
 /// please look at `HeaderBuilder` (requires the `builder` feature).
-#[derive(Debug)]
 #[repr(transparent)]
-pub struct Multiboot2Header<'a>(&'a Multiboot2BasicHeader);
+pub struct Multiboot2Header<'a>(&'a DynSizedStructure<Multiboot2BasicHeader>);
 
 impl<'a> Multiboot2Header<'a> {
     /// Public constructor for this type with various validations.
@@ -34,22 +37,18 @@ impl<'a> Multiboot2Header<'a> {
     /// This function may produce undefined behaviour, if the provided `addr` is not a valid
     /// Multiboot2 header pointer.
     pub unsafe fn load(ptr: *const Multiboot2BasicHeader) -> Result<Self, LoadError> {
-        // null or not aligned
-        if ptr.is_null() || ptr.align_offset(8) != 0 {
-            return Err(LoadError::InvalidAddress);
-        }
-
-        let reference = &*ptr;
+        let ptr = NonNull::new(ptr.cast_mut()).ok_or(LoadError::Memory(MemoryError::Null))?;
+        let inner = DynSizedStructure::ref_from_ptr(ptr).map_err(LoadError::Memory)?;
+        let this = Self(inner);
 
-        if reference.header_magic() != MAGIC {
+        let header = this.0.header();
+        if header.header_magic != MAGIC {
             return Err(LoadError::MagicNotFound);
         }
-
-        if !reference.verify_checksum() {
+        if !header.verify_checksum() {
             return Err(LoadError::ChecksumMismatch);
         }
-
-        Ok(Self(reference))
+        Ok(this)
     }
 
     /// Find the header in a given slice.
@@ -60,8 +59,8 @@ impl<'a> Multiboot2Header<'a> {
     /// or because it is truncated), it returns a [`LoadError`].
     /// If there is no header, it returns `None`.
     pub fn find_header(buffer: &[u8]) -> Result<Option<(&[u8], u32)>, LoadError> {
-        if buffer.as_ptr().align_offset(4) != 0 {
-            return Err(LoadError::InvalidAddress);
+        if buffer.as_ptr().align_offset(ALIGNMENT) != 0 {
+            return Err(LoadError::Memory(MemoryError::WrongAlignment));
         }
 
         let mut windows = buffer[0..8192].windows(4);
@@ -73,7 +72,7 @@ impl<'a> Multiboot2Header<'a> {
                 if idx % 8 == 0 {
                     idx
                 } else {
-                    return Err(LoadError::InvalidAddress);
+                    return Err(LoadError::Memory(MemoryError::WrongAlignment));
                 }
             }
             None => return Ok(None),
@@ -90,7 +89,7 @@ impl<'a> Multiboot2Header<'a> {
         let header_length: usize = u32::from_le_bytes(
             windows
                 .next()
-                .ok_or(LoadError::TooSmall)?
+                .ok_or(LoadError::Memory(MemoryError::MissingPadding))?
                 .try_into()
                 .unwrap(), // 4 bytes are a u32
         )
@@ -102,35 +101,36 @@ impl<'a> Multiboot2Header<'a> {
         )))
     }
 
+    /// Returns a [`TagIter`].
+    #[must_use]
+    pub fn iter(&self) -> TagIter {
+        TagIter::new(self.0.payload())
+    }
+
     /// Wrapper around [`Multiboot2BasicHeader::verify_checksum`].
     #[must_use]
     pub const fn verify_checksum(&self) -> bool {
-        self.0.verify_checksum()
+        self.0.header().verify_checksum()
     }
     /// Wrapper around [`Multiboot2BasicHeader::header_magic`].
     #[must_use]
     pub const fn header_magic(&self) -> u32 {
-        self.0.header_magic()
+        self.0.header().header_magic()
     }
     /// Wrapper around [`Multiboot2BasicHeader::arch`].
     #[must_use]
     pub const fn arch(&self) -> HeaderTagISA {
-        self.0.arch()
+        self.0.header().arch()
     }
     /// Wrapper around [`Multiboot2BasicHeader::length`].
     #[must_use]
     pub const fn length(&self) -> u32 {
-        self.0.length()
+        self.0.header().length()
     }
     /// Wrapper around [`Multiboot2BasicHeader::checksum`].
     #[must_use]
     pub const fn checksum(&self) -> u32 {
-        self.0.checksum()
-    }
-    /// Wrapper around [`Multiboot2BasicHeader::tag_iter`].
-    #[must_use]
-    pub fn iter(&self) -> Multiboot2HeaderTagIter {
-        self.0.tag_iter()
+        self.0.header().checksum()
     }
     /// Wrapper around [`Multiboot2BasicHeader::calc_checksum`].
     #[must_use]
@@ -138,98 +138,118 @@ impl<'a> Multiboot2Header<'a> {
         Multiboot2BasicHeader::calc_checksum(magic, arch, length)
     }
 
-    /// Search for the address header tag.
+    /// Search for the [`InformationRequestHeaderTag`] header tag.
+    #[must_use]
+    pub fn information_request_tag(&self) -> Option<&InformationRequestHeaderTag> {
+        self.get_tag()
+    }
+
+    /// Search for the [`AddressHeaderTag`] header tag.
     #[must_use]
     pub fn address_tag(&self) -> Option<&AddressHeaderTag> {
-        self.get_tag(HeaderTagType::Address)
-            .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const AddressHeaderTag) })
+        self.get_tag()
     }
 
-    /// Search for the entry address header tag.
+    /// Search for the [`EntryAddressHeaderTag`] header tag.
     #[must_use]
     pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> {
-        self.get_tag(HeaderTagType::EntryAddress)
-            .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const EntryAddressHeaderTag) })
+        self.get_tag()
     }
 
-    /// Search for the EFI32 entry address header tag.
+    /// Search for the [`EntryEfi32HeaderTag`] header tag.
     #[must_use]
     pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> {
-        self.get_tag(HeaderTagType::EntryAddressEFI32)
-            .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const EntryEfi32HeaderTag) })
+        self.get_tag()
     }
 
-    /// Search for the EFI64 entry address header tag.
+    /// Search for the [`EntryEfi64HeaderTag`] header tag.
     #[must_use]
     pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> {
-        self.get_tag(HeaderTagType::EntryAddressEFI64)
-            .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const EntryEfi64HeaderTag) })
+        self.get_tag()
     }
 
-    /// Search for the console flags header tag.
+    /// Search for the [`ConsoleHeaderTag`] header tag.
     #[must_use]
     pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> {
-        self.get_tag(HeaderTagType::ConsoleFlags)
-            .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const ConsoleHeaderTag) })
+        self.get_tag()
     }
 
-    /// Search for the framebuffer header tag.
+    /// Search for the [`FramebufferHeaderTag`] header tag.
     #[must_use]
     pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> {
-        self.get_tag(HeaderTagType::Framebuffer)
-            .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const FramebufferHeaderTag) })
+        self.get_tag()
     }
 
-    /// Search for the module align header tag.
+    /// Search for the [`ModuleAlignHeaderTag`] header tag.
     #[must_use]
     pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> {
-        self.get_tag(HeaderTagType::ModuleAlign)
-            .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const ModuleAlignHeaderTag) })
+        self.get_tag()
     }
 
-    /// Search for the EFI Boot Services header tag.
+    /// Search for the [`EfiBootServiceHeaderTag`] header tag.
     #[must_use]
     pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> {
-        self.get_tag(HeaderTagType::EfiBS).map(|tag| unsafe {
-            &*(tag as *const HeaderTagHeader as *const EfiBootServiceHeaderTag)
-        })
+        self.get_tag()
     }
 
-    /// Search for the EFI32 entry address header tag.
+    /// Search for the [`RelocatableHeaderTag`] header tag.
     #[must_use]
     pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> {
-        self.get_tag(HeaderTagType::Relocatable)
-            .map(|tag| unsafe { &*(tag as *const HeaderTagHeader as *const RelocatableHeaderTag) })
+        self.get_tag()
     }
 
-    fn get_tag(&self, typ: HeaderTagType) -> Option<&HeaderTagHeader> {
+    /// Searches for the specified tag by iterating the structure and returns
+    /// the first occurrence, if present.
+    #[must_use]
+    fn get_tag<T: Tag<IDType = HeaderTagType, Header = HeaderTagHeader> + ?Sized + 'a>(
+        &'a self,
+    ) -> Option<&'a T> {
         self.iter()
-            .map(|tag| unsafe { tag.as_ref() }.unwrap())
-            .find(|tag| tag.typ() == typ)
+            .find(|tag| tag.header().typ() == T::ID)
+            .map(|tag| tag.cast::<T>())
+    }
+}
+
+impl Debug for Multiboot2Header<'_> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+        f.debug_struct("Multiboot2Header")
+            .field("magic", &self.header_magic())
+            .field("arch", &self.arch())
+            .field("length", &self.length())
+            .field("checksum", &self.checksum())
+            // TODO better debug impl
+            .field("tags", &"<tags iter>")
+            .finish()
     }
 }
 
-/// Errors that can occur when parsing a header from a slice.
-/// See [`Multiboot2Header::find_header`].
+/// Errors that occur when a chunk of memory can't be parsed as
+/// [`Multiboot2Header`].
 #[derive(Copy, Clone, Debug, derive_more::Display, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum LoadError {
-    /// The checksum does not match the data.
+    /// The provided checksum does not match the expected value.
     ChecksumMismatch,
-    /// The header is not properly 64-bit aligned (or a null pointer).
-    InvalidAddress,
     /// The header does not contain the correct magic number.
     MagicNotFound,
-    /// The header is truncated.
-    TooSmall,
+    /// The provided memory can't be parsed as [`Multiboot2Header`].
+    /// See [`MemoryError`].
+    Memory(MemoryError),
 }
 
 #[cfg(feature = "unstable")]
-impl core::error::Error for LoadError {}
+impl Error for LoadError {
+    fn source(&self) -> Option<&(dyn Error + 'static)> {
+        match self {
+            Self::Memory(inner) => Some(inner),
+            _ => None,
+        }
+    }
+}
 
 /// The "basic" Multiboot2 header. This means only the properties, that are known during
 /// compile time. All other information are derived during runtime from the size property.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct Multiboot2BasicHeader {
     /// Must be the value of [`MAGIC`].
     header_magic: u32,
@@ -290,28 +310,16 @@ impl Multiboot2BasicHeader {
     pub const fn checksum(&self) -> u32 {
         self.checksum
     }
+}
+
+impl Header for Multiboot2BasicHeader {
+    fn payload_len(&self) -> usize {
+        self.length as usize - size_of::<Self>()
+    }
 
-    /// Returns a [`Multiboot2HeaderTagIter`].
-    ///
-    /// # Panics
-    /// See doc of [`Multiboot2HeaderTagIter`].
-    #[must_use]
-    pub fn tag_iter(&self) -> Multiboot2HeaderTagIter {
-        let base_hdr_size = size_of::<Self>();
-        if base_hdr_size == self.length as usize {
-            panic!("No end tag!");
-        }
-        let tag_base_addr = self as *const Self;
-        // cast to u8 so that the offset in bytes works correctly
-        let tag_base_addr = tag_base_addr as *const u8;
-        // tag_base_addr should now point behind the "static" members
-        let tag_base_addr = unsafe { tag_base_addr.add(base_hdr_size) };
-        // align pointer to 8 byte according to spec
-        let tag_base_addr = unsafe { tag_base_addr.add(tag_base_addr.align_offset(8)) };
-        // cast back
-        let tag_base_addr = tag_base_addr as *const HeaderTagHeader;
-        let tags_len = self.length as usize - base_hdr_size;
-        Multiboot2HeaderTagIter::new(tag_base_addr, tags_len as u32)
+    fn set_size(&mut self, total_size: usize) {
+        self.length = total_size as u32;
+        self.checksum = Self::calc_checksum(self.header_magic, self.arch, total_size as u32);
     }
 }
 
@@ -322,152 +330,11 @@ impl Debug for Multiboot2BasicHeader {
             .field("arch", &{ self.arch })
             .field("length", &{ self.length })
             .field("checksum", &{ self.checksum })
-            .field("tags", &self.tag_iter())
+            //.field("tags", &self.iter())
             .finish()
     }
 }
 
-/// Iterator over all tags of a Multiboot2 header. The number of items is derived
-/// by the size/length of the header.
-///
-/// # Panics
-/// Panics if the `length`-attribute doesn't match the number of found tags, there are
-/// more tags found than technically possible, or if there is more than one end tag.
-/// All of these errors come from bigger, underlying problems. Therefore, they are
-/// considered as "abort/panic" and not as recoverable errors.
-#[derive(Clone)]
-pub struct Multiboot2HeaderTagIter {
-    /// 8-byte aligned base address
-    base: *const HeaderTagHeader,
-    /// Offset in bytes from the base address.
-    /// Always <= than size.
-    n: u32,
-    /// Size / final value of [`Self::n`].
-    size: u32,
-    /// Counts the number of found tags. If more tags are found
-    /// than technically possible, for example because the length property
-    /// was invalid and there are hundreds of "End"-tags, we can use
-    /// this and enforce a hard iteration limit.
-    tag_count: u32,
-    /// Marks if the end-tag was found. Together with `tag_count`, this
-    /// further helps to improve safety when invalid length properties are given.
-    end_tag_found: bool,
-}
-
-impl Multiboot2HeaderTagIter {
-    fn new(base: *const HeaderTagHeader, size: u32) -> Self {
-        // transform to byte pointer => offset works properly
-        let base = base as *const u8;
-        let base = unsafe { base.add(base.align_offset(8)) };
-        let base = base as *const HeaderTagHeader;
-        Self {
-            base,
-            n: 0,
-            size,
-            tag_count: 0,
-            end_tag_found: false,
-        }
-    }
-}
-
-impl Iterator for Multiboot2HeaderTagIter {
-    type Item = *const HeaderTagHeader;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        // no more bytes left to check; length reached
-        if self.n >= self.size {
-            return None;
-        }
-
-        // transform to byte ptr => offset works correctly
-        let ptr = self.base as *const u8;
-        let ptr = unsafe { ptr.add(self.n as usize) };
-        let ptr = ptr as *const HeaderTagHeader;
-        assert_eq!(ptr as usize % 8, 0, "must be 8-byte aligned");
-        let tag = unsafe { &*ptr };
-        assert!(
-            tag.size() <= 500,
-            "no real mb2 header should be bigger than 500bytes - probably wrong memory?! is: {}",
-            { tag.size() }
-        );
-        assert!(
-            tag.size() >= 8,
-            "no real mb2 header tag is smaller than 8 bytes - probably wrong memory?! is: {}",
-            { tag.size() }
-        );
-        assert!(
-            !self.end_tag_found,
-            "There is more than one end tag! Maybe the `length` property is invalid?"
-        );
-        self.n += tag.size();
-        // 8-byte alignment of pointer address
-        self.n += self.n % 8;
-        self.tag_count += 1;
-        if tag.typ() == HeaderTagType::End {
-            self.end_tag_found = true;
-        }
-        assert!(self.tag_count < HeaderTagType::count(), "Invalid Multiboot2 header tags! There are more tags than technically possible! Maybe the `length` property is invalid?");
-        Some(ptr)
-    }
-}
-
-impl Debug for Multiboot2HeaderTagIter {
-    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
-        let mut debug = f.debug_list();
-        self.clone().for_each(|t| unsafe {
-            let typ = (*t).typ();
-            if typ == HeaderTagType::End {
-                let entry = t as *const EndHeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::InformationRequest {
-                let entry = t as *const InformationRequestHeaderTag<0>;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::Address {
-                let entry = t as *const AddressHeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::EntryAddress {
-                let entry = t as *const EntryAddressHeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::ConsoleFlags {
-                let entry = t as *const ConsoleHeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::Framebuffer {
-                let entry = t as *const FramebufferHeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::EfiBS {
-                let entry = t as *const EfiBootServiceHeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::EntryAddressEFI32 {
-                let entry = t as *const EntryEfi32HeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::EntryAddressEFI64 {
-                let entry = t as *const EntryEfi64HeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::ModuleAlign {
-                let entry = t as *const ModuleAlignHeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else if typ == HeaderTagType::Relocatable {
-                let entry = t as *const RelocatableHeaderTag;
-                let entry = &*(entry);
-                debug.entry(entry);
-            } else {
-                panic!("unknown tag ({:?})!", typ);
-            }
-        });
-        debug.finish()
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use crate::Multiboot2BasicHeader;

+ 70 - 110
multiboot2-header/src/information_request.rs

@@ -1,34 +1,37 @@
-use crate::{HeaderTagFlag, HeaderTagHeader, MbiTagType};
+use crate::{HeaderTagFlag, HeaderTagHeader};
 use crate::{HeaderTagType, MbiTagTypeId};
 use core::fmt;
 use core::fmt::{Debug, Formatter};
-use core::marker::PhantomData;
-use core::mem::size_of;
-use multiboot2::TagType;
+use core::mem;
+#[cfg(feature = "builder")]
+use multiboot2_common::new_boxed;
+use multiboot2_common::{MaybeDynSized, Tag};
+#[cfg(feature = "builder")]
+use {
+    alloc::boxed::Box,
+    core::{ptr, slice},
+};
 
 /// Specifies what specific tag types the bootloader should provide
 /// inside the mbi.
-#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
-pub struct InformationRequestHeaderTag<const N: usize> {
+#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, ptr_meta::Pointee)]
+#[repr(C, align(8))]
+pub struct InformationRequestHeaderTag {
     header: HeaderTagHeader,
-    // Length is determined by size.
-    // Must be parsed during runtime with unsafe pointer magic and the size field.
-    requests: [MbiTagTypeId; N],
+    requests: [MbiTagTypeId],
 }
 
-impl<const N: usize> InformationRequestHeaderTag<N> {
-    /// Creates a new object. The size parameter is the value of the size property.
-    /// It doesn't have to match with `N` necessarily, because during compile time we
-    /// can't know the size of the tag in all runtime situations.
+impl InformationRequestHeaderTag {
+    /// Creates a new object.
+    #[cfg(feature = "builder")]
     #[must_use]
-    pub fn new(flags: HeaderTagFlag, requests: [MbiTagTypeId; N], size: Option<u32>) -> Self {
-        let header = HeaderTagHeader::new(
-            HeaderTagType::InformationRequest,
-            flags,
-            size.unwrap_or(size_of::<Self>() as u32),
-        );
-        Self { header, requests }
+    pub fn new(flags: HeaderTagFlag, requests: &[MbiTagTypeId]) -> Box<Self> {
+        let header = HeaderTagHeader::new(HeaderTagType::InformationRequest, flags, 0);
+        let requests = unsafe {
+            let ptr = ptr::addr_of!(*requests);
+            slice::from_raw_parts(ptr.cast::<u8>(), mem::size_of_val(requests))
+        };
+        new_boxed(header, &[requests])
     }
 
     /// Returns the [`HeaderTagType`].
@@ -49,119 +52,76 @@ impl<const N: usize> InformationRequestHeaderTag<N> {
         self.header.size()
     }
 
-    /// Returns the requests as array. Only works if the number of requests
-    /// is known at compile time. For safety and correctness during runtime,
-    /// you should use `req_iter()`.
-    #[must_use]
-    pub const fn requests(&self) -> [MbiTagTypeId; N] {
-        // cheap to copy, otherwise difficult with lifetime
-        self.requests
-    }
-
-    /// Returns the number of [`MbiTagType`]-requests derived
-    /// from the `size`-property. This method is useful
-    /// because this struct uses a const generic, but during runtime
-    /// we don't know the value in almost any case.
+    /// Returns the requests as array
     #[must_use]
-    pub const fn dynamic_requests_size(&self) -> u32 {
-        let base_struct_size = size_of::<InformationRequestHeaderTag<0>>();
-        let size_diff = self.size() - base_struct_size as u32;
-        if size_diff > 0 {
-            size_diff / size_of::<u32>() as u32
-        } else {
-            0
-        }
-    }
-
-    /// Returns an [`InformationRequestHeaderTagIter`].
-    #[must_use]
-    pub const fn req_iter(&self) -> InformationRequestHeaderTagIter {
-        let base_struct_size = size_of::<InformationRequestHeaderTag<0>>();
-        let count = self.dynamic_requests_size();
-        let base_ptr = self as *const Self;
-        let base_ptr = base_ptr as *const u8;
-        let base_ptr = unsafe { base_ptr.add(base_struct_size) };
-        let base_ptr = base_ptr as *const MbiTagTypeId;
-        InformationRequestHeaderTagIter::new(count, base_ptr)
+    pub const fn requests(&self) -> &[MbiTagTypeId] {
+        &self.requests
     }
 }
 
-impl<const N: usize> Debug for InformationRequestHeaderTag<N> {
+impl Debug for InformationRequestHeaderTag {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
         f.debug_struct("InformationRequestHeaderTag")
             .field("type", &self.typ())
             .field("flags", &self.flags())
             .field("size", &self.size())
-            .field("requests", &self.req_iter())
+            .field("requests", &self.requests())
             .finish()
     }
 }
 
-/// Iterates the dynamically sized information request structure and finds all MBI tags
-/// that are requested.
-#[derive(Copy, Clone)]
-pub struct InformationRequestHeaderTagIter<'a> {
-    base_ptr: *const MbiTagTypeId,
-    i: u32,
-    count: u32,
-    _marker: PhantomData<&'a ()>,
-}
+impl MaybeDynSized for InformationRequestHeaderTag {
+    type Header = HeaderTagHeader;
 
-impl<'a> InformationRequestHeaderTagIter<'a> {
-    const fn new(count: u32, base_ptr: *const MbiTagTypeId) -> Self {
-        Self {
-            i: 0,
-            count,
-            base_ptr,
-            _marker: PhantomData,
-        }
-    }
-}
+    const BASE_SIZE: usize = mem::size_of::<HeaderTagHeader>();
 
-impl<'a> Iterator for InformationRequestHeaderTagIter<'a> {
-    type Item = MbiTagType;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.i < self.count {
-            let ptr = unsafe { self.base_ptr.offset(self.i as isize) };
-            self.i += 1;
-            let tag_type_id = unsafe { *ptr };
-            Some(TagType::from(tag_type_id))
-        } else {
-            None
-        }
+    fn dst_len(header: &Self::Header) -> Self::Metadata {
+        let dst_size = header.size() as usize - Self::BASE_SIZE;
+        assert_eq!(dst_size % mem::size_of::<MbiTagTypeId>(), 0);
+        dst_size / mem::size_of::<MbiTagTypeId>()
     }
 }
 
-impl<'a> Debug for InformationRequestHeaderTagIter<'a> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        let mut debug = f.debug_list();
-        self.for_each(|e| {
-            debug.entry(&e);
-        });
-        debug.finish()
-    }
+impl Tag for InformationRequestHeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::InformationRequest;
 }
 
 #[cfg(test)]
+#[cfg(feature = "builder")]
 mod tests {
-    use crate::InformationRequestHeaderTag;
+    use super::*;
+    use crate::MbiTagType;
 
     #[test]
-    #[allow(clippy::erasing_op)]
-    #[allow(clippy::identity_op)]
-    fn test_assert_size() {
-        assert_eq!(
-            core::mem::size_of::<InformationRequestHeaderTag<0>>(),
-            2 + 2 + 4 + 0 * 4
-        );
-        assert_eq!(
-            core::mem::size_of::<InformationRequestHeaderTag<1>>(),
-            2 + 2 + 4 + 1 * 4
-        );
-        assert_eq!(
-            core::mem::size_of::<InformationRequestHeaderTag<2>>(),
-            2 + 2 + 4 + 2 * 4
+    fn creation() {
+        // Main objective here is to satisfy Miri.
+        let _ir = InformationRequestHeaderTag::new(
+            HeaderTagFlag::Optional,
+            &[
+                MbiTagType::Cmdline.into(),
+                MbiTagType::BootLoaderName.into(),
+                MbiTagType::Module.into(),
+                MbiTagType::BasicMeminfo.into(),
+                MbiTagType::Bootdev.into(),
+                MbiTagType::Mmap.into(),
+                MbiTagType::Vbe.into(),
+                MbiTagType::Framebuffer.into(),
+                MbiTagType::ElfSections.into(),
+                MbiTagType::Apm.into(),
+                MbiTagType::Efi32.into(),
+                MbiTagType::Efi64.into(),
+                MbiTagType::Smbios.into(),
+                MbiTagType::AcpiV1.into(),
+                MbiTagType::AcpiV2.into(),
+                MbiTagType::Network.into(),
+                MbiTagType::EfiMmap.into(),
+                MbiTagType::EfiBs.into(),
+                MbiTagType::Efi32Ih.into(),
+                MbiTagType::Efi64Ih.into(),
+                MbiTagType::LoadBaseAddr.into(),
+                MbiTagType::Custom(0x1337).into(),
+            ],
         );
     }
 }

+ 20 - 27
multiboot2-header/src/lib.rs

@@ -2,34 +2,16 @@
 //! headers, as well as a builder to build them at runtime. This library is
 //! `no_std` and can be used in bootloaders.
 //!
-//! # Example
+//! # Example: Parsing a Header
 //!
-//! ```rust
-//! use multiboot2_header::builder::{InformationRequestHeaderTagBuilder, HeaderBuilder};
-//! use multiboot2_header::{HeaderTagFlag, HeaderTagISA, MbiTagType, RelocatableHeaderTag, RelocatableHeaderTagPreference, Multiboot2Header};
-//!
-//! // Small example that creates a Multiboot2 header and parses it afterwards.
-//!
-//! // We create a Multiboot2 header during runtime here. A practical example is that your
-//! // program gets the header from a file and parses it afterwards.
-//! let mb2_hdr_bytes = HeaderBuilder::new(HeaderTagISA::I386)
-//!     .relocatable_tag(RelocatableHeaderTag::new(
-//!         HeaderTagFlag::Required,
-//!         0x1337,
-//!         0xdeadbeef,
-//!         4096,
-//!         RelocatableHeaderTagPreference::None,
-//!     ))
-//!     .information_request_tag(
-//!         InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required)
-//!             .add_irs(&[MbiTagType::Cmdline, MbiTagType::BootLoaderName]),
-//!     )
-//!     .build();
-//!
-//! // Cast bytes in vector to Multiboot2 information structure
-//! let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr_bytes.as_ptr().cast()) };
-//! println!("{:#?}", mb2_hdr);
+//! ```no_run
+//! use multiboot2_header::Multiboot2Header;
 //!
+//! let ptr = 0x1337_0000 as *const u8 /* use real ptr here */;
+//! let mb2_hdr = unsafe { Multiboot2Header::load(ptr.cast()) }.unwrap();
+//! for _tag in mb2_hdr.iter() {
+//!     //
+//! }
 //! ```
 //!
 //! ## MSRV
@@ -62,6 +44,13 @@ extern crate alloc;
 #[cfg(test)]
 extern crate std;
 
+/// Iterator over the tags of a Multiboot2 boot information.
+pub type TagIter<'a> = multiboot2_common::TagIter<'a, HeaderTagHeader>;
+
+/// A generic version of all boot information tags.
+#[cfg(test)]
+pub type GenericHeaderTag = multiboot2_common::DynSizedStructure<HeaderTagHeader>;
+
 mod address;
 mod console;
 mod end;
@@ -77,7 +66,9 @@ mod tags;
 mod uefi_bs;
 
 #[cfg(feature = "builder")]
-pub mod builder;
+mod builder;
+
+pub use multiboot2_common::{DynSizedStructure, MaybeDynSized, Tag};
 
 pub use self::address::*;
 pub use self::console::*;
@@ -92,6 +83,8 @@ pub use self::module_align::*;
 pub use self::relocatable::*;
 pub use self::tags::*;
 pub use self::uefi_bs::*;
+#[cfg(feature = "builder")]
+pub use builder::Builder;
 
 /// Re-export of [`multiboot2::TagType`] from `multiboot2`-crate.
 pub use multiboot2::{TagType as MbiTagType, TagTypeId as MbiTagTypeId};

+ 21 - 4
multiboot2-header/src/module_align.rs

@@ -1,9 +1,10 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
-use core::mem::size_of;
+use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// If this tag is present, provided boot modules must be page aligned.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct ModuleAlignHeaderTag {
     header: HeaderTagHeader,
 }
@@ -12,8 +13,11 @@ impl ModuleAlignHeaderTag {
     /// Constructs a new tag.
     #[must_use]
     pub const fn new(flags: HeaderTagFlag) -> Self {
-        let header =
-            HeaderTagHeader::new(HeaderTagType::ModuleAlign, flags, size_of::<Self>() as u32);
+        let header = HeaderTagHeader::new(
+            HeaderTagType::ModuleAlign,
+            flags,
+            mem::size_of::<Self>() as u32,
+        );
         Self { header }
     }
 
@@ -36,6 +40,19 @@ impl ModuleAlignHeaderTag {
     }
 }
 
+impl MaybeDynSized for ModuleAlignHeaderTag {
+    type Header = HeaderTagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<Self>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for ModuleAlignHeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::ModuleAlign;
+}
+
 #[cfg(test)]
 mod tests {
     use crate::ModuleAlignHeaderTag;

+ 21 - 4
multiboot2-header/src/relocatable.rs

@@ -1,7 +1,8 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
 use core::fmt;
 use core::fmt::{Debug, Formatter};
-use core::mem::size_of;
+use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// It contains load address placement suggestion for boot loader. Boot loader
 /// should follow it. ‘0’ means none, ‘1’ means load image at lowest possible address
@@ -20,7 +21,7 @@ pub enum RelocatableHeaderTagPreference {
 
 /// This tag indicates that the image is relocatable.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct RelocatableHeaderTag {
     header: HeaderTagHeader,
     /// Lowest possible physical address at which image should be loaded. The bootloader cannot load any part of image below this address
@@ -42,8 +43,11 @@ impl RelocatableHeaderTag {
         align: u32,
         preference: RelocatableHeaderTagPreference,
     ) -> Self {
-        let header =
-            HeaderTagHeader::new(HeaderTagType::Relocatable, flags, size_of::<Self>() as u32);
+        let header = HeaderTagHeader::new(
+            HeaderTagType::Relocatable,
+            flags,
+            mem::size_of::<Self>() as u32,
+        );
         Self {
             header,
             min_addr,
@@ -111,6 +115,19 @@ impl Debug for RelocatableHeaderTag {
     }
 }
 
+impl MaybeDynSized for RelocatableHeaderTag {
+    type Header = HeaderTagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<Self>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for RelocatableHeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::Relocatable;
+}
+
 #[cfg(test)]
 mod tests {
     use crate::RelocatableHeaderTag;

+ 13 - 0
multiboot2-header/src/tags.rs

@@ -2,6 +2,9 @@
 //! code at the end of the official Multiboot2 spec. These tags follow in memory right after
 //! [`crate::Multiboot2BasicHeader`].
 
+use core::mem;
+use multiboot2_common::Header;
+
 /// ISA/ARCH in Multiboot2 header.
 #[repr(u32)]
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -102,6 +105,16 @@ impl HeaderTagHeader {
     }
 }
 
+impl Header for HeaderTagHeader {
+    fn payload_len(&self) -> usize {
+        self.size as usize - mem::size_of::<Self>()
+    }
+
+    fn set_size(&mut self, total_size: usize) {
+        self.size = total_size as u32;
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::HeaderTagHeader;

+ 18 - 3
multiboot2-header/src/uefi_bs.rs

@@ -1,10 +1,11 @@
 use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
-use core::mem::size_of;
+use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// This tag indicates that payload supports starting without terminating UEFI boot services.
 /// Or in other words: The payload wants to use UEFI boot services.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[repr(C)]
+#[repr(C, align(8))]
 pub struct EfiBootServiceHeaderTag {
     header: HeaderTagHeader,
 }
@@ -13,7 +14,8 @@ impl EfiBootServiceHeaderTag {
     /// Constructs a new tag.
     #[must_use]
     pub const fn new(flags: HeaderTagFlag) -> Self {
-        let header = HeaderTagHeader::new(HeaderTagType::EfiBS, flags, size_of::<Self>() as u32);
+        let header =
+            HeaderTagHeader::new(HeaderTagType::EfiBS, flags, mem::size_of::<Self>() as u32);
         Self { header }
     }
 
@@ -36,6 +38,19 @@ impl EfiBootServiceHeaderTag {
     }
 }
 
+impl MaybeDynSized for EfiBootServiceHeaderTag {
+    type Header = HeaderTagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<Self>();
+
+    fn dst_len(_header: &Self::Header) -> Self::Metadata {}
+}
+
+impl Tag for EfiBootServiceHeaderTag {
+    type IDType = HeaderTagType;
+    const ID: HeaderTagType = HeaderTagType::EfiBS;
+}
+
 #[cfg(test)]
 mod tests {
     use crate::EfiBootServiceHeaderTag;

+ 7 - 7
multiboot2/Cargo.toml

@@ -6,7 +6,7 @@ Multiboot2-compliant bootloaders, such as GRUB. It supports all tags from the
 specification including full support for the sections of ELF files. This library
 is `no_std` and can be used in a Multiboot2-kernel.
 """
-version = "0.21.0"
+version = "0.22.0"
 authors = [
     "Philipp Oppermann <dev@phil-opp.com>",
     "Calvin Lee <cyrus296@gmail.com>",
@@ -17,6 +17,7 @@ license = "MIT/Apache-2.0"
 edition = "2021"
 categories = [
     "no-std",
+    "no-std::no-alloc",
     "parsing",
 ]
 keywords = [
@@ -35,24 +36,23 @@ rust-version = "1.70"
 
 [features]
 default = ["builder"]
-alloc = []
-builder = ["alloc"]
+alloc = ["multiboot2-common/alloc"]
+builder = ["alloc", "multiboot2-common/builder"]
 # Nightly-only features, which will eventually be stabilized.
-unstable = []
+unstable = ["multiboot2-common/unstable"]
 
 [dependencies]
 bitflags.workspace = true
 derive_more.workspace = true
 log.workspace = true
+ptr_meta.workspace = true
+multiboot2-common = { version = "0.1.0", default-features = false }
 
-ptr_meta = { version = "~0.2", default-features = false }
 # We only use a very basic type definition from this crate. To prevent MSRV
 # bumps from uefi-raw, I restrict this here. Upstream users are likely to have
 # two versions of this library in it, which is no problem, as we only use the
 # type definition.
 uefi-raw = { version = "~0.5", default-features = false }
 
-[dev-dependencies]
-
 [package.metadata.docs.rs]
 all-features = true

+ 38 - 5
multiboot2/Changelog.md

@@ -1,17 +1,50 @@
 # CHANGELOG for crate `multiboot2`
 
-## Unreleased
-
--
+## v0.22.0
+
+This release contains another major refactoring of the internals, guaranteeing
+even more sanity checks for correct behaviour and lack of UB. In this release,
+the `Builder` was rewritten and lots of corresponding UB in certain
+corer-cases removed. Further, the builder's API was streamlined.
+
+If you are interested in the internals of the major refactorings recently taken
+place, please head to the documentation of `multiboot2-common`.
+
+- **Breaking:** The builder type is now just called `Builder`. This needs the
+  `builder` feature.
+- **Breaking:** The framebuffer tag was refactored and several bugs, memory
+- issues, and UB were fixed. It is now safe to use this, but some existing
+  usages might break and need to be slightly adapted.
+- **Breaking:** The trait `TagTrait` was removed and was replaced by a new `Tag`
+  trait coming from `multiboot2-common`. This only affects you if you provide
+  custom tag types for the library.
+- **Breaking:** The error type returned by `BootInformation::load` has been
+  changed.
+
+**General Note on Safety and UB (TL;DR: Crate is Safe)**
+
+The major refactorings of release `0.21` and `0.22` were an incredible step
+forward in code quality and memory safety. We have a comprehensive test coverage
+and all tests are passed by Miri. It might be that by using fuzzing, more
+corner and niche cases where UB can occur get uncovered. However, for every-day
+usage with sane bootloaders that do not intentionally create malformed tags, you
+are now absolutely good to go.
+
+Sorry for all the UB that silently slept insight many parts of the code base.
+This is a community project that has grown over the years. But now, the code
+base is in excellent shape!
 
 ## 0.21.0 (2024-08-17)
 
-This release contains a massive refactoring of various internals. Now, **all
-unit tests pass Miri**, thus we removed lots of undefined behaviour and
+This release contains a massive refactoring of various internals. Now, **almost
+**unit tests pass Miri**, thus we removed lots of undefined behaviour and
 increased the memory safety! 🎉 Only a small part of these internal refactorings
 leak to the public interface. If you don't use external custom tags, you
 should be fine from any refactorings.
 
+_**Edit**: The builder and the framebuffer still contain some UB. This is fixed
+in the next release._
+
 Please note that **all previous releases** must be considered unsafe, as they
 contain UB. However, it is never clear how UB results in immediate incorrect
 behaviour and it _might_ work. **Nevertheless, please migrate to the latest

+ 91 - 95
multiboot2/src/boot_information.rs

@@ -1,40 +1,41 @@
 //! Module for [`BootInformation`].
 
-#[cfg(feature = "builder")]
-use crate::builder::AsBytes;
 use crate::framebuffer::UnknownFramebufferType;
-use crate::tag::{TagHeader, TagIter};
+use crate::tag::TagHeader;
 use crate::{
     module, BasicMemoryInfoTag, BootLoaderNameTag, CommandLineTag, EFIBootServicesNotExitedTag,
     EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag, EFISdt32Tag, EFISdt64Tag,
     ElfSectionIter, ElfSectionsTag, EndTag, FramebufferTag, ImageLoadPhysAddrTag, MemoryMapTag,
-    ModuleIter, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagTrait, VBEInfoTag,
+    ModuleIter, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagIter, TagType, VBEInfoTag,
 };
+#[cfg(feature = "unstable")]
+use core::error::Error;
 use core::fmt;
 use core::mem;
-use core::ptr;
+use core::ptr::NonNull;
 use derive_more::Display;
+use multiboot2_common::{DynSizedStructure, Header, MaybeDynSized, MemoryError, Tag};
 
-/// Error type that describes errors while loading/parsing a multiboot2 information structure
-/// from a given address.
+/// Errors that occur when a chunk of memory can't be parsed as
+/// [`BootInformation`].
 #[derive(Display, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum MbiLoadError {
-    /// The address is invalid. Make sure that the address is 8-byte aligned,
-    /// according to the spec.
-    #[display(fmt = "The address is invalid")]
-    IllegalAddress,
-    /// The total size of the multiboot2 information structure must be not zero
-    /// and a multiple of 8.
-    #[display(fmt = "The size of the MBI is unexpected")]
-    IllegalTotalSize(u32),
-    /// Missing end tag. Each multiboot2 boot information requires to have an
-    /// end tag.
-    #[display(fmt = "There is no end tag")]
+pub enum LoadError {
+    /// The provided memory can't be parsed as [`BootInformation`].
+    /// See [`MemoryError`].
+    Memory(MemoryError),
+    /// Missing mandatory end tag.
     NoEndTag,
 }
 
 #[cfg(feature = "unstable")]
-impl core::error::Error for MbiLoadError {}
+impl Error for LoadError {
+    fn source(&self) -> Option<&(dyn Error + 'static)> {
+        match self {
+            Self::Memory(inner) => Some(inner),
+            Self::NoEndTag => None,
+        }
+    }
+}
 
 /// The basic header of a [`BootInformation`] as sized Rust type.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -62,40 +63,19 @@ impl BootInformationHeader {
     }
 }
 
-#[cfg(feature = "builder")]
-impl AsBytes for BootInformationHeader {}
-
-/// This type holds the whole data of the MBI. This helps to better satisfy miri
-/// when it checks for memory issues.
-#[derive(ptr_meta::Pointee)]
-#[repr(C, align(8))]
-struct BootInformationInner {
-    header: BootInformationHeader,
-    tags: [u8],
-}
-
-impl BootInformationInner {
-    /// Checks if the MBI has a valid end tag by checking the end of the mbi's
-    /// bytes.
-    fn has_valid_end_tag(&self) -> bool {
-        let self_ptr = ptr::addr_of!(*self);
-
-        let end_tag_ptr = unsafe {
-            self_ptr
-                .cast::<u8>()
-                .add(self.header.total_size as usize)
-                .sub(mem::size_of::<EndTag>())
-                .cast::<TagHeader>()
-        };
-        let end_tag = unsafe { &*end_tag_ptr };
+impl Header for BootInformationHeader {
+    fn payload_len(&self) -> usize {
+        self.total_size as usize - mem::size_of::<Self>()
+    }
 
-        end_tag.typ == EndTag::ID && end_tag.size as usize == mem::size_of::<EndTag>()
+    fn set_size(&mut self, total_size: usize) {
+        self.total_size = total_size as u32;
     }
 }
 
 /// A Multiboot 2 Boot Information (MBI) accessor.
 #[repr(transparent)]
-pub struct BootInformation<'a>(&'a BootInformationInner);
+pub struct BootInformation<'a>(&'a DynSizedStructure<BootInformationHeader>);
 
 impl<'a> BootInformation<'a> {
     /// Loads the [`BootInformation`] from a pointer. The pointer must be valid
@@ -115,36 +95,38 @@ impl<'a> BootInformation<'a> {
     /// ```
     ///
     /// ## Safety
-    /// * `ptr` must be valid for reading. Otherwise this function might cause
+    /// * `ptr` must be valid for reading. Otherwise, this function might cause
     ///   invalid machine state or crash your binary (kernel). This can be the
     ///   case in environments with standard environment (segfault), but also in
     ///   boot environments, such as UEFI.
     /// * The memory at `ptr` must not be modified after calling `load` or the
     ///   program may observe unsynchronized mutation.
-    pub unsafe fn load(ptr: *const BootInformationHeader) -> Result<Self, MbiLoadError> {
-        // null or not aligned
-        if ptr.is_null() || ptr.align_offset(8) != 0 {
-            return Err(MbiLoadError::IllegalAddress);
-        }
-
-        // mbi: reference to basic header
-        let mbi = &*ptr;
+    pub unsafe fn load(ptr: *const BootInformationHeader) -> Result<Self, LoadError> {
+        let ptr = NonNull::new(ptr.cast_mut()).ok_or(LoadError::Memory(MemoryError::Null))?;
+        let inner = DynSizedStructure::ref_from_ptr(ptr).map_err(LoadError::Memory)?;
 
-        // Check if total size is not 0 and a multiple of 8.
-        if mbi.total_size == 0 || mbi.total_size & 0b111 != 0 {
-            return Err(MbiLoadError::IllegalTotalSize(mbi.total_size));
+        let this = Self(inner);
+        if !this.has_valid_end_tag() {
+            return Err(LoadError::NoEndTag);
         }
+        Ok(this)
+    }
 
-        let slice_size = mbi.total_size as usize - mem::size_of::<BootInformationHeader>();
-        // mbi: reference to full mbi
-        let mbi = ptr_meta::from_raw_parts::<BootInformationInner>(ptr.cast(), slice_size);
-        let mbi = &*mbi;
-
-        if !mbi.has_valid_end_tag() {
-            return Err(MbiLoadError::NoEndTag);
-        }
+    /// Checks if the MBI has a valid end tag by checking the end of the mbi's
+    /// bytes.
+    fn has_valid_end_tag(&self) -> bool {
+        let header = self.0.header();
+        let end_tag_ptr = unsafe {
+            self.0
+                .payload()
+                .as_ptr()
+                .add(header.payload_len())
+                .sub(mem::size_of::<EndTag>())
+                .cast::<TagHeader>()
+        };
+        let end_tag = unsafe { &*end_tag_ptr };
 
-        Ok(Self(mbi))
+        end_tag.typ == EndTag::ID && end_tag.size as usize == mem::size_of::<EndTag>()
     }
 
     /// Get the start address of the boot info.
@@ -177,7 +159,7 @@ impl<'a> BootInformation<'a> {
     /// Get the total size of the boot info struct.
     #[must_use]
     pub const fn total_size(&self) -> usize {
-        self.0.header.total_size as usize
+        self.0.header().total_size as usize
     }
 
     // ######################################################
@@ -279,7 +261,7 @@ impl<'a> BootInformation<'a> {
     pub fn elf_sections(&self) -> Option<ElfSectionIter> {
         let tag = self.get_tag::<ElfSectionsTag>();
         tag.map(|t| {
-            assert!((t.entry_size() * t.shndx()) <= t.size() as u32);
+            assert!((t.entry_size() * t.shndx()) <= t.header().size);
             t.sections_iter()
         })
     }
@@ -289,10 +271,12 @@ impl<'a> BootInformation<'a> {
     #[must_use]
     pub fn framebuffer_tag(&self) -> Option<Result<&FramebufferTag, UnknownFramebufferType>> {
         self.get_tag::<FramebufferTag>()
-            .map(|tag| match tag.buffer_type() {
-                Ok(_) => Ok(tag),
-                Err(e) => Err(e),
-            })
+            // TODO temporarily. Someone needs to fix the framebuffer thingy.
+            .map(Ok)
+        /*.map(|tag| match tag.buffer_type() {
+            Ok(_) => Ok(tag),
+            Err(e) => Err(e),
+        })*/
     }
 
     /// Search for the Image Load Base Physical Address tag.
@@ -361,34 +345,44 @@ impl<'a> BootInformation<'a> {
     /// special handling is required. This is reflected by code-comments.
     ///
     /// ```no_run
-    /// use multiboot2::{BootInformation, BootInformationHeader, parse_slice_as_string, StringError, TagHeader, TagTrait, TagType, TagTypeId};
+    /// use std::mem;
+    /// use multiboot2::{BootInformation, BootInformationHeader, parse_slice_as_string, StringError, TagHeader, TagType, TagTypeId};    ///
+    /// use multiboot2_common::{MaybeDynSized, Tag};
     ///
     /// #[repr(C)]
     /// #[derive(multiboot2::Pointee)] // Only needed for DSTs.
     /// struct CustomTag {
-    ///     tag: TagTypeId,
-    ///     size: u32,
-    ///     // begin of inline string
+    ///     header: TagHeader,
+    ///     some_other_prop: u32,
+    ///     // Begin of C string, for example.
     ///     name: [u8],
     /// }
     ///
-    /// // This implementation is only necessary for tags that are DSTs.
-    /// impl TagTrait for CustomTag {
-    ///     const ID: TagType = TagType::Custom(0x1337);
+    /// impl CustomTag {
+    ///     fn name(&self) -> Result<&str, StringError> {
+    ///         parse_slice_as_string(&self.name)
+    ///     }
+    /// }
+    ///
+    /// // Give the library hints how big this tag is.
+    /// impl MaybeDynSized for CustomTag {
+    ///     type Header = TagHeader;
+    ///     const BASE_SIZE: usize = mem::size_of::<TagHeader>() + mem::size_of::<u32>();
     ///
+    ///     // This differs for DSTs and normal structs. See function
+    ///     // documentation.
     ///     fn dst_len(header: &TagHeader) -> usize {
-    ///         // The size of the sized portion of the custom tag.
-    ///         let tag_base_size = 8; // id + size is 8 byte in size
-    ///         assert!(header.size >= 8);
-    ///         header.size as usize - tag_base_size
+    ///         assert!(header.size >= Self::BASE_SIZE as u32);
+    ///         header.size as usize - Self::BASE_SIZE
     ///     }
     /// }
     ///
-    /// impl CustomTag {
-    ///     fn name(&self) -> Result<&str, StringError> {
-    ///         parse_slice_as_string(&self.name)
-    ///     }
+    /// // Make the Tag identifiable.
+    /// impl Tag for CustomTag {
+    ///     type IDType = TagType;
+    ///     const ID: TagType = TagType::Custom(0x1337);
     /// }
+    ///
     /// let mbi_ptr = 0xdeadbeef as *const BootInformationHeader;
     /// let mbi = unsafe { BootInformation::load(mbi_ptr).unwrap() };
     ///
@@ -400,15 +394,17 @@ impl<'a> BootInformation<'a> {
     ///
     /// [`TagType`]: crate::TagType
     #[must_use]
-    pub fn get_tag<TagT: TagTrait + ?Sized + 'a>(&'a self) -> Option<&'a TagT> {
+    pub fn get_tag<T: Tag<IDType = TagType, Header = TagHeader> + ?Sized + 'a>(
+        &'a self,
+    ) -> Option<&'a T> {
         self.tags()
-            .find(|tag| tag.header().typ == TagT::ID)
-            .map(|tag| tag.cast::<TagT>())
+            .find(|tag| tag.header().typ == T::ID)
+            .map(|tag| tag.cast::<T>())
     }
 
     /// Returns an iterator over all tags.
-    fn tags(&self) -> TagIter {
-        TagIter::new(&self.0.tags)
+    pub(crate) fn tags(&self) -> TagIter {
+        TagIter::new(self.0.payload())
     }
 }
 

+ 27 - 18
multiboot2/src/boot_loader_name.rs

@@ -1,13 +1,12 @@
 //! Module for [`BootLoaderNameTag`].
 
 use crate::tag::TagHeader;
-use crate::{parse_slice_as_string, StringError, TagTrait, TagType};
+use crate::{parse_slice_as_string, StringError, TagType};
 use core::fmt::{Debug, Formatter};
 use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 #[cfg(feature = "builder")]
-use {crate::new_boxed, alloc::boxed::Box};
-
-const METADATA_SIZE: usize = mem::size_of::<TagHeader>();
+use {alloc::boxed::Box, multiboot2_common::new_boxed};
 
 /// The bootloader name tag.
 #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -23,11 +22,12 @@ impl BootLoaderNameTag {
     #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(name: &str) -> Box<Self> {
+        let header = TagHeader::new(Self::ID, 0);
         let bytes = name.as_bytes();
         if bytes.ends_with(&[0]) {
-            new_boxed(&[bytes])
+            new_boxed(header, &[bytes])
         } else {
-            new_boxed(&[bytes, &[0]])
+            new_boxed(header, &[bytes, &[0]])
         }
     }
 
@@ -75,21 +75,29 @@ impl Debug for BootLoaderNameTag {
     }
 }
 
-impl TagTrait for BootLoaderNameTag {
-    const ID: TagType = TagType::BootLoaderName;
+impl MaybeDynSized for BootLoaderNameTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<TagHeader>();
 
     fn dst_len(header: &TagHeader) -> usize {
-        assert!(header.size as usize >= METADATA_SIZE);
-        header.size as usize - METADATA_SIZE
+        assert!(header.size as usize >= Self::BASE_SIZE);
+        header.size as usize - Self::BASE_SIZE
     }
 }
 
+impl Tag for BootLoaderNameTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::BootLoaderName;
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::tag::{GenericTag, TagBytesRef};
-    use crate::test_util::AlignedBytes;
+    use crate::GenericInfoTag;
     use core::borrow::Borrow;
+    use multiboot2_common::test_utils::AlignedBytes;
 
     #[rustfmt::skip]
     fn get_bytes() -> AlignedBytes<16> {
@@ -106,8 +114,7 @@ mod tests {
     #[test]
     fn test_parse_str() {
         let bytes = get_bytes();
-        let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
-        let tag = GenericTag::ref_from(bytes);
+        let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap();
         let tag = tag.cast::<BootLoaderNameTag>();
         assert_eq!(tag.header.typ, TagType::BootLoaderName);
         assert_eq!(tag.name(), Ok("hello"));
@@ -118,14 +125,16 @@ mod tests {
     #[cfg(feature = "builder")]
     fn test_build_str() {
         let tag = BootLoaderNameTag::new("hello");
-        let bytes = tag.as_bytes();
-        assert_eq!(bytes, &get_bytes()[..tag.size()]);
+        let bytes = tag.as_bytes().as_ref();
+        let bytes = &bytes[..tag.header.size as usize];
+        assert_eq!(bytes, &get_bytes()[..tag.header.size as usize]);
         assert_eq!(tag.name(), Ok("hello"));
 
         // With terminating null.
         let tag = BootLoaderNameTag::new("hello\0");
-        let bytes = tag.as_bytes();
-        assert_eq!(bytes, &get_bytes()[..tag.size()]);
+        let bytes = tag.as_bytes().as_ref();
+        let bytes = &bytes[..tag.header.size as usize];
+        assert_eq!(bytes, &get_bytes()[..tag.header.size as usize]);
         assert_eq!(tag.name(), Ok("hello"));
 
         // test also some bigger message

+ 346 - 0
multiboot2/src/builder.rs

@@ -0,0 +1,346 @@
+//! Module for [`Builder`].
+
+use crate::{
+    BasicMemoryInfoTag, BootInformationHeader, BootLoaderNameTag, CommandLineTag,
+    EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag,
+    EFISdt32Tag, EFISdt64Tag, ElfSectionsTag, EndTag, FramebufferTag, ImageLoadPhysAddrTag,
+    MemoryMapTag, ModuleTag, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagHeader, TagType, VBEInfoTag,
+};
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use multiboot2_common::{new_boxed, DynSizedStructure, MaybeDynSized};
+
+/// Builder for a Multiboot2 header information.
+// #[derive(Debug)]
+#[derive(Debug)]
+pub struct Builder {
+    cmdline: Option<Box<CommandLineTag>>,
+    bootloader: Option<Box<BootLoaderNameTag>>,
+    modules: Vec<Box<ModuleTag>>,
+    meminfo: Option<BasicMemoryInfoTag>,
+    // missing bootdev: Option<BootDevice>
+    mmap: Option<Box<MemoryMapTag>>,
+    vbe: Option<VBEInfoTag>,
+    framebuffer: Option<Box<FramebufferTag>>,
+    elf_sections: Option<Box<ElfSectionsTag>>,
+    // missing apm:
+    efi32: Option<EFISdt32Tag>,
+    efi64: Option<EFISdt64Tag>,
+    smbios: Vec<Box<SmbiosTag>>,
+    rsdpv1: Option<RsdpV1Tag>,
+    rsdpv2: Option<RsdpV2Tag>,
+    // missing: network
+    efi_mmap: Option<Box<EFIMemoryMapTag>>,
+    efi_bs: Option<EFIBootServicesNotExitedTag>,
+    efi32_ih: Option<EFIImageHandle32Tag>,
+    efi64_ih: Option<EFIImageHandle64Tag>,
+    image_load_addr: Option<ImageLoadPhysAddrTag>,
+    custom_tags: Vec<Box<DynSizedStructure<TagHeader>>>,
+}
+
+impl Default for Builder {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl Builder {
+    /// Creates a new builder.
+    #[must_use]
+    pub const fn new() -> Self {
+        Self {
+            cmdline: None,
+            bootloader: None,
+            modules: vec![],
+            meminfo: None,
+            mmap: None,
+            vbe: None,
+            framebuffer: None,
+            elf_sections: None,
+            efi32: None,
+            efi64: None,
+            smbios: vec![],
+            rsdpv1: None,
+            rsdpv2: None,
+            efi_mmap: None,
+            efi_bs: None,
+            efi32_ih: None,
+            efi64_ih: None,
+            image_load_addr: None,
+            custom_tags: vec![],
+        }
+    }
+
+    /// Sets the [`CommandLineTag`] tag.
+    #[must_use]
+    pub fn cmdline(mut self, cmdline: Box<CommandLineTag>) -> Self {
+        self.cmdline = Some(cmdline);
+        self
+    }
+
+    /// Sets the [`BootLoaderNameTag`] tag.
+    #[must_use]
+    pub fn bootloader(mut self, bootloader: Box<BootLoaderNameTag>) -> Self {
+        self.bootloader = Some(bootloader);
+        self
+    }
+
+    /// Adds a [`ModuleTag`] tag.
+    #[must_use]
+    pub fn add_module(mut self, module: Box<ModuleTag>) -> Self {
+        self.modules.push(module);
+        self
+    }
+
+    /// Sets the [`BasicMemoryInfoTag`] tag.
+    #[must_use]
+    pub const fn meminfo(mut self, meminfo: BasicMemoryInfoTag) -> Self {
+        self.meminfo = Some(meminfo);
+        self
+    }
+
+    /// Sets the [`MemoryMapTag`] tag.
+    #[must_use]
+    pub fn mmap(mut self, mmap: Box<MemoryMapTag>) -> Self {
+        self.mmap = Some(mmap);
+        self
+    }
+
+    /// Sets the [`VBEInfoTag`] tag.
+    #[must_use]
+    pub const fn vbe(mut self, vbe: VBEInfoTag) -> Self {
+        self.vbe = Some(vbe);
+        self
+    }
+
+    /// Sets the [`FramebufferTag`] tag.
+    #[must_use]
+    pub fn framebuffer(mut self, framebuffer: Box<FramebufferTag>) -> Self {
+        self.framebuffer = Some(framebuffer);
+        self
+    }
+
+    /// Sets the [`ElfSectionsTag`] tag.
+    #[must_use]
+    pub fn elf_sections(mut self, elf_sections: Box<ElfSectionsTag>) -> Self {
+        self.elf_sections = Some(elf_sections);
+        self
+    }
+
+    /// Sets the [`EFISdt32Tag`] tag.
+    #[must_use]
+    pub const fn efi32(mut self, efi32: EFISdt32Tag) -> Self {
+        self.efi32 = Some(efi32);
+        self
+    }
+
+    /// Sets the [`EFISdt64Tag`] tag.
+    #[must_use]
+    pub const fn efi64(mut self, efi64: EFISdt64Tag) -> Self {
+        self.efi64 = Some(efi64);
+        self
+    }
+
+    /// Adds a [`SmbiosTag`] tag.
+    #[must_use]
+    pub fn add_smbios(mut self, smbios: Box<SmbiosTag>) -> Self {
+        self.smbios.push(smbios);
+        self
+    }
+
+    /// Sets the [`RsdpV1Tag`] tag.
+    #[must_use]
+    pub const fn rsdpv1(mut self, rsdpv1: RsdpV1Tag) -> Self {
+        self.rsdpv1 = Some(rsdpv1);
+        self
+    }
+
+    /// Sets the [`RsdpV2Tag`] tag.
+    #[must_use]
+    pub const fn rsdpv2(mut self, rsdpv2: RsdpV2Tag) -> Self {
+        self.rsdpv2 = Some(rsdpv2);
+        self
+    }
+
+    /// Sets the [`EFIMemoryMapTag`] tag.
+    #[must_use]
+    pub fn efi_mmap(mut self, efi_mmap: Box<EFIMemoryMapTag>) -> Self {
+        self.efi_mmap = Some(efi_mmap);
+        self
+    }
+
+    /// Sets the [`EFIBootServicesNotExitedTag`] tag.
+    #[must_use]
+    pub const fn efi_bs(mut self, efi_bs: EFIBootServicesNotExitedTag) -> Self {
+        self.efi_bs = Some(efi_bs);
+        self
+    }
+
+    /// Sets the [`EFIImageHandle32Tag`] tag.
+    #[must_use]
+    pub const fn efi32_ih(mut self, efi32_ih: EFIImageHandle32Tag) -> Self {
+        self.efi32_ih = Some(efi32_ih);
+        self
+    }
+
+    /// Sets the [`EFIImageHandle64Tag`] tag.
+    #[must_use]
+    pub const fn efi64_ih(mut self, efi64_ih: EFIImageHandle64Tag) -> Self {
+        self.efi64_ih = Some(efi64_ih);
+        self
+    }
+
+    /// Sets the [`ImageLoadPhysAddrTag`] tag.
+    #[must_use]
+    pub const fn image_load_addr(mut self, image_load_addr: ImageLoadPhysAddrTag) -> Self {
+        self.image_load_addr = Some(image_load_addr);
+        self
+    }
+
+    /// Adds a custom tag.
+    #[must_use]
+    pub fn add_custom_tag(mut self, custom_tag: Box<DynSizedStructure<TagHeader>>) -> Self {
+        if let TagType::Custom(_c) = custom_tag.header().typ.into() {
+            self.custom_tags.push(custom_tag);
+        } else {
+            panic!("Only for custom types!");
+        }
+        self
+    }
+
+    /// Returns properly aligned bytes on the heap representing a valid
+    /// Multiboot2 header structure.
+    #[must_use]
+    pub fn build(self) -> Box<DynSizedStructure<BootInformationHeader>> {
+        let header = BootInformationHeader::new(0);
+        let mut byte_refs = Vec::new();
+        if let Some(tag) = self.cmdline.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.bootloader.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        for i in &self.modules {
+            byte_refs.push(i.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.meminfo.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.mmap.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.vbe.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.framebuffer.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.elf_sections.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.efi32.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.efi64.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        for i in &self.smbios {
+            byte_refs.push(i.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.rsdpv1.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.rsdpv2.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.efi_mmap.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.efi_bs.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.efi32_ih.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.efi64_ih.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        if let Some(tag) = self.image_load_addr.as_ref() {
+            byte_refs.push(tag.as_bytes().as_ref());
+        }
+        for i in &self.custom_tags {
+            byte_refs.push(i.as_bytes().as_ref());
+        }
+        let end_tag = EndTag::default();
+        byte_refs.push(end_tag.as_bytes().as_ref());
+        new_boxed(header, byte_refs.as_slice())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{
+        BootInformation, FramebufferType, MemoryArea, MemoryAreaType, VBEControlInfo, VBEModeInfo,
+    };
+    use uefi_raw::table::boot::MemoryDescriptor;
+
+    #[test]
+    fn build_and_parse() {
+        let builder = Builder::new()
+            .cmdline(CommandLineTag::new("this is a command line"))
+            .bootloader(BootLoaderNameTag::new("this is the bootloader"))
+            .add_module(ModuleTag::new(0x1000, 0x2000, "module 1"))
+            .add_module(ModuleTag::new(0x3000, 0x4000, "module 2"))
+            .meminfo(BasicMemoryInfoTag::new(0x4000, 0x5000))
+            .mmap(MemoryMapTag::new(&[MemoryArea::new(
+                0x1000000,
+                0x1000,
+                MemoryAreaType::Available,
+            )]))
+            .vbe(VBEInfoTag::new(
+                42,
+                2,
+                4,
+                9,
+                VBEControlInfo::default(),
+                VBEModeInfo::default(),
+            ))
+            // Currently causes UB.
+            .framebuffer(FramebufferTag::new(
+                0x1000,
+                1,
+                756,
+                1024,
+                8,
+                FramebufferType::Text,
+            ))
+            .elf_sections(ElfSectionsTag::new(0, 32, 0, &[]))
+            .efi32(EFISdt32Tag::new(0x1000))
+            .efi64(EFISdt64Tag::new(0x1000))
+            .add_smbios(SmbiosTag::new(0, 0, &[1, 2, 3]))
+            .add_smbios(SmbiosTag::new(1, 1, &[4, 5, 6]))
+            .rsdpv1(RsdpV1Tag::new(0, *b"abcdef", 5, 6))
+            .rsdpv2(RsdpV2Tag::new(0, *b"abcdef", 5, 6, 5, 4, 7))
+            .efi_mmap(EFIMemoryMapTag::new_from_descs(&[
+                MemoryDescriptor::default(),
+                MemoryDescriptor::default(),
+            ]))
+            .efi_bs(EFIBootServicesNotExitedTag::new())
+            .efi32_ih(EFIImageHandle32Tag::new(0x1000))
+            .efi64_ih(EFIImageHandle64Tag::new(0x1000))
+            .image_load_addr(ImageLoadPhysAddrTag::new(0x1000))
+            .add_custom_tag(new_boxed::<DynSizedStructure<TagHeader>>(
+                TagHeader::new(TagType::Custom(0x1337), 0),
+                &[],
+            ));
+
+        let structure = builder.build();
+
+        let info = unsafe { BootInformation::load(structure.as_bytes().as_ptr().cast()) }.unwrap();
+        for tag in info.tags() {
+            // Mainly a test for Miri.
+            dbg!(tag.header(), tag.payload().len());
+        }
+    }
+}

+ 0 - 391
multiboot2/src/builder/information.rs

@@ -1,391 +0,0 @@
-//! Exports item [`InformationBuilder`].
-use crate::builder::AsBytes;
-use crate::util::increase_to_alignment;
-use crate::{
-    BasicMemoryInfoTag, BootInformationHeader, BootLoaderNameTag, CommandLineTag,
-    EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag,
-    EFISdt32Tag, EFISdt64Tag, ElfSectionsTag, EndTag, FramebufferTag, ImageLoadPhysAddrTag,
-    MemoryMapTag, ModuleTag, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagTrait, TagType, VBEInfoTag,
-    ALIGNMENT,
-};
-use alloc::vec::Vec;
-use core::fmt::{Display, Formatter};
-use core::mem::size_of;
-use core::ops::Deref;
-
-/// Holds the raw bytes of a boot information built with [`InformationBuilder`]
-/// on the heap. The bytes returned by [`BootInformationBytes::as_bytes`] are
-/// guaranteed to be properly aligned.
-#[derive(Clone, Debug)]
-pub struct BootInformationBytes {
-    // Offset into the bytes where the MBI starts. This is necessary to
-    // guarantee alignment at the moment.
-    offset: usize,
-    structure_len: usize,
-    bytes: Vec<u8>,
-}
-
-impl BootInformationBytes {
-    /// Returns the bytes. They are guaranteed to be correctly aligned.
-    pub fn as_bytes(&self) -> &[u8] {
-        let slice = &self.bytes[self.offset..self.offset + self.structure_len];
-        // At this point, the alignment is guaranteed. If not, something is
-        // broken fundamentally.
-        assert_eq!(slice.as_ptr().align_offset(8), 0);
-        slice
-    }
-}
-
-impl Deref for BootInformationBytes {
-    type Target = [u8];
-
-    fn deref(&self) -> &Self::Target {
-        self.as_bytes()
-    }
-}
-
-type SerializedTag = Vec<u8>;
-
-/// Error that indicates a tag was added multiple times that is not allowed to
-/// be there multiple times.
-#[derive(Debug)]
-#[allow(unused)]
-pub struct RedundantTagError(TagType);
-
-impl Display for RedundantTagError {
-    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
-        write!(f, "{:?}", self)
-    }
-}
-
-#[cfg(feature = "unstable")]
-impl core::error::Error for RedundantTagError {}
-
-/// Builder to construct a valid Multiboot2 information dynamically at runtime.
-/// The tags will appear in the order of their corresponding enumeration,
-/// except for the END tag.
-#[derive(Debug, PartialEq, Eq)]
-pub struct InformationBuilder(Vec<(TagType, SerializedTag)>);
-
-impl Default for InformationBuilder {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl InformationBuilder {
-    /// Creates a new builder.
-    #[must_use]
-    pub const fn new() -> Self {
-        Self(Vec::new())
-    }
-
-    /// Returns the expected length of the boot information, when the
-    /// [`Self::build`]-method is called. This function assumes that the begin
-    /// of the boot information is 8-byte aligned and automatically adds padding
-    /// between tags to ensure that each tag is 8-byte aligned.
-    #[must_use]
-    pub fn expected_len(&self) -> usize {
-        let tag_size_iter = self.0.iter().map(|(_, bytes)| bytes.len());
-
-        let payload_tags_size =
-            tag_size_iter.fold(0, |acc, tag_size| acc + increase_to_alignment(tag_size));
-
-        size_of::<BootInformationHeader>() + payload_tags_size + size_of::<EndTag>()
-    }
-
-    /// Adds the bytes of a tag to the final Multiboot2 information byte vector.
-    fn build_add_tag(dest_buf: &mut Vec<u8>, tag_serialized: &[u8], tag_type: TagType) {
-        let vec_next_write_ptr = unsafe { dest_buf.as_ptr().add(dest_buf.len()) };
-
-        // At this point, the alignment is guaranteed. If not, something is
-        // broken fundamentally.
-        assert_eq!(vec_next_write_ptr.align_offset(8), 0);
-
-        dest_buf.extend(tag_serialized);
-
-        if tag_type != TagType::End {
-            let size = tag_serialized.len();
-            let size_to_8_align = increase_to_alignment(size);
-            let size_to_8_align_diff = size_to_8_align - size;
-            // fill zeroes so that next data block is 8-byte aligned
-            dest_buf.extend([0].repeat(size_to_8_align_diff));
-        }
-    }
-
-    /// Constructs the bytes for a valid Multiboot2 information with the given properties.
-    #[must_use]
-    pub fn build(self) -> BootInformationBytes {
-        // PHASE 1/2: Prepare Vector
-
-        // We allocate more than necessary so that we can ensure an correct
-        // alignment within this data.
-        let expected_len = self.expected_len();
-        let alloc_len = expected_len + 7;
-        let mut bytes = Vec::<u8>::with_capacity(alloc_len);
-        // Pointer to check that no relocation happened.
-        let alloc_ptr = bytes.as_ptr();
-
-        // As long as there is no nice way in stable Rust to guarantee the
-        // alignment of a vector, I add zero bytes at the beginning and the MBI
-        // might not start at the start of the allocation.
-        //
-        // Unfortunately, it is not possible to reliably test this in a unit
-        // test as long as the allocator_api feature is not stable.
-        // Due to my manual testing, however, it works.
-        let offset = bytes.as_ptr().align_offset(ALIGNMENT);
-        bytes.extend([0].repeat(offset));
-
-        // -----------------------------------------------
-        // PHASE 2/2: Add Tags
-        bytes.extend(BootInformationHeader::new(self.expected_len() as u32).as_bytes());
-
-        for (tag_type, tag_serialized) in self.0 {
-            Self::build_add_tag(&mut bytes, tag_serialized.as_slice(), tag_type)
-        }
-        Self::build_add_tag(&mut bytes, EndTag::default().as_bytes(), TagType::End);
-
-        assert_eq!(
-            alloc_ptr,
-            bytes.as_ptr(),
-            "Vector was reallocated. Alignment of MBI probably broken!"
-        );
-        assert_eq!(
-            bytes[0..offset].iter().sum::<u8>(),
-            0,
-            "The offset to alignment area should be zero."
-        );
-
-        BootInformationBytes {
-            offset,
-            bytes,
-            structure_len: expected_len,
-        }
-    }
-
-    /// Adds a arbitrary tag that implements [`TagTrait`] to the builder. Only
-    /// [`TagType::Module`] and [`TagType::Custom`] are allowed to occur
-    /// multiple times. For other tags, this function returns an error.
-    ///
-    /// It is not required to manually add the [`TagType::End`] tag.
-    ///
-    /// The tags of the boot information will be ordered naturally, i.e., by
-    /// their numeric ID.
-    pub fn add_tag<T: TagTrait + ?Sized>(mut self, tag: &T) -> Result<Self, RedundantTagError> {
-        // not required to do this manually
-        if T::ID == TagType::End {
-            return Ok(self);
-        }
-
-        let is_redundant_tag = self
-            .0
-            .iter()
-            .map(|(typ, _)| *typ)
-            // TODO make a type for tag_is_allowed_multiple_times so that we can
-            // link to it in the doc.
-            .any(|typ| typ == T::ID && !Self::tag_is_allowed_multiple_times(typ));
-
-        if is_redundant_tag {
-            log::debug!(
-                "Can't add tag of type {:?}. Only Module tags and Custom tags are allowed to appear multiple times.",
-                T::ID
-            );
-            return Err(RedundantTagError(T::ID));
-        }
-        self.0.push((T::ID, tag.as_bytes().to_vec()));
-        self.0.sort_by_key(|(typ, _)| *typ);
-
-        Ok(self)
-    }
-
-    /// Adds a 'basic memory information' tag (represented by [`BasicMemoryInfoTag`]) to the builder.
-    #[must_use]
-    pub fn basic_memory_info_tag(self, tag: &BasicMemoryInfoTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'bootloader name' tag (represented by [`BootLoaderNameTag`]) to the builder.
-    #[must_use]
-    pub fn bootloader_name_tag(self, tag: &BootLoaderNameTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'command line' tag (represented by [`CommandLineTag`]) to the builder.
-    #[must_use]
-    pub fn command_line_tag(self, tag: &CommandLineTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'EFI 32-bit system table pointer' tag (represented by [`EFISdt32Tag`]) to the builder.
-    #[must_use]
-    pub fn efisdt32_tag(self, tag: &EFISdt32Tag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'EFI 64-bit system table pointer' tag (represented by [`EFISdt64Tag`]) to the builder.
-    #[must_use]
-    pub fn efisdt64_tag(self, tag: &EFISdt64Tag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'EFI boot services not terminated' tag (represented by [`EFIBootServicesNotExitedTag`]) to the builder.
-    #[must_use]
-    pub fn efi_boot_services_not_exited_tag(self) -> Self {
-        self.add_tag(&EFIBootServicesNotExitedTag::new()).unwrap()
-    }
-
-    /// Adds a 'EFI 32-bit image handle pointer' tag (represented by [`EFIImageHandle32Tag`]) to the builder.
-    #[must_use]
-    pub fn efi_image_handle32(self, tag: &EFIImageHandle32Tag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'EFI 64-bit image handle pointer' tag (represented by [`EFIImageHandle64Tag`]) to the builder.
-    #[must_use]
-    pub fn efi_image_handle64(self, tag: &EFIImageHandle64Tag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'EFI Memory map' tag (represented by [`EFIMemoryMapTag`]) to the builder.
-    #[must_use]
-    pub fn efi_memory_map_tag(self, tag: &EFIMemoryMapTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'ELF-Symbols' tag (represented by [`ElfSectionsTag`]) to the builder.
-    #[must_use]
-    pub fn elf_sections_tag(self, tag: &ElfSectionsTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'Framebuffer info' tag (represented by [`FramebufferTag`]) to the builder.
-    #[must_use]
-    pub fn framebuffer_tag(self, tag: &FramebufferTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'Image load base physical address' tag (represented by [`ImageLoadPhysAddrTag`]) to the builder.
-    #[must_use]
-    pub fn image_load_addr(self, tag: &ImageLoadPhysAddrTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a (*none EFI*) 'memory map' tag (represented by [`MemoryMapTag`]) to the builder.
-    #[must_use]
-    pub fn memory_map_tag(self, tag: &MemoryMapTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'Modules' tag (represented by [`ModuleTag`]) to the builder.
-    /// This tag can occur multiple times in boot information.
-    #[must_use]
-    pub fn add_module_tag(self, tag: &ModuleTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'ACPI old RSDP' tag (represented by [`RsdpV1Tag`]) to the builder.
-    #[must_use]
-    pub fn rsdp_v1_tag(self, tag: &RsdpV1Tag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'ACPI new RSDP' tag (represented by [`RsdpV2Tag`]) to the builder.
-    #[must_use]
-    pub fn rsdp_v2_tag(self, tag: &RsdpV2Tag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'SMBIOS tables' tag (represented by [`SmbiosTag`]) to the builder.
-    #[must_use]
-    pub fn smbios_tag(self, tag: &SmbiosTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    /// Adds a 'VBE Info' tag (represented by [`VBEInfoTag`]) to the builder.
-    #[must_use]
-    pub fn vbe_info_tag(self, tag: &VBEInfoTag) -> Self {
-        self.add_tag(tag).unwrap()
-    }
-
-    #[must_use]
-    const fn tag_is_allowed_multiple_times(tag_type: TagType) -> bool {
-        matches!(
-            tag_type,
-            TagType::Module | TagType::Smbios | TagType::Custom(_)
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::builder::information::InformationBuilder;
-    use crate::{BasicMemoryInfoTag, BootInformation, CommandLineTag, ModuleTag};
-
-    fn create_builder() -> InformationBuilder {
-        let mut builder = InformationBuilder::new();
-
-        // Multiboot2 basic information + end tag
-        let mut expected_len = 8 + 8;
-        assert_eq!(builder.expected_len(), expected_len);
-
-        // the most simple tag
-        builder = builder.basic_memory_info_tag(&BasicMemoryInfoTag::new(640, 7 * 1024));
-        expected_len += 16;
-        assert_eq!(builder.expected_len(), expected_len);
-        // a tag that has a dynamic size
-        builder = builder.command_line_tag(&CommandLineTag::new("test"));
-        expected_len += 8 + 5 + 3; // padding
-        assert_eq!(builder.expected_len(), expected_len);
-        // many modules
-        builder = builder.add_module_tag(&ModuleTag::new(0, 1234, "module1"));
-        expected_len += 16 + 8;
-        assert_eq!(builder.expected_len(), expected_len);
-        builder = builder.add_module_tag(&ModuleTag::new(5678, 6789, "module2"));
-        expected_len += 16 + 8;
-        assert_eq!(builder.expected_len(), expected_len);
-
-        println!("builder: {:#?}", builder);
-        println!("expected_len: {} bytes", builder.expected_len());
-
-        builder
-    }
-
-    /// Test of the `build` method in isolation specifically for miri to check
-    /// for memory issues.
-    #[test]
-    fn test_builder_miri() {
-        let builder = create_builder();
-        let expected_len = builder.expected_len();
-        assert_eq!(builder.build().as_bytes().len(), expected_len);
-    }
-
-    #[test]
-    fn test_builder() {
-        // Step 1/2: Build MBI
-        let mb2i_data = create_builder().build();
-
-        // Step 2/2: Test the built MBI
-        let mb2i = unsafe { BootInformation::load(mb2i_data.as_ptr().cast()) }
-            .expect("generated information should be readable");
-
-        assert_eq!(mb2i.basic_memory_info_tag().unwrap().memory_lower(), 640);
-        assert_eq!(
-            mb2i.basic_memory_info_tag().unwrap().memory_upper(),
-            7 * 1024
-        );
-        assert_eq!(mb2i.command_line_tag().unwrap().cmdline().unwrap(), "test");
-        let mut modules = mb2i.module_tags();
-        let module_1 = modules.next().unwrap();
-        assert_eq!(module_1.start_address(), 0);
-        assert_eq!(module_1.end_address(), 1234);
-        assert_eq!(module_1.cmdline().unwrap(), "module1");
-        let module_2 = modules.next().unwrap();
-        assert_eq!(module_2.start_address(), 5678);
-        assert_eq!(module_2.end_address(), 6789);
-        assert_eq!(module_2.cmdline().unwrap(), "module2");
-        assert!(modules.next().is_none());
-
-        // Printing the MBI transitively ensures that a lot of stuff works.
-        println!("{:#?}", mb2i);
-    }
-}

+ 0 - 18
multiboot2/src/builder/mod.rs

@@ -1,18 +0,0 @@
-//! Module for the builder-feature.
-
-mod information;
-
-pub use information::InformationBuilder;
-
-/// Helper trait for all structs that need to be serialized that do not
-/// implement [`TagTrait`].
-///
-/// [`TagTrait`]: crate::TagTrait
-pub trait AsBytes: Sized {
-    /// Returns the raw bytes of the type.
-    fn as_bytes(&self) -> &[u8] {
-        let ptr = core::ptr::addr_of!(*self);
-        let size = core::mem::size_of::<Self>();
-        unsafe { core::slice::from_raw_parts(ptr.cast(), size) }
-    }
-}

+ 27 - 18
multiboot2/src/command_line.rs

@@ -1,14 +1,13 @@
 //! Module for [`CommandLineTag`].
 
 use crate::tag::TagHeader;
-use crate::{parse_slice_as_string, StringError, TagTrait, TagType};
+use crate::{parse_slice_as_string, StringError, TagType};
 use core::fmt::{Debug, Formatter};
 use core::mem;
 use core::str;
+use multiboot2_common::{MaybeDynSized, Tag};
 #[cfg(feature = "builder")]
-use {crate::new_boxed, alloc::boxed::Box};
-
-const METADATA_SIZE: usize = mem::size_of::<TagHeader>();
+use {alloc::boxed::Box, multiboot2_common::new_boxed};
 
 /// This tag contains the command line string.
 ///
@@ -27,11 +26,12 @@ impl CommandLineTag {
     #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(command_line: &str) -> Box<Self> {
+        let header = TagHeader::new(Self::ID, 0);
         let bytes = command_line.as_bytes();
         if bytes.ends_with(&[0]) {
-            new_boxed(&[bytes])
+            new_boxed(header, &[bytes])
         } else {
-            new_boxed(&[bytes, &[0]])
+            new_boxed(header, &[bytes, &[0]])
         }
     }
 
@@ -69,21 +69,29 @@ impl Debug for CommandLineTag {
     }
 }
 
-impl TagTrait for CommandLineTag {
-    const ID: TagType = TagType::Cmdline;
+impl MaybeDynSized for CommandLineTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<TagHeader>();
 
     fn dst_len(header: &TagHeader) -> usize {
-        assert!(header.size as usize >= METADATA_SIZE);
-        header.size as usize - METADATA_SIZE
+        assert!(header.size as usize >= Self::BASE_SIZE);
+        header.size as usize - Self::BASE_SIZE
     }
 }
 
+impl Tag for CommandLineTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Cmdline;
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::tag::{GenericTag, TagBytesRef};
-    use crate::test_util::AlignedBytes;
+    use crate::GenericInfoTag;
     use core::borrow::Borrow;
+    use multiboot2_common::test_utils::AlignedBytes;
 
     #[rustfmt::skip]
     fn get_bytes() -> AlignedBytes<16> {
@@ -100,8 +108,7 @@ mod tests {
     #[test]
     fn test_parse_str() {
         let bytes = get_bytes();
-        let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
-        let tag = GenericTag::ref_from(bytes);
+        let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap();
         let tag = tag.cast::<CommandLineTag>();
         assert_eq!(tag.header.typ, TagType::Cmdline);
         assert_eq!(tag.cmdline(), Ok("hello"));
@@ -112,14 +119,16 @@ mod tests {
     #[cfg(feature = "builder")]
     fn test_build_str() {
         let tag = CommandLineTag::new("hello");
-        let bytes = tag.as_bytes();
-        assert_eq!(bytes, &get_bytes()[..tag.size()]);
+        let bytes = tag.as_bytes().as_ref();
+        let bytes = &bytes[..tag.header.size as usize];
+        assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]);
         assert_eq!(tag.cmdline(), Ok("hello"));
 
         // With terminating null.
         let tag = CommandLineTag::new("hello\0");
-        let bytes = tag.as_bytes();
-        assert_eq!(bytes, &get_bytes()[..tag.size()]);
+        let bytes = tag.as_bytes().as_ref();
+        let bytes = &bytes[..tag.header.size as usize];
+        assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]);
         assert_eq!(tag.cmdline(), Ok("hello"));
 
         // test also some bigger message

+ 58 - 17
multiboot2/src/efi.rs

@@ -7,8 +7,9 @@
 //! - [`EFIBootServicesNotExitedTag`]
 
 use crate::tag::TagHeader;
-use crate::{TagTrait, TagType};
+use crate::TagType;
 use core::mem::size_of;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// EFI system table in 32 bit mode tag.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -19,11 +20,13 @@ pub struct EFISdt32Tag {
 }
 
 impl EFISdt32Tag {
+    const BASE_SIZE: usize = size_of::<TagHeader>() + size_of::<u32>();
+
     /// Create a new tag to pass the EFI32 System Table pointer.
     #[must_use]
     pub fn new(pointer: u32) -> Self {
         Self {
-            header: TagHeader::new(Self::ID, size_of::<Self>().try_into().unwrap()),
+            header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32),
             pointer,
         }
     }
@@ -35,12 +38,20 @@ impl EFISdt32Tag {
     }
 }
 
-impl TagTrait for EFISdt32Tag {
-    const ID: TagType = TagType::Efi32;
+impl MaybeDynSized for EFISdt32Tag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
+impl Tag for EFISdt32Tag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Efi32;
+}
+
 /// EFI system table in 64 bit mode tag.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[repr(C, align(8))]
@@ -66,12 +77,20 @@ impl EFISdt64Tag {
     }
 }
 
-impl TagTrait for EFISdt64Tag {
-    const ID: TagType = TagType::Efi64;
+impl MaybeDynSized for EFISdt64Tag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
+impl Tag for EFISdt64Tag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Efi64;
+}
+
 /// Tag that contains the pointer to the boot loader's UEFI image handle
 /// (32-bit).
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -82,12 +101,13 @@ pub struct EFIImageHandle32Tag {
 }
 
 impl EFIImageHandle32Tag {
+    const BASE_SIZE: usize = size_of::<TagHeader>() + size_of::<u32>();
+
     /// Constructs a new tag.
-    #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(pointer: u32) -> Self {
         Self {
-            header: TagHeader::new(Self::ID, size_of::<Self>().try_into().unwrap()),
+            header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32),
             pointer,
         }
     }
@@ -99,12 +119,20 @@ impl EFIImageHandle32Tag {
     }
 }
 
-impl TagTrait for EFIImageHandle32Tag {
-    const ID: TagType = TagType::Efi32Ih;
+impl MaybeDynSized for EFIImageHandle32Tag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
+impl Tag for EFIImageHandle32Tag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Efi32Ih;
+}
+
 /// Tag that contains the pointer to the boot loader's UEFI image handle
 /// (64-bit).
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -116,7 +144,6 @@ pub struct EFIImageHandle64Tag {
 
 impl EFIImageHandle64Tag {
     /// Constructs a new tag.
-    #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(pointer: u64) -> Self {
         Self {
@@ -132,12 +159,20 @@ impl EFIImageHandle64Tag {
     }
 }
 
-impl TagTrait for EFIImageHandle64Tag {
-    const ID: TagType = TagType::Efi64Ih;
+impl MaybeDynSized for EFIImageHandle64Tag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
+impl Tag for EFIImageHandle64Tag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Efi64Ih;
+}
+
 /// EFI ExitBootServices was not called tag. This tag has no payload and is
 /// just a marker.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -148,14 +183,12 @@ pub struct EFIBootServicesNotExitedTag {
 
 impl EFIBootServicesNotExitedTag {
     /// Constructs a new tag.
-    #[cfg(feature = "builder")]
     #[must_use]
     pub fn new() -> Self {
         Self::default()
     }
 }
 
-#[cfg(feature = "builder")]
 impl Default for EFIBootServicesNotExitedTag {
     fn default() -> Self {
         Self {
@@ -164,12 +197,20 @@ impl Default for EFIBootServicesNotExitedTag {
     }
 }
 
-impl TagTrait for EFIBootServicesNotExitedTag {
-    const ID: TagType = TagType::EfiBs;
+impl MaybeDynSized for EFIBootServicesNotExitedTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
+impl Tag for EFIBootServicesNotExitedTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::EfiBs;
+}
+
 #[cfg(all(test, feature = "builder"))]
 mod tests {
     use super::{EFIImageHandle32Tag, EFIImageHandle64Tag, EFISdt32Tag, EFISdt64Tag};

+ 20 - 9
multiboot2/src/elf_sections.rs

@@ -1,14 +1,13 @@
 //! Module for [`ElfSectionsTag`].
 
-use crate::{TagHeader, TagTrait, TagType};
+use crate::{TagHeader, TagType};
 use core::fmt::{Debug, Formatter};
 use core::marker::PhantomData;
 use core::mem;
 use core::str::Utf8Error;
+use multiboot2_common::{MaybeDynSized, Tag};
 #[cfg(feature = "builder")]
-use {crate::new_boxed, alloc::boxed::Box};
-
-const METADATA_SIZE: usize = mem::size_of::<TagHeader>() + 3 * mem::size_of::<u32>();
+use {alloc::boxed::Box, multiboot2_common::new_boxed};
 
 /// This tag contains the section header table from an ELF binary.
 // The sections iterator is provided via the [`ElfSectionsTag::sections`]
@@ -28,10 +27,14 @@ impl ElfSectionsTag {
     #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(number_of_sections: u32, entry_size: u32, shndx: u32, sections: &[u8]) -> Box<Self> {
+        let header = TagHeader::new(Self::ID, 0);
         let number_of_sections = number_of_sections.to_ne_bytes();
         let entry_size = entry_size.to_ne_bytes();
         let shndx = shndx.to_ne_bytes();
-        new_boxed(&[&number_of_sections, &entry_size, &shndx, sections])
+        new_boxed(
+            header,
+            &[&number_of_sections, &entry_size, &shndx, sections],
+        )
     }
 
     /// Get an iterator of loaded ELF sections.
@@ -68,15 +71,23 @@ impl ElfSectionsTag {
     }
 }
 
-impl TagTrait for ElfSectionsTag {
-    const ID: TagType = TagType::ElfSections;
+impl MaybeDynSized for ElfSectionsTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<TagHeader>() + 3 * mem::size_of::<u32>();
 
     fn dst_len(header: &TagHeader) -> usize {
-        assert!(header.size as usize >= METADATA_SIZE);
-        header.size as usize - METADATA_SIZE
+        assert!(header.size as usize >= Self::BASE_SIZE);
+        header.size as usize - Self::BASE_SIZE
     }
 }
 
+impl Tag for ElfSectionsTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::ElfSections;
+}
+
 impl Debug for ElfSectionsTag {
     fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
         f.debug_struct("ElfSectionsTag")

+ 13 - 3
multiboot2/src/end.rs

@@ -1,6 +1,8 @@
 //! Module for [`EndTag`].
 
-use crate::{TagHeader, TagTrait, TagType, TagTypeId};
+use crate::{TagHeader, TagType, TagTypeId};
+use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// The end tag ends the information struct.
 #[derive(Debug)]
@@ -19,12 +21,20 @@ impl Default for EndTag {
     }
 }
 
-impl TagTrait for EndTag {
-    const ID: TagType = TagType::End;
+impl MaybeDynSized for EndTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
+impl Tag for EndTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::End;
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;

+ 191 - 77
multiboot2/src/framebuffer.rs

@@ -1,53 +1,50 @@
 //! Module for [`FramebufferTag`].
 
 use crate::tag::TagHeader;
-use crate::{TagTrait, TagType, TagTypeId};
+use crate::TagType;
 use core::fmt::Debug;
 use core::mem;
 use core::slice;
 use derive_more::Display;
+use multiboot2_common::{MaybeDynSized, Tag};
 #[cfg(feature = "builder")]
-use {crate::builder::AsBytes, crate::new_boxed, alloc::boxed::Box, alloc::vec::Vec};
+use {alloc::boxed::Box, multiboot2_common::new_boxed};
 
 /// Helper struct to read bytes from a raw pointer and increase the pointer
 /// automatically.
-struct Reader {
-    ptr: *const u8,
+struct Reader<'a> {
+    buffer: &'a [u8],
     off: usize,
 }
 
-impl Reader {
-    const fn new<T>(ptr: *const T) -> Self {
-        Self {
-            ptr: ptr as *const u8,
-            off: 0,
-        }
+impl<'a> Reader<'a> {
+    const fn new(buffer: &'a [u8]) -> Self {
+        Self { buffer, off: 0 }
     }
 
     fn read_u8(&mut self) -> u8 {
+        let val = self
+            .buffer
+            .get(self.off)
+            .cloned()
+            // This is not a solution I'm proud of, but at least it is safe.
+            // The whole framebuffer tag code originally is not from me.
+            // I hope someone from the community wants to improve this overall
+            // functionality someday.
+            .expect("Embedded framebuffer info should be properly sized and available");
         self.off += 1;
-        unsafe { *self.ptr.add(self.off - 1) }
+        val
     }
 
     fn read_u16(&mut self) -> u16 {
         self.read_u8() as u16 | (self.read_u8() as u16) << 8
     }
 
-    fn read_u32(&mut self) -> u32 {
-        self.read_u16() as u32 | (self.read_u16() as u32) << 16
-    }
-
-    fn current_address(&self) -> usize {
-        unsafe { self.ptr.add(self.off) as usize }
+    const fn current_ptr(&self) -> *const u8 {
+        unsafe { self.buffer.as_ptr().add(self.off) }
     }
 }
 
-const METADATA_SIZE: usize = mem::size_of::<TagTypeId>()
-    + 4 * mem::size_of::<u32>()
-    + mem::size_of::<u64>()
-    + mem::size_of::<u16>()
-    + 2 * mem::size_of::<u8>();
-
 /// The VBE Framebuffer information tag.
 #[derive(ptr_meta::Pointee, Eq)]
 #[repr(C, align(8))]
@@ -73,13 +70,16 @@ pub struct FramebufferTag {
     /// Contains number of bits per pixel.
     bpp: u8,
 
-    /// The type of framebuffer, one of: `Indexed`, `RGB` or `Text`.
-    type_no: u8,
+    /// The type of framebuffer. See [`FramebufferTypeId`].
+    // TODO: Strictly speaking this causes UB for invalid values. However, no
+    //  sane bootloader puts something illegal there at the moment. When we
+    //  refactor this (newtype pattern?), we should also streamline other
+    //  parts in the code base accordingly.
+    framebuffer_type: FramebufferTypeId,
 
-    // In the multiboot spec, it has this listed as a u8 _NOT_ a u16.
-    // Reading the GRUB2 source code reveals it is in fact a u16.
-    _reserved: u16,
+    _padding: u16,
 
+    /// This optional data and its meaning depend on the [`FramebufferTypeId`].
     buffer: [u8],
 }
 
@@ -95,13 +95,27 @@ impl FramebufferTag {
         bpp: u8,
         buffer_type: FramebufferType,
     ) -> Box<Self> {
+        let header = TagHeader::new(Self::ID, 0);
         let address = address.to_ne_bytes();
         let pitch = pitch.to_ne_bytes();
         let width = width.to_ne_bytes();
         let height = height.to_ne_bytes();
-        let bpp = bpp.to_ne_bytes();
-        let buffer_type = buffer_type.to_bytes();
-        new_boxed(&[&address, &pitch, &width, &height, &bpp, &buffer_type])
+        let buffer_type_id = buffer_type.id();
+        let padding = [0; 2];
+        let optional_buffer = buffer_type.serialize();
+        new_boxed(
+            header,
+            &[
+                &address,
+                &pitch,
+                &width,
+                &height,
+                &[bpp],
+                &[buffer_type_id as u8],
+                &padding,
+                &optional_buffer,
+            ],
+        )
     }
 
     /// Contains framebuffer physical address.
@@ -140,18 +154,31 @@ impl FramebufferTag {
 
     /// The type of framebuffer, one of: `Indexed`, `RGB` or `Text`.
     pub fn buffer_type(&self) -> Result<FramebufferType, UnknownFramebufferType> {
-        let mut reader = Reader::new(self.buffer.as_ptr());
-        let typ = FramebufferTypeId::try_from(self.type_no)?;
-        match typ {
+        let mut reader = Reader::new(&self.buffer);
+
+        // TODO: We should use the newtype pattern instead or so to properly
+        //  solve this.
+        let fb_type_raw = self.framebuffer_type as u8;
+        let fb_type = FramebufferTypeId::try_from(fb_type_raw)?;
+
+        match fb_type {
             FramebufferTypeId::Indexed => {
-                let num_colors = reader.read_u32();
-                // TODO static cast looks like UB?
-                let palette = unsafe {
-                    slice::from_raw_parts(
-                        reader.current_address() as *const FramebufferColor,
-                        num_colors as usize,
-                    )
-                } as &'static [FramebufferColor];
+                // TODO we can create a struct for this and implement
+                //  DynSizedStruct for it to leverage the already existing
+                //  functionality
+                let num_colors = reader.read_u16();
+
+                let palette = {
+                    // Ensure the slice can be created without causing UB
+                    assert_eq!(mem::size_of::<FramebufferColor>(), 3);
+
+                    unsafe {
+                        slice::from_raw_parts(
+                            reader.current_ptr().cast::<FramebufferColor>(),
+                            num_colors as usize,
+                        )
+                    }
+                };
                 Ok(FramebufferType::Indexed { palette })
             }
             FramebufferTypeId::RGB => {
@@ -181,15 +208,27 @@ impl FramebufferTag {
     }
 }
 
-impl TagTrait for FramebufferTag {
-    const ID: TagType = TagType::Framebuffer;
+impl MaybeDynSized for FramebufferTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<TagHeader>()
+        + mem::size_of::<u64>()
+        + 3 * mem::size_of::<u32>()
+        + 2 * mem::size_of::<u8>()
+        + mem::size_of::<u16>();
 
     fn dst_len(header: &TagHeader) -> usize {
-        assert!(header.size as usize >= METADATA_SIZE);
-        header.size as usize - METADATA_SIZE
+        assert!(header.size as usize >= Self::BASE_SIZE);
+        header.size as usize - Self::BASE_SIZE
     }
 }
 
+impl Tag for FramebufferTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Framebuffer;
+}
+
 impl Debug for FramebufferTag {
     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
         f.debug_struct("FramebufferTag")
@@ -213,16 +252,16 @@ impl PartialEq for FramebufferTag {
             && self.width == { other.width }
             && self.height == { other.height }
             && self.bpp == { other.bpp }
-            && self.type_no == { other.type_no }
+            && self.framebuffer_type == { other.framebuffer_type }
             && self.buffer == other.buffer
     }
 }
 
-/// Helper struct for [`FramebufferType`].
+/// ABI-compatible framebuffer type.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[repr(u8)]
 #[allow(clippy::upper_case_acronyms)]
-enum FramebufferTypeId {
+pub enum FramebufferTypeId {
     Indexed = 0,
     RGB = 1,
     Text = 2,
@@ -242,7 +281,18 @@ impl TryFrom<u8> for FramebufferTypeId {
     }
 }
 
-/// The type of framebuffer.
+impl From<FramebufferType<'_>> for FramebufferTypeId {
+    fn from(value: FramebufferType) -> Self {
+        match value {
+            FramebufferType::Indexed { .. } => Self::Indexed,
+            FramebufferType::RGB { .. } => Self::RGB,
+            FramebufferType::Text => Self::Text,
+        }
+    }
+}
+
+/// Structured accessory to the provided framebuffer type that is not ABI
+/// compatible.
 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum FramebufferType<'a> {
     /// Indexed color.
@@ -269,32 +319,44 @@ pub enum FramebufferType<'a> {
     Text,
 }
 
-#[cfg(feature = "builder")]
 impl<'a> FramebufferType<'a> {
-    fn to_bytes(&self) -> Vec<u8> {
-        let mut v = Vec::new();
+    #[must_use]
+    #[cfg(feature = "builder")]
+    const fn id(&self) -> FramebufferTypeId {
+        match self {
+            FramebufferType::Indexed { .. } => FramebufferTypeId::Indexed,
+            FramebufferType::RGB { .. } => FramebufferTypeId::RGB,
+            FramebufferType::Text => FramebufferTypeId::Text,
+        }
+    }
+
+    #[must_use]
+    #[cfg(feature = "builder")]
+    fn serialize(&self) -> alloc::vec::Vec<u8> {
+        let mut data = alloc::vec::Vec::new();
         match self {
             FramebufferType::Indexed { palette } => {
-                v.extend(0u8.to_ne_bytes()); // type
-                v.extend(0u16.to_ne_bytes()); // reserved
-                v.extend((palette.len() as u32).to_ne_bytes());
-                for color in palette.iter() {
-                    v.extend(color.as_bytes());
+                // TODO we can create a struct for this and implement
+                //  DynSizedStruct for it to leverage the already existing
+                //  functionality
+                let num_colors = palette.len() as u16;
+                data.extend(&num_colors.to_ne_bytes());
+                for color in *palette {
+                    let serialized_color = [color.red, color.green, color.blue];
+                    data.extend(&serialized_color);
                 }
             }
-            FramebufferType::RGB { red, green, blue } => {
-                v.extend(1u8.to_ne_bytes()); // type
-                v.extend(0u16.to_ne_bytes()); // reserved
-                v.extend(red.as_bytes());
-                v.extend(green.as_bytes());
-                v.extend(blue.as_bytes());
-            }
-            FramebufferType::Text => {
-                v.extend(2u8.to_ne_bytes()); // type
-                v.extend(0u16.to_ne_bytes()); // reserved
-            }
+            FramebufferType::RGB { red, green, blue } => data.extend(&[
+                red.position,
+                red.size,
+                green.position,
+                green.size,
+                blue.position,
+                blue.size,
+            ]),
+            FramebufferType::Text => {}
         }
-        v
+        data
     }
 }
 
@@ -309,10 +371,9 @@ pub struct FramebufferField {
     pub size: u8,
 }
 
-#[cfg(feature = "builder")]
-impl AsBytes for FramebufferField {}
-
-/// A framebuffer color descriptor in the palette.
+/// A framebuffer color descriptor in the palette. On the ABI level, multiple
+/// values are consecutively without padding bytes. The spec is not precise in
+/// that regard, but looking at Limine's and GRUB's source code confirm that.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[repr(C)] // no align(8) here is correct
 pub struct FramebufferColor {
@@ -326,9 +387,6 @@ pub struct FramebufferColor {
     pub blue: u8,
 }
 
-#[cfg(feature = "builder")]
-impl AsBytes for FramebufferColor {}
-
 /// Error when an unknown [`FramebufferTypeId`] is found.
 #[derive(Debug, Copy, Clone, Display, PartialEq, Eq)]
 #[display(fmt = "Unknown framebuffer type {}", _0)]
@@ -346,4 +404,60 @@ mod tests {
     fn test_size() {
         assert_eq!(mem::size_of::<FramebufferColor>(), 3)
     }
+
+    #[test]
+    #[cfg(feature = "builder")]
+    fn create_new() {
+        let tag = FramebufferTag::new(0x1000, 1, 1024, 1024, 8, FramebufferType::Text);
+        // Good test for Miri
+        dbg!(tag);
+
+        let tag = FramebufferTag::new(
+            0x1000,
+            1,
+            1024,
+            1024,
+            8,
+            FramebufferType::Indexed {
+                palette: &[
+                    FramebufferColor {
+                        red: 255,
+                        green: 255,
+                        blue: 255,
+                    },
+                    FramebufferColor {
+                        red: 127,
+                        green: 42,
+                        blue: 73,
+                    },
+                ],
+            },
+        );
+        // Good test for Miri
+        dbg!(tag);
+
+        let tag = FramebufferTag::new(
+            0x1000,
+            1,
+            1024,
+            1024,
+            8,
+            FramebufferType::RGB {
+                red: FramebufferField {
+                    position: 0,
+                    size: 0,
+                },
+                green: FramebufferField {
+                    position: 10,
+                    size: 20,
+                },
+                blue: FramebufferField {
+                    position: 30,
+                    size: 40,
+                },
+            },
+        );
+        // Good test for Miri
+        dbg!(tag);
+    }
 }

+ 14 - 5
multiboot2/src/image_load_addr.rs

@@ -1,9 +1,10 @@
 //! Module for [`ImageLoadPhysAddrTag`].
 
 use crate::tag::TagHeader;
-use crate::{TagTrait, TagType};
+use crate::TagType;
 #[cfg(feature = "builder")]
 use core::mem::size_of;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// The physical load address tag. Typically, this is only available if the
 /// binary was relocated, for example if the relocatable header tag was
@@ -16,12 +17,13 @@ pub struct ImageLoadPhysAddrTag {
 }
 
 impl ImageLoadPhysAddrTag {
+    const BASE_SIZE: usize = size_of::<TagHeader>() + size_of::<u32>();
+
     /// Constructs a new tag.
-    #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(load_base_addr: u32) -> Self {
         Self {
-            header: TagHeader::new(Self::ID, size_of::<Self>().try_into().unwrap()),
+            header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32),
             load_base_addr,
         }
     }
@@ -32,13 +34,20 @@ impl ImageLoadPhysAddrTag {
         self.load_base_addr
     }
 }
+impl MaybeDynSized for ImageLoadPhysAddrTag {
+    type Header = TagHeader;
 
-impl TagTrait for ImageLoadPhysAddrTag {
-    const ID: TagType = TagType::LoadBaseAddr;
+    const BASE_SIZE: usize = size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
+impl Tag for ImageLoadPhysAddrTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::LoadBaseAddr;
+}
+
 #[cfg(all(test, feature = "builder"))]
 mod tests {
     use super::ImageLoadPhysAddrTag;

+ 54 - 27
multiboot2/src/lib.rs

@@ -43,6 +43,7 @@
 //! ## MSRV
 //! The MSRV is 1.70.0 stable.
 
+#[cfg_attr(feature = "builder", macro_use)]
 #[cfg(feature = "builder")]
 extern crate alloc;
 
@@ -55,9 +56,14 @@ extern crate std;
 extern crate bitflags;
 
 #[cfg(feature = "builder")]
-pub mod builder;
+mod builder;
+
+/// Iterator over the tags of a Multiboot2 boot information.
+pub type TagIter<'a> = multiboot2_common::TagIter<'a, TagHeader>;
+
+/// A generic version of all boot information tags.
 #[cfg(test)]
-pub(crate) mod test_util;
+pub type GenericInfoTag = multiboot2_common::DynSizedStructure<TagHeader>;
 
 mod boot_information;
 mod boot_loader_name;
@@ -72,13 +78,16 @@ mod module;
 mod rsdp;
 mod smbios;
 mod tag;
-mod tag_trait;
 mod tag_type;
 pub(crate) mod util;
 mod vbe_info;
 
-pub use boot_information::{BootInformation, BootInformationHeader, MbiLoadError};
+pub use multiboot2_common::{DynSizedStructure, MaybeDynSized, Tag};
+
+pub use boot_information::{BootInformation, BootInformationHeader, LoadError};
 pub use boot_loader_name::BootLoaderNameTag;
+#[cfg(feature = "builder")]
+pub use builder::Builder;
 pub use command_line::CommandLineTag;
 pub use efi::{
     EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFISdt32Tag, EFISdt64Tag,
@@ -98,10 +107,7 @@ pub use ptr_meta::Pointee;
 pub use rsdp::{RsdpV1Tag, RsdpV2Tag};
 pub use smbios::SmbiosTag;
 pub use tag::TagHeader;
-pub use tag_trait::TagTrait;
 pub use tag_type::{TagType, TagTypeId};
-#[cfg(feature = "alloc")]
-pub use util::new_boxed;
 pub use util::{parse_slice_as_string, StringError};
 pub use vbe_info::{
     VBECapabilities, VBEControlInfo, VBEDirectColorAttributes, VBEField, VBEInfoTag,
@@ -113,13 +119,12 @@ pub use vbe_info::{
 /// machine state.
 pub const MAGIC: u32 = 0x36d76289;
 
-/// The required alignment for tags and the boot information.
-pub const ALIGNMENT: usize = 8;
-
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::test_util::AlignedBytes;
+    use multiboot2_common::test_utils::AlignedBytes;
+    use multiboot2_common::{MaybeDynSized, Tag};
+    use std::mem;
 
     /// Compile time test to check if the boot information is Send and Sync.
     /// This test is relevant to give library users flexebility in passing the
@@ -270,7 +275,6 @@ mod tests {
         assert_eq!(addr, bi.start_address());
         assert_eq!(addr + bytes.0.len(), bi.end_address());
         assert_eq!(bytes.0.len(), bi.total_size());
-        use framebuffer::{FramebufferField, FramebufferType};
         assert!(bi.framebuffer_tag().is_some());
         let fbi = bi
             .framebuffer_tag()
@@ -301,11 +305,11 @@ mod tests {
     }
 
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn framebuffer_tag_indexed() {
         // indexed mode test:
         // this is synthetic, as I can't get QEMU
         // to run in indexed color mode.
+        #[rustfmt::skip]
         let bytes = AlignedBytes([
             64, 0, 0, 0, // total size
             0, 0, 0, 0, // reserved
@@ -316,10 +320,16 @@ mod tests {
             0, 20, 0, 0, // framebuffer pitch
             0, 5, 0, 0, // framebuffer width
             208, 2, 0, 0, // framebuffer height
-            32, 0, 0, 0, // framebuffer bpp, type, reserved word
-            4, 0, 0, 0, // framebuffer palette length
-            255, 0, 0, 0, // framebuffer palette
-            255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, // end tag type
+            32, // framebuffer bpp
+            0, // framebuffer type
+            0, 0, // reserved word
+            4, 0, // framebuffer palette length
+            255, 0, 0, // framebuffer palette: 1/3
+            0, 255, 0, // framebuffer palette: 2/3
+            0, 0, 255, // framebuffer palette: 3/3
+            3, 7, 73, // framebuffer palette: 4/4
+            0, 0, // padding  for 8-byte alignment
+            0, 0, 0, 0, // end tag type
             8, 0, 0, 0, // end tag size
         ]);
         let ptr = bytes.0.as_ptr();
@@ -329,7 +339,6 @@ mod tests {
         assert_eq!(addr, bi.start_address());
         assert_eq!(addr + bytes.0.len(), bi.end_address());
         assert_eq!(bytes.0.len(), bi.total_size());
-        use framebuffer::{FramebufferColor, FramebufferType};
         assert!(bi.framebuffer_tag().is_some());
         let fbi = bi
             .framebuffer_tag()
@@ -360,9 +369,9 @@ mod tests {
                         blue: 255,
                     },
                     FramebufferColor {
-                        red: 0,
-                        green: 0,
-                        blue: 0,
+                        red: 3,
+                        green: 7,
+                        blue: 73,
                     }
                 ]
             ),
@@ -1089,7 +1098,7 @@ mod tests {
     /// This test succeeds if it compiles.
     fn mbi_load_error_implements_error() {
         fn consumer<E: core::error::Error>(_e: E) {}
-        consumer(MbiLoadError::IllegalAddress)
+        consumer(LoadError::NoEndTag)
     }
 
     /// Example for a custom tag.
@@ -1102,11 +1111,20 @@ mod tests {
             foo: u32,
         }
 
-        impl TagTrait for CustomTag {
-            const ID: TagType = TagType::Custom(0x1337);
+        impl MaybeDynSized for CustomTag {
+            type Header = TagHeader;
+
+            const BASE_SIZE: usize = mem::size_of::<Self>();
 
-            fn dst_len(_tag_header: &TagHeader) {}
+            fn dst_len(_: &TagHeader) -> Self::Metadata {}
         }
+
+        impl Tag for CustomTag {
+            type IDType = TagType;
+
+            const ID: TagType = TagType::Custom(0x1337);
+        }
+
         // Raw bytes of a MBI that only contains the custom tag.
         let bytes = AlignedBytes([
             32,
@@ -1171,8 +1189,10 @@ mod tests {
             }
         }
 
-        impl TagTrait for CustomTag {
-            const ID: TagType = TagType::Custom(0x1337);
+        impl MaybeDynSized for CustomTag {
+            type Header = TagHeader;
+
+            const BASE_SIZE: usize = mem::size_of::<TagHeader>() + mem::size_of::<u32>();
 
             fn dst_len(header: &TagHeader) -> usize {
                 // The size of the sized portion of the command line tag.
@@ -1181,6 +1201,13 @@ mod tests {
                 header.size as usize - tag_base_size
             }
         }
+
+        impl Tag for CustomTag {
+            type IDType = TagType;
+
+            const ID: TagType = TagType::Custom(0x1337);
+        }
+
         // Raw bytes of a MBI that only contains the custom tag.
         let bytes = AlignedBytes([
             32,

+ 53 - 20
multiboot2/src/memory_map.rs

@@ -6,14 +6,13 @@ pub use uefi_raw::table::boot::MemoryDescriptor as EFIMemoryDesc;
 pub use uefi_raw::table::boot::MemoryType as EFIMemoryAreaType;
 
 use crate::tag::TagHeader;
-use crate::{TagTrait, TagType, TagTypeId};
+use crate::{TagType, TagTypeId};
 use core::fmt::{Debug, Formatter};
 use core::marker::PhantomData;
 use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 #[cfg(feature = "builder")]
-use {crate::new_boxed, alloc::boxed::Box, core::slice};
-
-const METADATA_SIZE: usize = mem::size_of::<TagHeader>() + 2 * mem::size_of::<u32>();
+use {alloc::boxed::Box, core::slice, multiboot2_common::new_boxed};
 
 /// This tag provides an initial host memory map (legacy boot, not UEFI).
 ///
@@ -39,14 +38,15 @@ impl MemoryMapTag {
     #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(areas: &[MemoryArea]) -> Box<Self> {
-        let entry_size = mem::size_of::<MemoryArea>().to_ne_bytes();
+        let header = TagHeader::new(Self::ID, 0);
+        let entry_size = (mem::size_of::<MemoryArea>() as u32).to_ne_bytes();
         let entry_version = 0_u32.to_ne_bytes();
         let areas = {
             let ptr = areas.as_ptr().cast::<u8>();
             let len = mem::size_of_val(areas);
             unsafe { slice::from_raw_parts(ptr, len) }
         };
-        new_boxed(&[&entry_size, &entry_version, areas])
+        new_boxed(header, &[&entry_size, &entry_version, areas])
     }
 
     /// Returns the entry size.
@@ -73,17 +73,25 @@ impl MemoryMapTag {
     }
 }
 
-impl TagTrait for MemoryMapTag {
-    const ID: TagType = TagType::Mmap;
+impl MaybeDynSized for MemoryMapTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<TagHeader>() + 2 * mem::size_of::<u32>();
 
     fn dst_len(header: &TagHeader) -> usize {
-        assert!(header.size as usize >= METADATA_SIZE);
-        let size = header.size as usize - METADATA_SIZE;
+        assert!(header.size as usize >= Self::BASE_SIZE);
+        let size = header.size as usize - Self::BASE_SIZE;
         assert_eq!(size % mem::size_of::<MemoryArea>(), 0);
         size / mem::size_of::<MemoryArea>()
     }
 }
 
+impl Tag for MemoryMapTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Mmap;
+}
+
 /// A descriptor for an available or taken area of physical memory.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[repr(C)]
@@ -282,13 +290,19 @@ impl BasicMemoryInfoTag {
     }
 }
 
-impl TagTrait for BasicMemoryInfoTag {
-    const ID: TagType = TagType::BasicMeminfo;
+impl MaybeDynSized for BasicMemoryInfoTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
-const EFI_METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + 3 * mem::size_of::<u32>();
+impl Tag for BasicMemoryInfoTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::BasicMeminfo;
+}
 
 /// EFI memory map tag. The embedded [`EFIMemoryDesc`]s follows the EFI
 /// specification.
@@ -337,10 +351,11 @@ impl EFIMemoryMapTag {
     #[cfg(feature = "builder")]
     #[must_use]
     pub fn new_from_map(desc_size: u32, desc_version: u32, efi_mmap: &[u8]) -> Box<Self> {
+        let header = TagHeader::new(Self::ID, 0);
         assert_ne!(desc_size, 0);
         let desc_size = desc_size.to_ne_bytes();
         let desc_version = desc_version.to_ne_bytes();
-        new_boxed(&[&desc_size, &desc_version, efi_mmap])
+        new_boxed(header, &[&desc_size, &desc_version, efi_mmap])
     }
 
     /// Returns an iterator over the provided memory areas.
@@ -376,15 +391,23 @@ impl Debug for EFIMemoryMapTag {
     }
 }
 
-impl TagTrait for EFIMemoryMapTag {
-    const ID: TagType = TagType::EfiMmap;
+impl MaybeDynSized for EFIMemoryMapTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<TagTypeId>() + 3 * mem::size_of::<u32>();
 
     fn dst_len(header: &TagHeader) -> usize {
-        assert!(header.size as usize >= EFI_METADATA_SIZE);
-        header.size as usize - EFI_METADATA_SIZE
+        assert!(header.size as usize >= Self::BASE_SIZE);
+        header.size as usize - Self::BASE_SIZE
     }
 }
 
+impl Tag for EFIMemoryMapTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::EfiMmap;
+}
+
 /// An iterator over the EFI memory areas emitting [`EFIMemoryDesc`] items.
 #[derive(Clone, Debug)]
 pub struct EFIMemoryAreaIter<'a> {
@@ -443,7 +466,17 @@ mod tests {
     use std::mem::size_of;
 
     #[test]
-    fn construction_and_parsing() {
+    fn test_create_old_mmap() {
+        let _mmap = MemoryMapTag::new(&[]);
+        let mmap = MemoryMapTag::new(&[
+            MemoryArea::new(0x1000, 0x2000, MemoryAreaType::Available),
+            MemoryArea::new(0x2000, 0x3000, MemoryAreaType::Available),
+        ]);
+        dbg!(mmap);
+    }
+
+    #[test]
+    fn efi_construct_and_parse() {
         let descs = [
             EFIMemoryDesc {
                 ty: EFIMemoryAreaType::CONVENTIONAL,
@@ -474,7 +507,7 @@ mod tests {
     /// This is taken from the uefi-rs repository. See
     /// <https://github.com/rust-osdev/uefi-rs/pull/1175> for more info.
     #[test]
-    fn test_real_data() {
+    fn efi_test_real_data() {
         const DESC_SIZE: u32 = 48;
         const DESC_VERSION: u32 = 1;
         /// Sample with 10 entries of a real UEFI memory map extracted from our

+ 28 - 19
multiboot2/src/module.rs

@@ -1,13 +1,12 @@
 //! Module for [`ModuleTag`].
 
-use crate::tag::{TagHeader, TagIter};
-use crate::{parse_slice_as_string, StringError, TagTrait, TagType};
+use crate::tag::TagHeader;
+use crate::{parse_slice_as_string, StringError, TagIter, TagType};
 use core::fmt::{Debug, Formatter};
 use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 #[cfg(feature = "builder")]
-use {crate::new_boxed, alloc::boxed::Box};
-
-const METADATA_SIZE: usize = mem::size_of::<TagHeader>() + 2 * mem::size_of::<u32>();
+use {alloc::boxed::Box, multiboot2_common::new_boxed};
 
 /// The module tag can occur multiple times and specifies passed boot modules
 /// (blobs in memory). The tag itself doesn't include the blog, but references
@@ -27,6 +26,7 @@ impl ModuleTag {
     #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(start: u32, end: u32, cmdline: &str) -> Box<Self> {
+        let header = TagHeader::new(Self::ID, 0);
         assert!(end > start, "must have a size");
 
         let start = start.to_ne_bytes();
@@ -34,9 +34,9 @@ impl ModuleTag {
         let cmdline = cmdline.as_bytes();
 
         if cmdline.ends_with(&[0]) {
-            new_boxed(&[&start, &end, cmdline])
+            new_boxed(header, &[&start, &end, cmdline])
         } else {
-            new_boxed(&[&start, &end, cmdline, &[0]])
+            new_boxed(header, &[&start, &end, cmdline, &[0]])
         }
     }
 
@@ -72,15 +72,23 @@ impl ModuleTag {
     }
 }
 
-impl TagTrait for ModuleTag {
-    const ID: TagType = TagType::Module;
+impl MaybeDynSized for ModuleTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<TagHeader>() + 2 * mem::size_of::<u32>();
 
     fn dst_len(header: &TagHeader) -> usize {
-        assert!(header.size as usize >= METADATA_SIZE);
-        header.size as usize - METADATA_SIZE
+        assert!(header.size as usize >= Self::BASE_SIZE);
+        header.size as usize - Self::BASE_SIZE
     }
 }
 
+impl Tag for ModuleTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Module;
+}
+
 impl Debug for ModuleTag {
     fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
         f.debug_struct("ModuleTag")
@@ -128,9 +136,9 @@ impl<'a> Debug for ModuleIter<'a> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::tag::{GenericTag, TagBytesRef};
-    use crate::test_util::AlignedBytes;
+    use crate::GenericInfoTag;
     use core::borrow::Borrow;
+    use multiboot2_common::test_utils::AlignedBytes;
 
     #[rustfmt::skip]
     fn get_bytes() -> AlignedBytes<24> {
@@ -151,8 +159,7 @@ mod tests {
     #[test]
     fn test_parse_str() {
         let bytes = get_bytes();
-        let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
-        let tag = GenericTag::ref_from(bytes);
+        let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap();
         let tag = tag.cast::<ModuleTag>();
         assert_eq!(tag.header.typ, TagType::Module);
         assert_eq!(tag.cmdline(), Ok("hello"));
@@ -163,14 +170,16 @@ mod tests {
     #[cfg(feature = "builder")]
     fn test_build_str() {
         let tag = ModuleTag::new(0xff00, 0xffff, "hello");
-        let bytes = tag.as_bytes();
-        assert_eq!(bytes, &get_bytes()[..tag.size()]);
+        let bytes = tag.as_bytes().as_ref();
+        let bytes = &bytes[..tag.header.size as usize];
+        assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]);
         assert_eq!(tag.cmdline(), Ok("hello"));
 
         // With terminating null.
         let tag = ModuleTag::new(0xff00, 0xffff, "hello\0");
-        let bytes = tag.as_bytes();
-        assert_eq!(bytes, &get_bytes()[..tag.size()]);
+        let bytes = tag.as_bytes().as_ref();
+        let bytes = &bytes[..tag.header.size as usize];
+        assert_eq!(bytes, &get_bytes()[..tag.header().size as usize]);
         assert_eq!(tag.cmdline(), Ok("hello"));
 
         // test also some bigger message

+ 38 - 19
multiboot2/src/rsdp.rs

@@ -13,12 +13,13 @@
 //!
 
 use crate::tag::TagHeader;
-use crate::{TagTrait, TagType};
+use crate::TagType;
 #[cfg(feature = "builder")]
 use core::mem::size_of;
 use core::slice;
 use core::str;
 use core::str::Utf8Error;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 const RSDPV1_LENGTH: usize = 20;
 
@@ -35,19 +36,17 @@ pub struct RsdpV1Tag {
 }
 
 impl RsdpV1Tag {
+    /// Signature of RSDP v1.
+    pub const SIGNATURE: [u8; 8] = *b"RSD PTR ";
+
+    const BASE_SIZE: usize = size_of::<TagHeader>() + 16 + 4;
+
     /// Constructs a new tag.
-    #[cfg(feature = "builder")]
     #[must_use]
-    pub fn new(
-        signature: [u8; 8],
-        checksum: u8,
-        oem_id: [u8; 6],
-        revision: u8,
-        rsdt_address: u32,
-    ) -> Self {
+    pub fn new(checksum: u8, oem_id: [u8; 6], revision: u8, rsdt_address: u32) -> Self {
         Self {
-            header: TagHeader::new(Self::ID, size_of::<Self>().try_into().unwrap()),
-            signature,
+            header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32),
+            signature: Self::SIGNATURE,
             checksum,
             oem_id,
             revision,
@@ -91,12 +90,20 @@ impl RsdpV1Tag {
     }
 }
 
-impl TagTrait for RsdpV1Tag {
-    const ID: TagType = TagType::AcpiV1;
+impl MaybeDynSized for RsdpV1Tag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
+impl Tag for RsdpV1Tag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::AcpiV1;
+}
+
 /// This tag contains a copy of RSDP as defined per ACPI 2.0 or later specification.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[repr(C, align(8))]
@@ -115,12 +122,16 @@ pub struct RsdpV2Tag {
 }
 
 impl RsdpV2Tag {
+    /// Signature of RSDP v2.
+    pub const SIGNATURE: [u8; 8] = *b"RSD PTR ";
+
+    const BASE_SIZE: usize =
+        size_of::<TagHeader>() + 16 + 2 * size_of::<u32>() + size_of::<u64>() + 4;
+
     /// Constructs a new tag.
-    #[cfg(feature = "builder")]
     #[allow(clippy::too_many_arguments)]
     #[must_use]
     pub fn new(
-        signature: [u8; 8],
         checksum: u8,
         oem_id: [u8; 6],
         revision: u8,
@@ -130,8 +141,8 @@ impl RsdpV2Tag {
         ext_checksum: u8,
     ) -> Self {
         Self {
-            header: TagHeader::new(Self::ID, size_of::<Self>().try_into().unwrap()),
-            signature,
+            header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32),
+            signature: Self::SIGNATURE,
             checksum,
             oem_id,
             revision,
@@ -188,8 +199,16 @@ impl RsdpV2Tag {
     }
 }
 
-impl TagTrait for RsdpV2Tag {
-    const ID: TagType = TagType::AcpiV2;
+impl MaybeDynSized for RsdpV2Tag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
+
+impl Tag for RsdpV2Tag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::AcpiV2;
+}

+ 23 - 15
multiboot2/src/smbios.rs

@@ -1,13 +1,12 @@
 //! Module for [`SmbiosTag`].
 
 use crate::tag::TagHeader;
-use crate::{TagTrait, TagType};
+use crate::TagType;
 use core::fmt::Debug;
 use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 #[cfg(feature = "builder")]
-use {crate::new_boxed, alloc::boxed::Box};
-
-const METADATA_SIZE: usize = mem::size_of::<TagHeader>() + mem::size_of::<u8>() * 8;
+use {alloc::boxed::Box, multiboot2_common::new_boxed};
 
 /// This tag contains a copy of SMBIOS tables as well as their version.
 #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -25,8 +24,9 @@ impl SmbiosTag {
     #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(major: u8, minor: u8, tables: &[u8]) -> Box<Self> {
+        let header = TagHeader::new(Self::ID, 0);
         let reserved = [0, 0, 0, 0, 0, 0];
-        new_boxed(&[&[major, minor], &reserved, tables])
+        new_boxed(header, &[&[major, minor], &reserved, tables])
     }
 
     /// Returns the major number.
@@ -48,15 +48,23 @@ impl SmbiosTag {
     }
 }
 
-impl TagTrait for SmbiosTag {
-    const ID: TagType = TagType::Smbios;
+impl MaybeDynSized for SmbiosTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<TagHeader>() + mem::size_of::<u8>() * 8;
 
     fn dst_len(header: &TagHeader) -> usize {
-        assert!(header.size as usize >= METADATA_SIZE);
-        header.size as usize - METADATA_SIZE
+        assert!(header.size as usize >= Self::BASE_SIZE);
+        header.size as usize - Self::BASE_SIZE
     }
 }
 
+impl Tag for SmbiosTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Smbios;
+}
+
 impl Debug for SmbiosTag {
     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
         f.debug_struct("BootLoaderNameTag")
@@ -71,9 +79,9 @@ impl Debug for SmbiosTag {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::tag::{GenericTag, TagBytesRef};
-    use crate::test_util::AlignedBytes;
+    use crate::GenericInfoTag;
     use core::borrow::Borrow;
+    use multiboot2_common::test_utils::AlignedBytes;
 
     #[rustfmt::skip]
     fn get_bytes() -> AlignedBytes<32> {
@@ -97,8 +105,7 @@ mod tests {
     #[test]
     fn test_parse() {
         let bytes = get_bytes();
-        let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
-        let tag = GenericTag::ref_from(bytes);
+        let tag = GenericInfoTag::ref_from_slice(bytes.borrow()).unwrap();
         let tag = tag.cast::<SmbiosTag>();
         assert_eq!(tag.header.typ, TagType::Smbios);
         assert_eq!(tag.major, 7);
@@ -111,7 +118,8 @@ mod tests {
     #[cfg(feature = "builder")]
     fn test_build() {
         let tag = SmbiosTag::new(7, 42, &[0, 1, 2, 3, 4, 5, 6, 7, 8]);
-        let bytes = tag.as_bytes();
-        assert_eq!(bytes, &get_bytes()[..tag.size()]);
+        let bytes = tag.as_bytes().as_ref();
+        let bytes = &bytes[..tag.header.size as usize];
+        assert_eq!(bytes, &get_bytes()[..tag.header.size as usize]);
     }
 }

+ 12 - 409
multiboot2/src/tag.rs

@@ -1,20 +1,9 @@
-//! Module for the base tag definitions and helper types.
-//!
-//! The relevant exports of this module are [`TagHeader`], [`GenericTag`], and
-//! [`TagIter`].
-//!
-//! The (internal) workflow to parse a tag from bytes is the following:
-//! - `&[u8]` --> [`TagBytesRef`]
-//! - [`TagBytesRef`] --> [`TagHeader`]
-//! - [`TagBytesRef`] + [`TagHeader`] --> [`GenericTag`]
-//! - [`GenericTag`] --> cast to desired tag
+//! Module for the base tag definition [`TagHeader`].
 
-use crate::util::increase_to_alignment;
-use crate::{TagTrait, TagType, TagTypeId, ALIGNMENT};
-use core::fmt::{Debug, Formatter};
+use crate::TagTypeId;
+use core::fmt::Debug;
 use core::mem;
-use core::ops::Deref;
-use core::ptr;
+use multiboot2_common::Header;
 
 /// The common header that all tags have in common. This type is ABI compatible.
 ///
@@ -26,6 +15,8 @@ use core::ptr;
 #[repr(C, align(8))] // Alignment also propagates to all tag types using this.
 pub struct TagHeader {
     /// The ABI-compatible [`TagType`].
+    ///
+    /// [`TagType`]: crate::TagType
     pub typ: TagTypeId, /* u32 */
     /// The total size of the tag including the header.
     pub size: u32,
@@ -42,401 +33,13 @@ impl TagHeader {
     }
 }
 
-/// Wraps a byte slice representing a tag, but guarantees that the memory
-/// requirements are fulfilled.
-///
-/// This is the only type that can be used to construct a [`GenericTag`].
-///
-/// The main reason for this dedicated type is to create fine-grained unit-tests
-/// for Miri.
-///
-/// # Memory Requirements (for Multiboot and Rust/Miri)
-/// - At least as big as a `size_of<TagHeader>()`
-/// - at least [`ALIGNMENT`]-aligned
-/// - Length is multiple of [`ALIGNMENT`]. In other words, there are enough
-///   padding bytes until so that pointer coming right after the last byte
-///   is [`ALIGNMENT`]-aligned
-#[derive(Clone, Debug, PartialEq, Eq)]
-#[repr(transparent)]
-pub struct TagBytesRef<'a>(&'a [u8]);
-
-impl<'a> TryFrom<&'a [u8]> for TagBytesRef<'a> {
-    type Error = MemoryError;
-
-    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
-        if value.len() < mem::size_of::<TagHeader>() {
-            return Err(MemoryError::MinLengthNotSatisfied);
-        }
-        // Doesn't work as expected: if align_of_val(&value[0]) < ALIGNMENT {
-        if value.as_ptr().align_offset(ALIGNMENT) != 0 {
-            return Err(MemoryError::WrongAlignment);
-        }
-        let padding_bytes = value.len() % ALIGNMENT;
-        if padding_bytes != 0 {
-            return Err(MemoryError::MissingPadding);
-        }
-        Ok(Self(value))
-    }
-}
-
-impl<'a> Deref for TagBytesRef<'a> {
-    type Target = &'a [u8];
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-/// Errors that occur when constructing a [`TagBytesRef`].
-#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
-pub enum MemoryError {
-    /// The memory must be at least [`ALIGNMENT`]-aligned.
-    WrongAlignment,
-    /// The memory must cover at least the length of a [`TagHeader`].
-    MinLengthNotSatisfied,
-    /// The buffer misses the terminating padding to the next alignment
-    /// boundary.
-    // This is mainly relevant to satisfy Miri. As the spec also mandates an
-    // alignment, we can rely on this property.
-    MissingPadding,
-}
-
-/// A generic tag serving as base to cast to specific tags. This is a DST
-/// version of [`TagHeader`] that solves various type and memory safety
-/// problems by having a type that owns the whole memory of a tag.
-#[derive(Eq, Ord, PartialEq, PartialOrd, ptr_meta::Pointee)]
-#[repr(C)]
-pub struct GenericTag {
-    header: TagHeader,
-    /// Payload of the tag that is reflected in the `size` attribute, thus, no
-    /// padding bytes!
-    payload: [u8],
-}
-
-impl GenericTag {
-    /// Base size of the DST struct without the dynamic part.
-    const BASE_SIZE: usize = mem::size_of::<TagHeader>();
-
-    /// Creates a reference to a [`GenericTag`] from the provided `bytes`
-    /// [`TagBytesRef`].
-    pub(crate) fn ref_from(bytes: TagBytesRef) -> &Self {
-        let header = bytes.as_ptr().cast::<TagHeader>();
-        let header = unsafe { &*header };
-        let dst_len = Self::dst_len(header);
-        assert_eq!(header.size as usize, Self::BASE_SIZE + dst_len);
-
-        let generic_tag: *const Self = ptr_meta::from_raw_parts(bytes.as_ptr().cast(), dst_len);
-        unsafe { &*generic_tag }
-    }
-
-    pub const fn header(&self) -> &TagHeader {
-        &self.header
-    }
-
-    #[cfg(all(test, feature = "builder"))]
-    pub const fn payload(&self) -> &[u8] {
-        &self.payload
-    }
-
-    /// Casts the generic tag to a specific [`TagTrait`] implementation which
-    /// may be a ZST or DST typed tag.
-    pub fn cast<T: TagTrait + ?Sized>(&self) -> &T {
-        let base_ptr = ptr::addr_of!(*self);
-        let t_dst_size = T::dst_len(&self.header);
-        let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
-        let t_ref = unsafe { &*t_ptr };
-        assert_eq!(mem::size_of_val(self), mem::size_of_val(t_ref));
-        t_ref
-    }
-}
-
-impl Debug for GenericTag {
-    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
-        f.debug_struct("GenericTag")
-            .field("header", &self.header)
-            .field("payload", &"<bytes>")
-            .finish()
-    }
-}
-
-impl TagTrait for GenericTag {
-    const ID: TagType = TagType::Custom(0xffffffff);
-
-    fn dst_len(header: &TagHeader) -> usize {
-        assert!(header.size as usize >= Self::BASE_SIZE);
-        header.size as usize - Self::BASE_SIZE
-    }
-}
-
-/// Iterates the tags of the MBI from the first tag to the end tag. THe end tag
-/// included.
-#[derive(Clone, Debug)]
-pub struct TagIter<'a> {
-    /// Absolute offset to next tag and updated in each iteration.
-    next_tag_offset: usize,
-    exclusive_end: *const u8,
-    buffer: &'a [u8],
-}
-
-impl<'a> TagIter<'a> {
-    /// Creates a new iterator
-    pub fn new(mem: &'a [u8]) -> Self {
-        // Assert alignment.
-        assert_eq!(mem.as_ptr().align_offset(8), 0);
-
-        let exclusive_end = unsafe { mem.as_ptr().add(mem.len()) };
-
-        TagIter {
-            next_tag_offset: 0,
-            buffer: mem,
-            exclusive_end,
-        }
-    }
-}
-
-impl<'a> Iterator for TagIter<'a> {
-    type Item = &'a GenericTag;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let next_ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) };
-
-        if next_ptr == self.exclusive_end {
-            return None;
-        }
-        assert!(next_ptr < self.exclusive_end);
-
-        let next_tag_ptr = next_ptr.cast::<TagHeader>();
-
-        let tag_hdr = unsafe { &*next_tag_ptr };
-
-        // Get relevant byte portion for the next tag. This includes padding
-        // bytes to fulfill Rust memory guarantees. Otherwise, Miri complains.
-        // See <https://doc.rust-lang.org/reference/type-layout.html>.
-        let bytes = {
-            let from = self.next_tag_offset;
-            let to = from + tag_hdr.size as usize;
-            // The size of [the allocation for] a value is always a multiple of its
-            // alignment.
-            // https://doc.rust-lang.org/reference/type-layout.html
-            let to = increase_to_alignment(to);
-
-            // Update ptr for next iteration.
-            self.next_tag_offset += to - from;
-
-            &self.buffer[from..to]
-        };
-
-        // Should never fail at this point.
-        let tag_bytes = TagBytesRef::try_from(bytes).unwrap();
-
-        Some(GenericTag::ref_from(tag_bytes))
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::test_util::AlignedBytes;
-    use core::borrow::Borrow;
-    use core::mem;
-
-    #[test]
-    fn test_new_generic_tag() {
-        let bytes = AlignedBytes::new([
-            /* id: 0xffff_ffff */
-            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
-            16, 0, 0, 0, /* field a: 0xdead_beef */
-            0xde, 0xad, 0xbe, 0xef, /* field b: 0x1337_1337 */
-            0x13, 0x37, 0x13, 0x37,
-        ]);
-
-        let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
-        let tag = GenericTag::ref_from(bytes);
-        assert_eq!(tag.header.typ, 0xffff_ffff);
-        assert_eq!(tag.header.size, 16);
-        assert_eq!(tag.payload.len(), 8);
-    }
-
-    #[test]
-    fn test_cast_generic_tag_to_sized_tag() {
-        #[repr(C)]
-        struct CustomTag {
-            tag_header: TagHeader,
-            a: u32,
-            b: u32,
-        }
-
-        impl TagTrait for CustomTag {
-            const ID: TagType = TagType::End;
-
-            fn dst_len(_header: &TagHeader) -> Self::Metadata {}
-        }
-
-        let bytes = AlignedBytes([
-            /* id: 0xffff_ffff */
-            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
-            16, 0, 0, 0, /* field a: 0xdead_beef */
-            0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
-            0x37, 0x13, 0x37, 0x13,
-        ]);
-        let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
-        let tag = GenericTag::ref_from(bytes);
-        let custom_tag = tag.cast::<CustomTag>();
-
-        assert_eq!(mem::size_of_val(custom_tag), 16);
-        assert_eq!(custom_tag.a, 0xdead_beef);
-        assert_eq!(custom_tag.b, 0x1337_1337);
+impl Header for TagHeader {
+    fn payload_len(&self) -> usize {
+        assert!(self.size as usize >= mem::size_of::<Self>());
+        self.size as usize - mem::size_of::<Self>()
     }
 
-    #[test]
-    fn test_cast_generic_tag_to_dynamically_sized_tag() {
-        #[repr(C)]
-        #[derive(ptr_meta::Pointee)]
-        struct CustomDstTag {
-            tag_header: TagHeader,
-            a: u32,
-            payload: [u8],
-        }
-
-        impl TagTrait for CustomDstTag {
-            const ID: TagType = TagType::End;
-
-            fn dst_len(header: &TagHeader) -> Self::Metadata {
-                let base_size = mem::size_of::<TagHeader>() + mem::size_of::<u32>();
-                header.size as usize - base_size
-            }
-        }
-
-        let bytes = AlignedBytes([
-            /* id: 0xffff_ffff */
-            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
-            16, 0, 0, 0, /* field a: 0xdead_beef */
-            0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
-            0x37, 0x13, 0x37, 0x13,
-        ]);
-
-        let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
-        let tag = GenericTag::ref_from(bytes);
-        let custom_tag = tag.cast::<CustomDstTag>();
-
-        assert_eq!(mem::size_of_val(custom_tag), 16);
-        assert_eq!(custom_tag.a, 0xdead_beef);
-        assert_eq!(custom_tag.payload.len(), 4);
-        assert_eq!(custom_tag.payload[0], 0x37);
-        assert_eq!(custom_tag.payload[1], 0x13);
-        assert_eq!(custom_tag.payload[2], 0x37);
-        assert_eq!(custom_tag.payload[3], 0x13);
-    }
-
-    #[test]
-    fn test_tag_bytes_ref() {
-        let empty: &[u8] = &[];
-        assert_eq!(
-            TagBytesRef::try_from(empty),
-            Err(MemoryError::MinLengthNotSatisfied)
-        );
-
-        let slice = &[0_u8, 1, 2, 3, 4, 5, 6];
-        assert_eq!(
-            TagBytesRef::try_from(&slice[..]),
-            Err(MemoryError::MinLengthNotSatisfied)
-        );
-
-        let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0]);
-        // Guaranteed wrong alignment
-        let unaligned_slice = &slice[3..];
-        assert_eq!(
-            TagBytesRef::try_from(unaligned_slice),
-            Err(MemoryError::WrongAlignment)
-        );
-
-        let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7]);
-        let slice = &slice[..];
-        assert_eq!(TagBytesRef::try_from(slice), Ok(TagBytesRef(slice)));
-    }
-
-    #[test]
-    fn test_create_generic_tag() {
-        #[rustfmt::skip]
-        let bytes = AlignedBytes::new(
-            [
-                TagType::Cmdline.val() as u8, 0, 0, 0,
-                /* Tag size */
-                18, 0, 0, 0,
-                /* Some payload.  */
-                0, 1, 2, 3,
-                4, 5, 6, 7,
-                8, 9,
-                // Padding
-                0, 0, 0, 0, 0, 0
-            ],
-        );
-        let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
-        let tag = GenericTag::ref_from(bytes);
-        assert_eq!(tag.header.typ, TagType::Cmdline);
-        assert_eq!(tag.header.size, 8 + 10);
-    }
-
-    #[test]
-    fn test_cast_generic_tag_to_generic_tag() {
-        #[rustfmt::skip]
-        let bytes = AlignedBytes::new(
-            [
-                TagType::Cmdline.val() as u8, 0, 0, 0,
-                /* Tag size */
-                18, 0, 0, 0,
-                /* Some payload.  */
-                0, 1, 2, 3,
-                4, 5, 6, 7,
-                8, 9,
-                // Padding
-                0, 0, 0, 0, 0, 0
-            ],
-        );
-        let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
-        let tag = GenericTag::ref_from(bytes);
-
-        // Main objective here is also that this test passes Miri.
-        let tag = tag.cast::<GenericTag>();
-        assert_eq!(tag.header.typ, TagType::Cmdline);
-        assert_eq!(tag.header.size, 8 + 10);
-    }
-
-    #[test]
-    fn test_tag_iter() {
-        #[rustfmt::skip]
-        let bytes = AlignedBytes::new(
-            [
-                /* Some minimal tag.  */
-                0xff, 0, 0, 0,
-                8, 0, 0, 0,
-                /* Some tag with payload.  */
-                0xfe, 0, 0, 0,
-                12, 0, 0, 0,
-                1, 2, 3, 4,
-                // Padding
-                0, 0, 0, 0,
-                /* End tag */
-                0, 0, 0, 0,
-                8, 0, 0, 0,
-            ],
-        );
-        let mut iter = TagIter::new(bytes.borrow());
-        let first = iter.next().unwrap();
-        assert_eq!(first.header.typ, TagType::Custom(0xff));
-        assert_eq!(first.header.size, 8);
-        assert!(first.payload.is_empty());
-
-        let second = iter.next().unwrap();
-        assert_eq!(second.header.typ, TagType::Custom(0xfe));
-        assert_eq!(second.header.size, 12);
-        assert_eq!(&second.payload, &[1, 2, 3, 4]);
-
-        let third = iter.next().unwrap();
-        assert_eq!(third.header.typ, TagType::End);
-        assert_eq!(third.header.size, 8);
-        assert!(first.payload.is_empty());
-
-        assert_eq!(iter.next(), None);
+    fn set_size(&mut self, total_size: usize) {
+        self.size = total_size as u32
     }
 }

+ 0 - 47
multiboot2/src/tag_trait.rs

@@ -1,47 +0,0 @@
-//! Module for [`TagTrait`].
-
-use crate::tag::TagHeader;
-use crate::TagType;
-use ptr_meta::Pointee;
-
-/// A trait to abstract over all sized and unsized tags (DSTs). For sized tags,
-/// this trait does not much. For DSTs, a [`TagTrait::dst_len`] implementation
-/// must be provided, which returns the right size hint for the dynamically
-/// sized portion of the struct.
-///
-/// # Trivia
-/// This crate uses the [`Pointee`]-abstraction of the [`ptr_meta`] crate to
-/// create fat pointers for tags that are DST.
-pub trait TagTrait: Pointee {
-    /// The numeric ID of this tag.
-    const ID: TagType;
-
-    /// Returns the amount of items in the dynamically sized portion of the
-    /// DST. Note that this is not the amount of bytes. So if the dynamically
-    /// sized portion is 16 bytes in size and each element is 4 bytes big, then
-    /// this function must return 4.
-    ///
-    /// For sized tags, this just returns `()`. For DSTs, this returns an
-    /// `usize`.
-    fn dst_len(header: &TagHeader) -> Self::Metadata;
-
-    /// Returns the tag as the common base tag structure.
-    fn as_base_tag(&self) -> &TagHeader {
-        let ptr = core::ptr::addr_of!(*self);
-        unsafe { &*ptr.cast::<TagHeader>() }
-    }
-
-    /// Returns the total size of the tag. The depends on the `size` field of
-    /// the tag.
-    fn size(&self) -> usize {
-        self.as_base_tag().size as usize
-    }
-
-    /// Returns a slice to the underlying bytes of the tag. This includes all
-    /// bytes, also for tags that are DSTs. The slice length depends on the
-    /// `size` field of the tag.
-    fn as_bytes(&self) -> &[u8] {
-        let ptr = core::ptr::addr_of!(*self);
-        unsafe { core::slice::from_raw_parts(ptr.cast(), self.size()) }
-    }
-}

+ 0 - 92
multiboot2/src/util.rs

@@ -1,16 +1,8 @@
 //! Various utilities.
 
-use crate::ALIGNMENT;
 use core::fmt;
 use core::fmt::{Display, Formatter};
 use core::str::Utf8Error;
-#[cfg(feature = "alloc")]
-use {
-    crate::{TagHeader, TagTrait},
-    core::{mem, ptr},
-};
-#[cfg(feature = "builder")]
-use {alloc::alloc::Layout, alloc::boxed::Box};
 
 /// Error type describing failures when parsing the string from a tag.
 #[derive(Debug, PartialEq, Eq, Clone)]
@@ -38,52 +30,6 @@ impl core::error::Error for StringError {
     }
 }
 
-/// Creates a new tag implementing [`TagTrait`] on the heap. This works for
-/// sized and unsized tags. However, it only makes sense to use this for tags
-/// that are DSTs (unsized), as for the sized ones, you can call a regular
-/// constructor and box the result.
-///
-/// # Parameters
-/// - `additional_bytes_slices`: Array of byte slices that should be included
-///   without additional padding in-between. You don't need to add the bytes
-///   for [`TagHeader`], but only additional ones.
-#[cfg(feature = "alloc")]
-#[must_use]
-pub fn new_boxed<T: TagTrait + ?Sized>(additional_bytes_slices: &[&[u8]]) -> Box<T> {
-    let additional_size = additional_bytes_slices
-        .iter()
-        .map(|b| b.len())
-        .sum::<usize>();
-
-    let size = mem::size_of::<TagHeader>() + additional_size;
-    let alloc_size = increase_to_alignment(size);
-    let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap();
-    let heap_ptr = unsafe { alloc::alloc::alloc(layout) };
-    assert!(!heap_ptr.is_null());
-
-    unsafe {
-        heap_ptr.cast::<u32>().write(T::ID.val());
-        heap_ptr.cast::<u32>().add(1).write(size as u32);
-    }
-
-    let mut write_offset = mem::size_of::<TagHeader>();
-    for &bytes in additional_bytes_slices {
-        unsafe {
-            let len = bytes.len();
-            let src = bytes.as_ptr();
-            let dst = heap_ptr.add(write_offset);
-            ptr::copy_nonoverlapping(src, dst, len);
-            write_offset += len;
-        }
-    }
-
-    let header = unsafe { heap_ptr.cast::<TagHeader>().as_ref() }.unwrap();
-
-    let ptr = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(header));
-
-    unsafe { Box::from_raw(ptr) }
-}
-
 /// Parses the provided byte sequence as Multiboot string, which maps to a
 /// [`str`].
 pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> {
@@ -91,22 +37,9 @@ pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> {
     cstr.to_str().map_err(StringError::Utf8)
 }
 
-/// Increases the given size to the next alignment boundary, if it is not a
-/// multiple of the alignment yet. This is relevant as in Rust's [type layout],
-/// the allocated size of a type is always a multiple of the alignment, even
-/// if the type is smaller.
-///
-/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
-pub const fn increase_to_alignment(size: usize) -> usize {
-    let mask = ALIGNMENT - 1;
-    (size + mask) & !mask
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
-    #[cfg(feature = "alloc")]
-    use {crate::tag::GenericTag, crate::CommandLineTag};
 
     #[test]
     fn test_parse_slice_as_string() {
@@ -133,29 +66,4 @@ mod tests {
         // must skip everytihng after first null
         assert_eq!(parse_slice_as_string(b"hello\0foo"), Ok("hello"));
     }
-
-    #[test]
-    fn test_increase_to_alignment() {
-        assert_eq!(increase_to_alignment(0), 0);
-        assert_eq!(increase_to_alignment(1), 8);
-        assert_eq!(increase_to_alignment(7), 8);
-        assert_eq!(increase_to_alignment(8), 8);
-        assert_eq!(increase_to_alignment(9), 16);
-    }
-
-    #[test]
-    #[cfg(feature = "alloc")]
-    fn test_new_boxed() {
-        let tag = new_boxed::<GenericTag>(&[&[0, 1, 2, 3]]);
-        assert_eq!(tag.header().typ, GenericTag::ID);
-        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
-
-        // Test that bytes are added consecutively without gaps.
-        let tag = new_boxed::<GenericTag>(&[&[0], &[1], &[2, 3]]);
-        assert_eq!(tag.header().typ, GenericTag::ID);
-        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
-
-        let tag = new_boxed::<CommandLineTag>(&[b"hello\0"]);
-        assert_eq!(tag.cmdline(), Ok("hello"));
-    }
 }

+ 68 - 6
multiboot2/src/vbe_info.rs

@@ -1,8 +1,9 @@
 //! Module for [`VBEInfoTag`].
 
-use crate::{TagHeader, TagTrait, TagType};
+use crate::{TagHeader, TagType};
 use core::fmt;
 use core::mem;
+use multiboot2_common::{MaybeDynSized, Tag};
 
 /// This tag contains VBE metadata, VBE controller information returned by the
 /// VBE Function 00h and VBE mode information returned by the VBE Function 01h.
@@ -20,7 +21,6 @@ pub struct VBEInfoTag {
 
 impl VBEInfoTag {
     /// Constructs a new tag.
-    #[cfg(feature = "builder")]
     #[must_use]
     pub fn new(
         mode: u16,
@@ -83,12 +83,20 @@ impl VBEInfoTag {
     }
 }
 
-impl TagTrait for VBEInfoTag {
-    const ID: TagType = TagType::Vbe;
+impl MaybeDynSized for VBEInfoTag {
+    type Header = TagHeader;
+
+    const BASE_SIZE: usize = mem::size_of::<Self>();
 
     fn dst_len(_: &TagHeader) {}
 }
 
+impl Tag for VBEInfoTag {
+    type IDType = TagType;
+
+    const ID: TagType = TagType::Vbe;
+}
+
 /// VBE controller information.
 ///
 /// The capabilities of the display controller, the revision level of the
@@ -156,6 +164,25 @@ impl fmt::Debug for VBEControlInfo {
     }
 }
 
+impl Default for VBEControlInfo {
+    fn default() -> Self {
+        Self {
+            signature: Default::default(),
+            version: 0,
+            oem_string_ptr: 0,
+            capabilities: Default::default(),
+            mode_list_ptr: 0,
+            total_memory: 0,
+            oem_software_revision: 0,
+            oem_vendor_name_ptr: 0,
+            oem_product_name_ptr: 0,
+            oem_product_revision_ptr: 0,
+            reserved: [0; 222],
+            oem_data: [0; 256],
+        }
+    }
+}
+
 /// Extended information about a specific VBE display mode from the
 /// mode list returned by `VBEControlInfo` (VBE Function `00h`).
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -283,10 +310,44 @@ impl fmt::Debug for VBEModeInfo {
     }
 }
 
+impl Default for VBEModeInfo {
+    fn default() -> Self {
+        Self {
+            mode_attributes: Default::default(),
+            window_a_attributes: Default::default(),
+            window_b_attributes: Default::default(),
+            window_granularity: 0,
+            window_size: 0,
+            window_a_segment: 0,
+            window_b_segment: 0,
+            window_function_ptr: 0,
+            pitch: 0,
+            resolution: (0, 0),
+            character_size: (0, 0),
+            number_of_planes: 0,
+            bpp: 0,
+            number_of_banks: 0,
+            memory_model: Default::default(),
+            bank_size: 0,
+            number_of_image_pages: 0,
+            reserved0: 0,
+            red_field: Default::default(),
+            green_field: Default::default(),
+            blue_field: Default::default(),
+            reserved_field: Default::default(),
+            direct_color_attributes: Default::default(),
+            framebuffer_base_ptr: 0,
+            offscreen_memory_offset: 0,
+            offscreen_memory_size: 0,
+            reserved1: [0; 206],
+        }
+    }
+}
+
 /// A VBE colour field.
 ///
 /// Describes the size and position of some colour capability.
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[repr(C, packed)]
 pub struct VBEField {
     /// The size, in bits, of the color components of a direct color pixel.
@@ -386,11 +447,12 @@ bitflags! {
 }
 
 /// The MemoryModel field specifies the general type of memory organization used in modes.
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[repr(u8)]
 #[allow(missing_docs)]
 #[allow(clippy::upper_case_acronyms)]
 pub enum VBEMemoryModel {
+    #[default]
     Text = 0x00,
     CGAGraphics = 0x01,
     HerculesGraphics = 0x02,