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