multiboot2_common/
iter.rs

1//! Iterator over Multiboot2 structures. Technically, the process for iterating
2//! Multiboot2 information tags and iterating Multiboot2 header tags is the
3//! same.
4
5use crate::{increase_to_alignment, DynSizedStructure, Header, ALIGNMENT};
6use core::marker::PhantomData;
7use core::mem;
8
9/// Iterates over the tags (modelled by [`DynSizedStructure`]) of the underlying
10/// byte slice. Each tag is expected to have the same common [`Header`].
11///
12/// As the iterator emits elements of type [`DynSizedStructure`], users should
13/// casted them to specific [`Tag`]s using [`DynSizedStructure::cast`] following
14/// a user policy. This can for example happen on the basis of some ID.
15///
16/// This type ensures the memory safety guarantees promised by this crates
17/// documentation.
18///
19/// [`Tag`]: crate::Tag
20#[derive(Clone, Debug)]
21pub struct TagIter<'a, H: Header> {
22    /// Absolute offset to next tag and updated in each iteration.
23    next_tag_offset: usize,
24    buffer: &'a [u8],
25    // Ensure that all instances are bound to a specific `Header`.
26    // Otherwise, UB can happen.
27    _t: PhantomData<H>,
28}
29
30impl<'a, H: Header> TagIter<'a, H> {
31    /// Creates a new iterator.
32    #[must_use]
33    pub fn new(mem: &'a [u8]) -> Self {
34        // Assert alignment.
35        assert_eq!(mem.as_ptr().align_offset(ALIGNMENT), 0);
36
37        TagIter {
38            next_tag_offset: 0,
39            buffer: mem,
40            _t: PhantomData,
41        }
42    }
43}
44
45impl<'a, H: Header + 'a> Iterator for TagIter<'a, H> {
46    type Item = &'a DynSizedStructure<H>;
47
48    fn next(&mut self) -> Option<Self::Item> {
49        if self.next_tag_offset == self.buffer.len() {
50            return None;
51        }
52        assert!(self.next_tag_offset < self.buffer.len());
53
54        let ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) }.cast::<H>();
55        let tag_hdr = unsafe { &*ptr };
56
57        // Get relevant byte portion for the next tag. This includes padding
58        // bytes to fulfill Rust memory guarantees. Otherwise, Miri complains.
59        // See <https://doc.rust-lang.org/reference/type-layout.html>.
60        let slice = {
61            let from = self.next_tag_offset;
62            let len = mem::size_of::<H>() + tag_hdr.payload_len();
63            let to = from + len;
64
65            // The size of (the allocation for) a value is always a multiple of
66            // its alignment.
67            // https://doc.rust-lang.org/reference/type-layout.html
68            let to = increase_to_alignment(to);
69
70            // Update ptr for next iteration.
71            self.next_tag_offset += to - from;
72
73            &self.buffer[from..to]
74        };
75
76        // unwrap: We should not fail at this point.
77        let tag = DynSizedStructure::ref_from_slice(slice).unwrap();
78        Some(tag)
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use crate::test_utils::{AlignedBytes, DummyTestHeader};
85    use crate::TagIter;
86    use core::borrow::Borrow;
87
88    #[test]
89    fn test_tag_iter() {
90        #[rustfmt::skip]
91        let bytes = AlignedBytes::new(
92            [
93                /* Some minimal tag.  */
94                0xff, 0, 0, 0,
95                8, 0, 0, 0,
96                /* Some tag with payload.  */
97                0xfe, 0, 0, 0,
98                12, 0, 0, 0,
99                1, 2, 3, 4,
100                // Padding
101                0, 0, 0, 0,
102                /* End tag */
103                0, 0, 0, 0,
104                8, 0, 0, 0,
105            ],
106        );
107        let mut iter = TagIter::<DummyTestHeader>::new(bytes.borrow());
108        let first = iter.next().unwrap();
109        assert_eq!(first.header().typ(), 0xff);
110        assert_eq!(first.header().size(), 8);
111        assert!(first.payload().is_empty());
112
113        let second = iter.next().unwrap();
114        assert_eq!(second.header().typ(), 0xfe);
115        assert_eq!(second.header().size(), 12);
116        assert_eq!(&second.payload(), &[1, 2, 3, 4]);
117
118        let third = iter.next().unwrap();
119        assert_eq!(third.header().typ(), 0);
120        assert_eq!(third.header().size(), 8);
121        assert!(first.payload().is_empty());
122
123        assert_eq!(iter.next(), None);
124    }
125}