Skip to content

Commit 6ed74ef

Browse files
committed
multiboot2-common: init with test_util module and memory helpers
1 parent 4bc3e93 commit 6ed74ef

File tree

8 files changed

+295
-34
lines changed

8 files changed

+295
-34
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ exclude = [
1313
bitflags = "2.6.0"
1414
derive_more = { version = "~0.99.18", default-features = false, features = ["display"] }
1515
log = { version = "~0.4", default-features = false }
16+
ptr_meta = { version = "~0.2", default-features = false }
1617

17-
# This way, the "multiboot2" dependency in the multiboot2-header crate can be
18-
# referenced by version, while still the repository version is used
19-
# transparently during local development.
18+
# This way, the corresponding crate dependency can be normalley referenced by
19+
# version, while still the repository version is used transparently during local
20+
# development.
2021
[patch.crates-io]
2122
multiboot2 = { path = "multiboot2" }
23+
multiboot2-common = { path = "multiboot2-common" }

multiboot2-common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ documentation = "https://docs.rs/multiboot2-common"
2929

3030

3131
[dependencies]
32+
ptr_meta.workspace = true
3233

3334
[package.metadata.docs.rs]
3435
all-features = true

multiboot2-common/src/lib.rs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,194 @@
2424
#![deny(missing_debug_implementations)]
2525
#![deny(rustdoc::all)]
2626
// --- END STYLE CHECKS ---
27+
28+
#[cfg_attr(test, macro_use)]
29+
#[cfg(test)]
30+
extern crate std;
31+
32+
// Doesn't work: #[cfg(test)]
33+
#[allow(unused)]
34+
pub mod test_utils;
35+
36+
use core::fmt::Debug;
37+
use core::marker::PhantomData;
38+
use core::mem;
39+
use core::ops::Deref;
40+
41+
/// The alignment of all Multiboot2 data structures.
42+
pub const ALIGNMENT: usize = 8;
43+
44+
/// A sized header type for [`DynSizedStructure`]. Note that `header` refers to
45+
/// the header pattern. Thus, depending on the use case, this is not just the
46+
/// tag header. Instead, it refers to all bytes that are fixed and not part of
47+
/// any optional terminating dynamic `[u8]` slice.
48+
///
49+
/// It's alignment **must** be the alignment of the tags. Typically,
50+
/// [`ALIGNMENT`].
51+
pub trait Header: Sized + PartialEq + Eq + Debug {
52+
/// Returns the length of the payload, i.e., the bytes that are additional
53+
/// to the header. The value is measured in bytes.
54+
fn payload_len(&self) -> usize;
55+
}
56+
57+
/// Errors that occur when constructing [`DynSizedStructure`].
58+
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
59+
pub enum DynSizedStructureError {
60+
/// The size-property has an illegal value that can't be fulfilled with the
61+
/// given bytes.
62+
SizeTooBig,
63+
}
64+
65+
/// A dynamically sized type with a common sized header and a dynamic amount of
66+
/// bytes that owns all its memory. It is fulfilling all memory requirements and
67+
/// guarantees of Multiboot2 structures and Rustc/Miri.
68+
///
69+
/// # ABI
70+
/// This has a C ABI. The fixed [`Header`] portion is always there. Further,
71+
/// there is a variable amount of payload bytes. Thus, this type can only
72+
/// exist on the heap or references to it can be made by cast via fat pointers.
73+
#[derive(Debug, PartialEq, Eq, ptr_meta::Pointee)]
74+
#[repr(C, align(8))]
75+
pub struct DynSizedStructure<H: Header> {
76+
header: H,
77+
payload: [u8],
78+
// Plus optional padding bytes to next alignment boundary, which are not
79+
// reflected here. However, Rustc allocates them anyway and expects them
80+
// to be there.
81+
// See <https://doc.rust-lang.org/reference/type-layout.html>.
82+
}
83+
84+
impl<H: Header> DynSizedStructure<H> {
85+
/// Returns a new reference from the given [`BytesRef`].
86+
pub fn ref_from(bytes: BytesRef<H>) -> Result<&Self, DynSizedStructureError> {
87+
let header = bytes.as_ptr().cast::<H>();
88+
let header = unsafe { &*header };
89+
90+
if header.payload_len() > bytes.len() {
91+
return Err(DynSizedStructureError::SizeTooBig);
92+
}
93+
94+
// Create fat pointer for DST.
95+
let structure: *const Self =
96+
ptr_meta::from_raw_parts(bytes.as_ptr().cast(), header.payload_len());
97+
let structure = unsafe { &*structure };
98+
Ok(structure)
99+
}
100+
101+
/// Returns the underlying [`Header`].
102+
pub const fn header(&self) -> &H {
103+
&self.header
104+
}
105+
106+
/// Returns the underlying payload.
107+
pub const fn payload(&self) -> &[u8] {
108+
&self.payload
109+
}
110+
}
111+
112+
/// Wraps a byte slice representing a Multiboot2 structure including an optional
113+
/// terminating padding, if necessary. Guarantees that the memory requirements
114+
/// for both Multiboot2 and Rustc/Miri are fulfilled.
115+
///
116+
/// Useful to construct [`DynSizedStructure`]. The main reason for this
117+
/// dedicated type is to create fine-grained unit-tests for Miri.
118+
///
119+
/// # Memory Requirements
120+
/// - At least as big as a `size_of::<HeaderT>()`
121+
/// - at least [`ALIGNMENT`]-aligned
122+
/// - Length is multiple of [`ALIGNMENT`]. In other words, there are enough
123+
/// padding bytes so that the pointer coming right after the last byte
124+
/// is [`ALIGNMENT`]-aligned.
125+
///
126+
/// See <https://doc.rust-lang.org/reference/type-layout.html> for information.
127+
#[derive(Clone, Debug, PartialEq, Eq)]
128+
#[repr(transparent)]
129+
pub struct BytesRef<'a, H: Header> {
130+
bytes: &'a [u8],
131+
// Ensure that consumers can rely on the size properties for HeaderT that
132+
// already have been verified when this type was constructed.
133+
_h: PhantomData<H>,
134+
}
135+
136+
impl<'a, H: Header> TryFrom<&'a [u8]> for BytesRef<'a, H> {
137+
type Error = BytesRefError;
138+
139+
fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
140+
if bytes.len() < mem::size_of::<H>() {
141+
return Err(BytesRefError::MinLengthNotSatisfied);
142+
}
143+
// Doesn't work as expected: if align_of_val(&value[0]) < ALIGNMENT {
144+
if bytes.as_ptr().align_offset(ALIGNMENT) != 0 {
145+
return Err(BytesRefError::WrongAlignment);
146+
}
147+
let padding_bytes = bytes.len() % ALIGNMENT;
148+
if padding_bytes != 0 {
149+
return Err(BytesRefError::MissingPadding);
150+
}
151+
Ok(Self {
152+
bytes,
153+
_h: PhantomData,
154+
})
155+
}
156+
}
157+
158+
impl<'a, H: Header> Deref for BytesRef<'a, H> {
159+
type Target = &'a [u8];
160+
161+
fn deref(&self) -> &Self::Target {
162+
&self.bytes
163+
}
164+
}
165+
166+
/// Errors that occur when constructing [`BytesRef`].
167+
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
168+
pub enum BytesRefError {
169+
/// The memory must be at least [`ALIGNMENT`]-aligned.
170+
WrongAlignment,
171+
/// The memory must cover at least the length of the sized structure header
172+
/// type.
173+
MinLengthNotSatisfied,
174+
/// The buffer misses the terminating padding to the next alignment
175+
/// boundary. The padding is relevant to satisfy Rustc/Miri, but also the
176+
/// spec mandates that the padding is added.
177+
MissingPadding,
178+
}
179+
180+
#[cfg(test)]
181+
mod tests {
182+
use super::*;
183+
use crate::test_utils::{AlignedBytes, DummyTestHeader};
184+
185+
#[test]
186+
fn test_bytes_ref() {
187+
let empty: &[u8] = &[];
188+
assert_eq!(
189+
BytesRef::<'_, DummyTestHeader>::try_from(empty),
190+
Err(BytesRefError::MinLengthNotSatisfied)
191+
);
192+
193+
let slice = &[0_u8, 1, 2, 3, 4, 5, 6];
194+
assert_eq!(
195+
BytesRef::<'_, DummyTestHeader>::try_from(&slice[..]),
196+
Err(BytesRefError::MinLengthNotSatisfied)
197+
);
198+
199+
let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0]);
200+
// Guaranteed wrong alignment
201+
let unaligned_slice = &slice[3..];
202+
assert_eq!(
203+
BytesRef::<'_, DummyTestHeader>::try_from(unaligned_slice),
204+
Err(BytesRefError::WrongAlignment)
205+
);
206+
207+
let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7]);
208+
let slice = &slice[..];
209+
assert_eq!(
210+
BytesRef::try_from(slice),
211+
Ok(BytesRef {
212+
bytes: slice,
213+
_h: PhantomData::<DummyTestHeader>
214+
})
215+
);
216+
}
217+
}

