Skip to main content

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