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;
#[derive(Clone, Debug)]
pub struct HeaderBytes {
offset: usize,
structure_len: usize,
bytes: Vec<u8>,
}
impl HeaderBytes {
pub fn as_bytes(&self) -> &[u8] {
let slice = &self.bytes[self.offset..self.offset + self.structure_len];
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()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HeaderBuilder {
arch: HeaderTagISA,
information_request_tag: Option<InformationRequestHeaderTagBuilder>,
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>,
}
impl HeaderBuilder {
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,
}
}
const fn size_or_up_aligned(size: usize) -> usize {
(size + 7) & !7
}
pub fn expected_len(&self) -> usize {
let base_len = size_of::<Multiboot2BasicHeader>();
let mut len = Self::size_or_up_aligned(base_len);
if let Some(tag_builder) = self.information_request_tag.as_ref() {
len += Self::size_or_up_aligned(tag_builder.expected_len())
}
if self.address_tag.is_some() {
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>())
}
len += size_of::<EndHeaderTag>();
len
}
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()) };
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;
dest.extend([0].repeat(size_to_8_align_diff));
}
}
pub fn build(mut self) -> HeaderBytes {
const ALIGN: usize = 8;
let expected_len = self.expected_len();
let alloc_len = expected_len + 7;
let mut bytes = Vec::<u8>::with_capacity(alloc_len);
let alloc_ptr = bytes.as_ptr();
let offset = bytes.as_ptr().align_offset(ALIGN);
bytes.extend([0].repeat(offset));
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,
}
}
fn build_add_tags(&mut self, bytes: &mut Vec<u8>) {
Self::build_add_bytes(
bytes,
&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);
}
#[allow(clippy::missing_const_for_fn)]
pub fn information_request_tag(
mut self,
information_request_tag: InformationRequestHeaderTagBuilder,
) -> Self {
self.information_request_tag = Some(information_request_tag);
self
}
pub const fn address_tag(mut self, address_tag: AddressHeaderTag) -> Self {
self.address_tag = Some(address_tag);
self
}
pub const fn entry_tag(mut self, entry_tag: EntryAddressHeaderTag) -> Self {
self.entry_tag = Some(entry_tag);
self
}
pub const fn console_tag(mut self, console_tag: ConsoleHeaderTag) -> Self {
self.console_tag = Some(console_tag);
self
}
pub const fn framebuffer_tag(mut self, framebuffer_tag: FramebufferHeaderTag) -> Self {
self.framebuffer_tag = Some(framebuffer_tag);
self
}
pub const fn module_align_tag(mut self, module_align_tag: ModuleAlignHeaderTag) -> Self {
self.module_align_tag = Some(module_align_tag);
self
}
pub const fn efi_bs_tag(mut self, efi_bs_tag: EfiBootServiceHeaderTag) -> Self {
self.efi_bs_tag = Some(efi_bs_tag);
self
}
pub const fn efi_32_tag(mut self, efi_32_tag: EntryEfi32HeaderTag) -> Self {
self.efi_32_tag = Some(efi_32_tag);
self
}
pub const fn efi_64_tag(mut self, efi_64_tag: EntryEfi64HeaderTag) -> Self {
self.efi_64_tag = Some(efi_64_tag);
self
}
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);
let mut expected_len = 16 + 8;
assert_eq!(builder.expected_len(), expected_len);
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]
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() {
let mb2_hdr_data = create_builder().build();
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
);
println!("{:#?}", mb2_hdr);
}
}