multiboot2-common/src/test_utils.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//! Various test utilities.
2+
3+
use crate::Header;
4+
use core::borrow::Borrow;
5+
use core::ops::Deref;
6+
7+
/// Helper to 8-byte align the underlying bytes, as mandated in the Multiboot2
8+
/// spec. With this type, one can create manual and raw Multiboot2 boot
9+
/// information or just the bytes for simple tags, in a manual and raw approach.
10+
#[derive(Debug)]
11+
#[repr(C, align(8))]
12+
pub struct AlignedBytes<const N: usize>(pub [u8; N]);
13+
14+
impl<const N: usize> AlignedBytes<N> {
15+
/// Creates a new type.
16+
#[must_use]
17+
pub const fn new(bytes: [u8; N]) -> Self {
18+
Self(bytes)
19+
}
20+
}
21+
22+
impl<const N: usize> Borrow<[u8; N]> for AlignedBytes<N> {
23+
fn borrow(&self) -> &[u8; N] {
24+
&self.0
25+
}
26+
}
27+
28+
impl<const N: usize> Borrow<[u8]> for AlignedBytes<N> {
29+
fn borrow(&self) -> &[u8] {
30+
&self.0
31+
}
32+
}
33+
34+
impl<const N: usize> Deref for AlignedBytes<N> {
35+
type Target = [u8; N];
36+
37+
fn deref(&self) -> &Self::Target {
38+
&self.0
39+
}
40+
}
41+
42+
/// Dummy test header.
43+
#[derive(Debug, PartialEq, Eq)]
44+
#[repr(C, align(8))]
45+
pub struct DummyTestHeader {
46+
typ: u32,
47+
size: u32,
48+
}
49+
50+
impl DummyTestHeader {
51+
#[must_use]
52+
pub const fn typ(&self) -> u32 {
53+
self.typ
54+
}
55+
56+
#[must_use]
57+
pub const fn size(&self) -> u32 {
58+
self.size
59+
}
60+
}
61+
62+
impl Header for DummyTestHeader {
63+
fn payload_len(&self) -> usize {
64+
self.size as usize - size_of::<Self>()
65+
}
66+
}
67+
68+
#[cfg(test)]
69+
mod tests {
70+
use super::*;
71+
use crate::ALIGNMENT;
72+
use core::mem;
73+
use core::ptr::addr_of;
74+
75+
#[test]
76+
fn abi() {
77+
assert_eq!(mem::align_of::<AlignedBytes<0>>(), ALIGNMENT);
78+
79+
let bytes = AlignedBytes([0]);
80+
assert_eq!(bytes.as_ptr().align_offset(8), 0);
81+
assert_eq!((addr_of!(bytes[0])).align_offset(8), 0);
82+
83+
let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7]);
84+
assert_eq!(bytes.as_ptr().align_offset(8), 0);
85+
assert_eq!((addr_of!(bytes[0])).align_offset(8), 0);
86+
87+
let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
88+
assert_eq!(bytes.as_ptr().align_offset(8), 0);
89+
assert_eq!((addr_of!(bytes[0])).align_offset(8), 0);
90+
assert_eq!((addr_of!(bytes[7])).align_offset(8), 1);
91+
assert_eq!((addr_of!(bytes[8])).align_offset(8), 0);
92+
assert_eq!((addr_of!(bytes[9])).align_offset(8), 7);
93+
}
94+
}

