1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 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);
}
}