Skip to content

Multiboot2 header builder restructuring #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions multiboot2-header/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ multiboot2-header = "<latest>"

## Example 1: Builder + Parse
```rust
use multiboot2_header::builder::Multiboot2HeaderBuilder;
use multiboot2_header::{HeaderTagFlag, HeaderTagISA, InformationRequestHeaderTagBuilder, MbiTagType, RelocatableHeaderTag, RelocatableHeaderTagPreference, Multiboot2Header};
use multiboot2_header::builder::{InformationRequestHeaderTagBuilder, Multiboot2HeaderBuilder};
use multiboot2_header::{HeaderTagFlag, HeaderTagISA, MbiTagType, RelocatableHeaderTag, RelocatableHeaderTagPreference, Multiboot2Header};

/// Small example that creates a Multiboot2 header and parses it afterwards.
fn main() {
Expand Down
6 changes: 3 additions & 3 deletions multiboot2-header/examples/minimal.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use multiboot2_header::builder::Multiboot2HeaderBuilder;
use multiboot2_header::builder::{InformationRequestHeaderTagBuilder, Multiboot2HeaderBuilder};
use multiboot2_header::{
HeaderTagFlag, HeaderTagISA, InformationRequestHeaderTagBuilder, MbiTagType, Multiboot2Header,
RelocatableHeaderTag, RelocatableHeaderTagPreference,
HeaderTagFlag, HeaderTagISA, MbiTagType, Multiboot2Header, RelocatableHeaderTag,
RelocatableHeaderTagPreference,
};

/// Small example that creates a Multiboot2 header and parses it afterwards.
Expand Down
3 changes: 0 additions & 3 deletions multiboot2-header/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,3 @@ impl AddressHeaderTag {
self.bss_end_addr
}
}

#[cfg(feature = "builder")]
impl crate::StructAsBytes for AddressHeaderTag {}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Exports item [`Multiboot2HeaderBuilder`].

use crate::builder::information_request::InformationRequestHeaderTagBuilder;
use crate::builder::traits::StructAsBytes;
use crate::HeaderTagISA;
use crate::{
AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, EntryEfi32HeaderTag,
EntryEfi64HeaderTag, EntryHeaderTag, FramebufferHeaderTag, InformationRequestHeaderTagBuilder,
ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag, StructAsBytes,
EntryEfi64HeaderTag, EntryHeaderTag, FramebufferHeaderTag, ModuleAlignHeaderTag,
Multiboot2BasicHeader, RelocatableHeaderTag,
};
use alloc::vec::Vec;
use core::mem::size_of;
Expand Down Expand Up @@ -226,9 +228,10 @@ impl Multiboot2HeaderBuilder {

#[cfg(test)]
mod tests {
use crate::builder::header::Multiboot2HeaderBuilder;
use crate::builder::information_request::InformationRequestHeaderTagBuilder;
use crate::{
HeaderTagFlag, HeaderTagISA, InformationRequestHeaderTagBuilder, MbiTagType,
Multiboot2Header, Multiboot2HeaderBuilder, RelocatableHeaderTag,
HeaderTagFlag, HeaderTagISA, MbiTagType, Multiboot2Header, RelocatableHeaderTag,
RelocatableHeaderTagPreference,
};

Expand Down
119 changes: 119 additions & 0 deletions multiboot2-header/src/builder/information_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use super::traits::StructAsBytes;
use crate::InformationRequestHeaderTag;
use crate::{HeaderTagFlag, MbiTagType};
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use core::fmt::Debug;
use core::mem::size_of;

/// 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(Debug)]
#[cfg(feature = "builder")]
pub struct InformationRequestHeaderTagBuilder {
flag: HeaderTagFlag,
// information requests (irs)
irs: BTreeSet<MbiTagType>,
}

#[cfg(feature = "builder")]
impl InformationRequestHeaderTagBuilder {
/// New builder.
pub 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.
pub fn expected_len(&self) -> usize {
let basic_header_size = size_of::<InformationRequestHeaderTag<0>>();
let req_tags_size = self.irs.len() * size_of::<MbiTagType>();
basic_header_size + req_tags_size
}

/// Adds an [`MbiTagType`] to the information request.
pub fn add_ir(mut self, tag: MbiTagType) -> Self {
self.irs.insert(tag);
self
}

/// Adds multiple [`MbiTagType`] to the information request.
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 in &self.irs {
let bytes: [u8; 4] = (*tag as u32).to_ne_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};

#[test]
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(&MbiTagType::EfiMmap));
assert!(tag.requests().contains(&MbiTagType::BootLoaderName));
assert!(tag.requests().contains(&MbiTagType::Cmdline));
assert_eq!(tag.requests().len(), 3);
assert!(!tag.requests().contains(&MbiTagType::AcpiV1));
println!("{:#?}", tag);
}
}
8 changes: 8 additions & 0 deletions multiboot2-header/src/builder/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Module for the builder-feature.

mod header;
mod information_request;
pub(self) mod traits;

pub use header::Multiboot2HeaderBuilder;
pub use information_request::InformationRequestHeaderTagBuilder;
75 changes: 75 additions & 0 deletions multiboot2-header/src/builder/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Module for the helper trait [`StructAsBytes`].

use crate::{
AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, EntryEfi32HeaderTag,
EntryEfi64HeaderTag, EntryHeaderTag, FramebufferHeaderTag, InformationRequestHeaderTag,
ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag,
};
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(crate) 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 [`size`].
fn struct_as_bytes(&self) -> alloc::vec::Vec<u8> {
let ptr = self.as_ptr();
let mut vec = alloc::vec::Vec::with_capacity(self.byte_size());
for i in 0..self.byte_size() {
vec.push(unsafe { *ptr.add(i) })
}
vec
}
}

impl StructAsBytes for AddressHeaderTag {}
impl StructAsBytes for ConsoleHeaderTag {}
impl StructAsBytes for EndHeaderTag {}
impl StructAsBytes for EntryEfi32HeaderTag {}
impl StructAsBytes for EntryEfi64HeaderTag {}
impl StructAsBytes for EntryHeaderTag {}
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]
fn test_as_bytes() {
struct Foobar {
a: u32,
b: u8,
c: u128,
}
impl StructAsBytes for Foobar {}
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);
}
}
3 changes: 0 additions & 3 deletions multiboot2-header/src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ impl ConsoleHeaderTag {
}
}

#[cfg(feature = "builder")]
impl crate::StructAsBytes for ConsoleHeaderTag {}

#[cfg(test)]
mod tests {
use crate::{ConsoleHeaderTag, ConsoleHeaderTagFlags, HeaderTagFlag, HeaderTagType};
Expand Down
3 changes: 0 additions & 3 deletions multiboot2-header/src/end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,3 @@ impl EndHeaderTag {
self.size
}
}

#[cfg(feature = "builder")]
impl crate::StructAsBytes for EndHeaderTag {}
3 changes: 0 additions & 3 deletions multiboot2-header/src/entry_efi_32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,3 @@ impl Debug for EntryEfi32HeaderTag {
.finish()
}
}

#[cfg(feature = "builder")]
impl crate::StructAsBytes for EntryEfi32HeaderTag {}
3 changes: 0 additions & 3 deletions multiboot2-header/src/entry_efi_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,3 @@ impl Debug for EntryEfi64HeaderTag {
.finish()
}
}

#[cfg(feature = "builder")]
impl crate::StructAsBytes for EntryEfi64HeaderTag {}
3 changes: 0 additions & 3 deletions multiboot2-header/src/entry_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,3 @@ impl Debug for EntryHeaderTag {
.finish()
}
}

#[cfg(feature = "builder")]
impl crate::StructAsBytes for EntryHeaderTag {}
3 changes: 0 additions & 3 deletions multiboot2-header/src/framebuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,3 @@ impl FramebufferHeaderTag {
self.depth
}
}

#[cfg(feature = "builder")]
impl crate::StructAsBytes for FramebufferHeaderTag {}
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
//! Module for the main struct, which marks the begin of a Multiboot2 header.
//! See [`Multiboot2Header`].

pub mod builder;

pub use self::builder::*;
use crate::{AddressHeaderTag, InformationRequestHeaderTag, RelocatableHeaderTag};
use crate::{ConsoleHeaderTag, EntryHeaderTag};
use crate::{EfiBootServiceHeaderTag, FramebufferHeaderTag};
use crate::{EndHeaderTag, HeaderTagType};
use crate::{EntryEfi32HeaderTag, EntryEfi64HeaderTag};
use crate::{HeaderTag, HeaderTagISA};
use crate::{
AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, EntryEfi32HeaderTag,
EntryEfi64HeaderTag, EntryHeaderTag, FramebufferHeaderTag, HeaderTag, HeaderTagISA,
HeaderTagType, InformationRequestHeaderTag, RelocatableHeaderTag,
};
use core::fmt::{Debug, Formatter};
use core::mem::size_of;

Expand All @@ -21,7 +14,7 @@ pub const MULTIBOOT2_HEADER_MAGIC: u32 = 0xe85250d6;
/// by all tags (see [`crate::tags::HeaderTagType`]).
/// 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 [`builder::Multiboot2HeaderBuilder`].
/// please look at [`crate::builder::Multiboot2HeaderBuilder`].
#[repr(transparent)]
pub struct Multiboot2Header<'a> {
inner: &'a Multiboot2BasicHeader,
Expand Down Expand Up @@ -122,6 +115,7 @@ pub struct Multiboot2BasicHeader {
}

impl Multiboot2BasicHeader {
#[cfg(feature = "builder")]
/// Constructor for the basic header.
pub(crate) const fn new(arch: HeaderTagISA, length: u32) -> Self {
let magic = MULTIBOOT2_HEADER_MAGIC;
Expand Down Expand Up @@ -194,9 +188,6 @@ impl Debug for Multiboot2BasicHeader {
}
}

#[cfg(feature = "builder")]
impl crate::StructAsBytes for Multiboot2BasicHeader {}

/// Iterator over all tags of a Multiboot2 header. The number of items is derived
/// by the size/length of the header.
///
Expand Down
Loading