multiboot2/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ unstable = []
4444
bitflags.workspace = true
4545
derive_more.workspace = true
4646
log.workspace = true
47+
ptr_meta.workspace = true
4748

48-
ptr_meta = { version = "~0.2", default-features = false }
4949
# We only use a very basic type definition from this crate. To prevent MSRV
5050
# bumps from uefi-raw, I restrict this here. Upstream users are likely to have
5151
# two versions of this library in it, which is no problem, as we only use the

multiboot2/src/lib.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,6 @@ pub use vbe_info::{
113113
/// machine state.
114114
pub const MAGIC: u32 = 0x36d76289;
115115

116-
/// The required alignment for tags and the boot information.
117-
pub const ALIGNMENT: usize = 8;
118-
119116
#[cfg(test)]
120117
mod tests {
121118
use super::*;

multiboot2/src/tag.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -328,33 +328,6 @@ mod tests {
328328
assert_eq!(custom_tag.payload[3], 0x13);
329329
}
330330

331-
#[test]
332-
fn test_tag_bytes_ref() {
333-
let empty: &[u8] = &[];
334-
assert_eq!(
335-
TagBytesRef::try_from(empty),
336-
Err(MemoryError::MinLengthNotSatisfied)
337-
);
338-
339-
let slice = &[0_u8, 1, 2, 3, 4, 5, 6];
340-
assert_eq!(
341-
TagBytesRef::try_from(&slice[..]),
342-
Err(MemoryError::MinLengthNotSatisfied)
343-
);
344-
345-
let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0]);
346-
// Guaranteed wrong alignment
347-
let unaligned_slice = &slice[3..];
348-
assert_eq!(
349-
TagBytesRef::try_from(unaligned_slice),
350-
Err(MemoryError::WrongAlignment)
351-
);
352-
353-
let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7]);
354-
let slice = &slice[..];
355-
assert_eq!(TagBytesRef::try_from(slice), Ok(TagBytesRef(slice)));
356-
}
357-
358331
#[test]
359332
fn test_create_generic_tag() {
360333
#[rustfmt::skip]

0 commit comments

Comments
 (0)