Skip to main content

simple_someip/protocol/sd/
header.rs

1use crate::protocol::byte_order::WriteBytesExt;
2
3use crate::traits::WireFormat;
4
5use super::{
6    Entry, Flags, Options,
7    entry::{ENTRY_SIZE, EntryIter, EntryType},
8    options::{OptionIter, validate_option},
9};
10
11/// An SD header that borrows its entries and options slices.
12///
13/// Used for constructing and encoding outgoing SD messages. For zero-copy
14/// parsing of incoming SD messages, see [`SdHeaderView`].
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct Header<'a> {
17    /// The SD flags byte (reboot + unicast).
18    pub flags: Flags,
19    /// The SD entries.
20    pub entries: &'a [Entry],
21    /// The SD options.
22    pub options: &'a [Options],
23}
24
25impl<'a> Header<'a> {
26    /// Creates a new SD header from the given flags, entries, and options.
27    #[must_use]
28    pub const fn new(flags: Flags, entries: &'a [Entry], options: &'a [Options]) -> Self {
29        Self {
30            flags,
31            entries,
32            options,
33        }
34    }
35}
36
37/// Zero-copy view into an SD header payload.
38///
39/// Created by [`SdHeaderView::parse`], which fully validates the SD header,
40/// entries, and options upfront. This makes the entry and option iterators
41/// infallible.
42#[derive(Clone, Copy, Debug)]
43pub struct SdHeaderView<'a> {
44    flags: Flags,
45    entries_buf: &'a [u8],
46    options_buf: &'a [u8],
47}
48
49impl<'a> SdHeaderView<'a> {
50    /// Parse and fully validate an SD header from `buf`.
51    ///
52    /// Validates:
53    /// - Buffer has enough data for flags + `entries_size` + entries + `options_size` + options
54    /// - `entries_size` is a multiple of `ENTRY_SIZE` (16)
55    /// - All entry type bytes are valid
56    /// - All options have valid types and lengths
57    ///
58    /// # Errors
59    ///
60    /// Returns an error if the buffer is too short, `entries_size` is not a multiple of 16,
61    /// any entry type byte is invalid, or any option has an invalid type or length.
62    pub fn parse(buf: &'a [u8]) -> Result<Self, crate::protocol::Error> {
63        // Minimum: 4 (flags+reserved) + 4 (entries_size) + 4 (options_size) = 12
64        if buf.len() < 12 {
65            return Err(crate::protocol::Error::UnexpectedEof);
66        }
67
68        let flags = Flags::from(buf[0]);
69        // bytes [1..4] are reserved
70
71        let entries_size = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]) as usize;
72
73        if !entries_size.is_multiple_of(ENTRY_SIZE) {
74            return Err(super::Error::IncorrectEntriesSize(entries_size).into());
75        }
76
77        // Need entries data + 4 bytes for options_size field
78        if buf.len() < 8 + entries_size + 4 {
79            return Err(crate::protocol::Error::UnexpectedEof);
80        }
81
82        let entries_buf = &buf[8..8 + entries_size];
83
84        // Validate all entry type bytes
85        let mut offset = 0;
86        while offset < entries_size {
87            EntryType::try_from(entries_buf[offset])?;
88            offset += ENTRY_SIZE;
89        }
90
91        let options_size_offset = 8 + entries_size;
92        let options_size = u32::from_be_bytes([
93            buf[options_size_offset],
94            buf[options_size_offset + 1],
95            buf[options_size_offset + 2],
96            buf[options_size_offset + 3],
97        ]) as usize;
98
99        let options_start = options_size_offset + 4;
100        if buf.len() < options_start + options_size {
101            return Err(crate::protocol::Error::UnexpectedEof);
102        }
103
104        let options_buf = &buf[options_start..options_start + options_size];
105
106        // Validate all options
107        let mut opt_offset = 0;
108        while opt_offset < options_size {
109            let remaining = &options_buf[opt_offset..];
110            let wire_size = validate_option(remaining)?;
111            opt_offset += wire_size;
112        }
113
114        Ok(Self {
115            flags,
116            entries_buf,
117            options_buf,
118        })
119    }
120
121    /// Returns the SD flags.
122    #[must_use]
123    pub fn flags(&self) -> Flags {
124        self.flags
125    }
126
127    /// Returns an iterator over the SD entries.
128    #[must_use]
129    pub fn entries(&self) -> EntryIter<'a> {
130        EntryIter::new(self.entries_buf)
131    }
132
133    /// Returns an iterator over the SD options.
134    #[must_use]
135    pub fn options(&self) -> OptionIter<'a> {
136        OptionIter::new(self.options_buf)
137    }
138
139    /// Returns the number of entries in this SD header.
140    #[must_use]
141    pub fn entry_count(&self) -> usize {
142        self.entries_buf.len() / ENTRY_SIZE
143    }
144}
145
146impl WireFormat for Header<'_> {
147    fn required_size(&self) -> usize {
148        let mut size = 12 + self.entries.len() * ENTRY_SIZE;
149        for option in self.options {
150            size += option.size();
151        }
152        size
153    }
154
155    fn encode<T: embedded_io::Write>(
156        &self,
157        writer: &mut T,
158    ) -> Result<usize, crate::protocol::Error> {
159        writer.write_u8(u8::from(self.flags))?;
160        let reserved: [u8; 3] = [0; 3];
161        writer.write_bytes(&reserved)?;
162        let entries_size = u32::try_from(self.entries.len() * 16).expect("entries size fits u32");
163        writer.write_u32_be(entries_size)?;
164        for entry in self.entries {
165            entry.encode(writer)?;
166        }
167        let mut options_size = 0;
168        for option in self.options {
169            options_size += option.size();
170        }
171        writer.write_u32_be(u32::try_from(options_size).expect("options size fits u32"))?;
172        for option in self.options {
173            option.write(writer)?;
174        }
175        Ok(12 + entries_size as usize + options_size)
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use core::net::Ipv4Addr;
182
183    use super::*;
184    use crate::{
185        protocol::sd::{
186            Error as SdError, EventGroupEntry, OptionsCount, ServiceEntry, TransportProtocol,
187        },
188        traits::WireFormat,
189    };
190
191    fn ipv4_endpoint_bytes(ip: [u8; 4], protocol: u8, port: u16) -> [u8; 12] {
192        let mut b = [0u8; 12];
193        b[0] = 0x00;
194        b[1] = 0x09; // length = 9 (size - 3)
195        b[2] = 0x04; // type = IpV4Endpoint
196        b[3] = 0x00; // discard flag = 0
197        b[4..8].copy_from_slice(&ip);
198        b[8] = 0x00; // reserved
199        b[9] = protocol;
200        b[10] = (port >> 8) as u8;
201        b[11] = (port & 0xFF) as u8;
202        b
203    }
204
205    fn raw_header(entries_size: u32, options_size: u32) -> [u8; 12] {
206        let mut b = [0u8; 12];
207        // flags = 0, reserved = 0
208        b[4..8].copy_from_slice(&entries_size.to_be_bytes());
209        b[8..12].copy_from_slice(&options_size.to_be_bytes());
210        b
211    }
212
213    #[test]
214    fn header_new_stores_fields() {
215        let flags = Flags::new_sd(true);
216        let entries: &[Entry] = &[];
217        let options: &[Options] = &[];
218        let h = Header::new(flags, entries, options);
219        assert_eq!(h.flags, flags);
220        assert!(h.entries.is_empty());
221        assert!(h.options.is_empty());
222    }
223
224    #[test]
225    fn service_offer_round_trips() {
226        let ip = Ipv4Addr::new(192, 168, 1, 10);
227        let entry = Entry::OfferService(ServiceEntry {
228            service_id: 0x1234,
229            instance_id: 0x0001,
230            major_version: 1,
231            ttl: 0xFFFFFF,
232            index_first_options_run: 0,
233            index_second_options_run: 0,
234            options_count: OptionsCount::new(1, 0),
235            minor_version: 0,
236        });
237        let endpoint = Options::IpV4Endpoint {
238            ip,
239            protocol: TransportProtocol::Udp,
240            port: 30509,
241        };
242        let entries = [entry];
243        let options = [endpoint];
244        let h = Header::new(Flags::new_sd(false), &entries, &options);
245        assert_eq!(h.required_size(), 40);
246        let mut buf = [0u8; 64];
247        h.encode(&mut buf.as_mut_slice()).unwrap();
248        let view = SdHeaderView::parse(&buf[..h.required_size()]).unwrap();
249        assert_eq!(view.entry_count(), 1);
250        let entry_view = view.entries().next().unwrap();
251        assert_eq!(entry_view.service_id(), 0x1234);
252    }
253
254    #[test]
255    fn subscribe_ack_round_trips() {
256        let entry = Entry::SubscribeAckEventGroup(EventGroupEntry::new(
257            0xAAAA, 0x0001, 1, 0xFFFFFF, 0x0010,
258        ));
259        let entries = [entry];
260        let h = Header::new(Flags::new_sd(true), &entries, &[]);
261        assert_eq!(h.required_size(), 28);
262        let mut buf = [0u8; 32];
263        h.encode(&mut buf.as_mut_slice()).unwrap();
264        let view = SdHeaderView::parse(&buf[..h.required_size()]).unwrap();
265        assert_eq!(view.entry_count(), 1);
266    }
267
268    #[test]
269    fn parse_exact_size_slice_succeeds() {
270        let entry = Entry::OfferService(ServiceEntry {
271            service_id: 0x1234,
272            instance_id: 0x0001,
273            major_version: 1,
274            ttl: 0xFFFFFF,
275            index_first_options_run: 0,
276            index_second_options_run: 0,
277            options_count: OptionsCount::new(1, 0),
278            minor_version: 0,
279        });
280        let endpoint = Options::IpV4Endpoint {
281            ip: Ipv4Addr::new(192, 168, 1, 10),
282            protocol: TransportProtocol::Udp,
283            port: 30509,
284        };
285        let entries = [entry];
286        let options = [endpoint];
287        let h = Header::new(Flags::new_sd(false), &entries, &options);
288        let mut buf = [0u8; 64];
289        let n = h.encode(&mut buf.as_mut_slice()).unwrap();
290        let view = SdHeaderView::parse(&buf[..n]).unwrap();
291        assert_eq!(view.entry_count(), 1);
292    }
293
294    #[test]
295    fn parse_options_size_below_minimum_returns_error() {
296        let prefix = raw_header(0, 2);
297        let mut buf = [0u8; 14];
298        buf[..12].copy_from_slice(&prefix);
299        assert!(matches!(
300            SdHeaderView::parse(&buf),
301            Err(crate::protocol::Error::Sd(SdError::IncorrectOptionsSize(2)))
302        ));
303    }
304
305    #[test]
306    fn parse_option_size_exceeds_declared_remaining_returns_error() {
307        let prefix = raw_header(0, 5);
308        let option = ipv4_endpoint_bytes([127, 0, 0, 1], 0x11, 1234);
309        let mut buf = [0u8; 24];
310        buf[..12].copy_from_slice(&prefix);
311        buf[12..24].copy_from_slice(&option);
312        assert!(matches!(
313            SdHeaderView::parse(&buf),
314            Err(crate::protocol::Error::Sd(SdError::IncorrectOptionsSize(5)))
315        ));
316    }
317
318    // --- SdHeaderView accessors ---
319
320    #[test]
321    fn sd_header_view_entry_count() {
322        let entries = [
323            Entry::FindService(ServiceEntry::find(0x0001)),
324            Entry::FindService(ServiceEntry::find(0x0002)),
325        ];
326        let h = Header::new(Flags::new_sd(false), &entries, &[]);
327        let mut buf = [0u8; 64];
328        h.encode(&mut buf.as_mut_slice()).unwrap();
329        let view = SdHeaderView::parse(&buf[..h.required_size()]).unwrap();
330        assert_eq!(view.entry_count(), 2);
331    }
332
333    #[test]
334    fn sd_header_view_flags() {
335        let h = Header::new(Flags::new_sd(true), &[], &[]);
336        let mut buf = [0u8; 16];
337        h.encode(&mut buf.as_mut_slice()).unwrap();
338        let view = SdHeaderView::parse(&buf[..h.required_size()]).unwrap();
339        assert_eq!(view.flags(), h.flags);
340    }
341
342    #[test]
343    fn parse_incorrect_entries_size_returns_error() {
344        let mut buf = [0u8; 12];
345        buf[4..8].copy_from_slice(&5u32.to_be_bytes());
346        assert!(matches!(
347            SdHeaderView::parse(&buf),
348            Err(crate::protocol::Error::Sd(SdError::IncorrectEntriesSize(5)))
349        ));
350    }
351}