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, and IP-bearing options have a
57    ///   recognized transport protocol byte
58    ///
59    /// # Errors
60    ///
61    /// Returns an error if the buffer is too short, `entries_size` is not a multiple of 16,
62    /// any entry type byte is invalid, or any option has an invalid type, length, or
63    /// transport protocol byte.
64    pub fn parse(buf: &'a [u8]) -> Result<Self, crate::protocol::Error> {
65        // Minimum: 4 (flags+reserved) + 4 (entries_size) + 4 (options_size) = 12
66        if buf.len() < 12 {
67            return Err(crate::protocol::Error::UnexpectedEof);
68        }
69
70        let flags = Flags::from(buf[0]);
71        // bytes [1..4] are reserved
72
73        let entries_size = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]) as usize;
74
75        if !entries_size.is_multiple_of(ENTRY_SIZE) {
76            return Err(super::Error::IncorrectEntriesSize(entries_size).into());
77        }
78
79        // Need entries data + 4 bytes for options_size field
80        if buf.len() < 8 + entries_size + 4 {
81            return Err(crate::protocol::Error::UnexpectedEof);
82        }
83
84        let entries_buf = &buf[8..8 + entries_size];
85
86        // Validate all entry type bytes
87        let mut offset = 0;
88        while offset < entries_size {
89            EntryType::try_from(entries_buf[offset])?;
90            offset += ENTRY_SIZE;
91        }
92
93        let options_size_offset = 8 + entries_size;
94        let options_size = u32::from_be_bytes([
95            buf[options_size_offset],
96            buf[options_size_offset + 1],
97            buf[options_size_offset + 2],
98            buf[options_size_offset + 3],
99        ]) as usize;
100
101        let options_start = options_size_offset + 4;
102        if buf.len() < options_start + options_size {
103            return Err(crate::protocol::Error::UnexpectedEof);
104        }
105
106        let options_buf = &buf[options_start..options_start + options_size];
107
108        // Validate all options
109        let mut opt_offset = 0;
110        while opt_offset < options_size {
111            let remaining = &options_buf[opt_offset..];
112            let wire_size = validate_option(remaining)?;
113            opt_offset += wire_size;
114        }
115
116        Ok(Self {
117            flags,
118            entries_buf,
119            options_buf,
120        })
121    }
122
123    /// Returns the SD flags.
124    #[must_use]
125    pub fn flags(&self) -> Flags {
126        self.flags
127    }
128
129    /// Returns an iterator over the SD entries.
130    #[must_use]
131    pub fn entries(&self) -> EntryIter<'a> {
132        EntryIter::new(self.entries_buf)
133    }
134
135    /// Returns an iterator over the SD options.
136    #[must_use]
137    pub fn options(&self) -> OptionIter<'a> {
138        OptionIter::new(self.options_buf)
139    }
140
141    /// Returns the number of entries in this SD header.
142    #[must_use]
143    pub fn entry_count(&self) -> usize {
144        self.entries_buf.len() / ENTRY_SIZE
145    }
146}
147
148impl WireFormat for Header<'_> {
149    fn required_size(&self) -> usize {
150        let mut size = 12 + self.entries.len() * ENTRY_SIZE;
151        for option in self.options {
152            size += option.size();
153        }
154        size
155    }
156
157    fn encode<T: embedded_io::Write>(
158        &self,
159        writer: &mut T,
160    ) -> Result<usize, crate::protocol::Error> {
161        writer.write_u8(u8::from(self.flags))?;
162        let reserved: [u8; 3] = [0; 3];
163        writer.write_bytes(&reserved)?;
164        let entries_size = u32::try_from(self.entries.len() * 16).expect("entries size fits u32");
165        writer.write_u32_be(entries_size)?;
166        for entry in self.entries {
167            entry.encode(writer)?;
168        }
169        let mut options_size = 0;
170        for option in self.options {
171            options_size += option.size();
172        }
173        writer.write_u32_be(u32::try_from(options_size).expect("options size fits u32"))?;
174        for option in self.options {
175            option.write(writer)?;
176        }
177        Ok(12 + entries_size as usize + options_size)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use core::net::Ipv4Addr;
184
185    use super::*;
186    use crate::{
187        protocol::sd::{
188            Error as SdError, EventGroupEntry, OptionType, OptionsCount, RebootFlag, ServiceEntry,
189            TransportProtocol,
190            options::{
191                IPV4_OPTION_IP_OFFSET, IPV4_OPTION_LENGTH_FIELD, IPV4_OPTION_PORT_OFFSET,
192                IPV4_OPTION_PROTOCOL_OFFSET, IPV4_OPTION_WIRE_SIZE,
193            },
194        },
195        traits::WireFormat,
196    };
197
198    fn ipv4_endpoint_bytes(ip: [u8; 4], protocol: u8, port: u16) -> [u8; IPV4_OPTION_WIRE_SIZE] {
199        let mut b = [0u8; IPV4_OPTION_WIRE_SIZE];
200        b[0..2].copy_from_slice(&IPV4_OPTION_LENGTH_FIELD.to_be_bytes());
201        b[2] = u8::from(OptionType::IpV4Endpoint);
202        // b[3] is the discard flag (0).
203        b[IPV4_OPTION_IP_OFFSET..IPV4_OPTION_IP_OFFSET + 4].copy_from_slice(&ip);
204        // b[IPV4_OPTION_IP_OFFSET + 4] is reserved (0).
205        b[IPV4_OPTION_PROTOCOL_OFFSET] = protocol;
206        b[IPV4_OPTION_PORT_OFFSET..IPV4_OPTION_PORT_OFFSET + 2]
207            .copy_from_slice(&port.to_be_bytes());
208        b
209    }
210
211    fn raw_header(entries_size: u32, options_size: u32) -> [u8; 12] {
212        let mut b = [0u8; 12];
213        // flags = 0, reserved = 0
214        b[4..8].copy_from_slice(&entries_size.to_be_bytes());
215        b[8..12].copy_from_slice(&options_size.to_be_bytes());
216        b
217    }
218
219    #[test]
220    fn header_new_stores_fields() {
221        let flags = Flags::new_sd(RebootFlag::RecentlyRebooted);
222        let entries: &[Entry] = &[];
223        let options: &[Options] = &[];
224        let h = Header::new(flags, entries, options);
225        assert_eq!(h.flags, flags);
226        assert!(h.entries.is_empty());
227        assert!(h.options.is_empty());
228    }
229
230    #[test]
231    fn service_offer_round_trips() {
232        let ip = Ipv4Addr::new(192, 168, 1, 10);
233        let entry = Entry::OfferService(ServiceEntry {
234            service_id: 0x1234,
235            instance_id: 0x0001,
236            major_version: 1,
237            ttl: 0xFF_FFFF,
238            index_first_options_run: 0,
239            index_second_options_run: 0,
240            options_count: OptionsCount::new(1, 0),
241            minor_version: 0,
242        });
243        let endpoint = Options::IpV4Endpoint {
244            ip,
245            protocol: TransportProtocol::Udp,
246            port: 30509,
247        };
248        let entries = [entry];
249        let options = [endpoint];
250        let h = Header::new(
251            Flags::new_sd(RebootFlag::RecentlyRebooted),
252            &entries,
253            &options,
254        );
255        assert_eq!(h.required_size(), 40);
256        let mut buf = [0u8; 64];
257        h.encode(&mut buf.as_mut_slice()).unwrap();
258        let view = SdHeaderView::parse(&buf[..h.required_size()]).unwrap();
259        assert_eq!(view.entry_count(), 1);
260        let entry_view = view.entries().next().unwrap();
261        assert_eq!(entry_view.service_id(), 0x1234);
262    }
263
264    #[test]
265    fn subscribe_ack_round_trips() {
266        let entry = Entry::SubscribeAckEventGroup(EventGroupEntry::new(
267            0xAAAA, 0x0001, 1, 0xFF_FFFF, 0x0010,
268        ));
269        let entries = [entry];
270        let h = Header::new(Flags::new_sd(RebootFlag::RecentlyRebooted), &entries, &[]);
271        assert_eq!(h.required_size(), 28);
272        let mut buf = [0u8; 32];
273        h.encode(&mut buf.as_mut_slice()).unwrap();
274        let view = SdHeaderView::parse(&buf[..h.required_size()]).unwrap();
275        assert_eq!(view.entry_count(), 1);
276    }
277
278    #[test]
279    fn parse_exact_size_slice_succeeds() {
280        let entry = Entry::OfferService(ServiceEntry {
281            service_id: 0x1234,
282            instance_id: 0x0001,
283            major_version: 1,
284            ttl: 0xFF_FFFF,
285            index_first_options_run: 0,
286            index_second_options_run: 0,
287            options_count: OptionsCount::new(1, 0),
288            minor_version: 0,
289        });
290        let endpoint = Options::IpV4Endpoint {
291            ip: Ipv4Addr::new(192, 168, 1, 10),
292            protocol: TransportProtocol::Udp,
293            port: 30509,
294        };
295        let entries = [entry];
296        let options = [endpoint];
297        let h = Header::new(
298            Flags::new_sd(RebootFlag::RecentlyRebooted),
299            &entries,
300            &options,
301        );
302        let mut buf = [0u8; 64];
303        let n = h.encode(&mut buf.as_mut_slice()).unwrap();
304        let view = SdHeaderView::parse(&buf[..n]).unwrap();
305        assert_eq!(view.entry_count(), 1);
306    }
307
308    #[test]
309    fn parse_options_size_below_minimum_returns_error() {
310        let prefix = raw_header(0, 2);
311        let mut buf = [0u8; 14];
312        buf[..12].copy_from_slice(&prefix);
313        assert!(matches!(
314            SdHeaderView::parse(&buf),
315            Err(crate::protocol::Error::Sd(SdError::IncorrectOptionsSize(2)))
316        ));
317    }
318
319    #[test]
320    fn parse_option_size_exceeds_declared_remaining_returns_error() {
321        let prefix = raw_header(0, 5);
322        let option = ipv4_endpoint_bytes([127, 0, 0, 1], 0x11, 1234);
323        let mut buf = [0u8; 24];
324        buf[..12].copy_from_slice(&prefix);
325        buf[12..24].copy_from_slice(&option);
326        assert!(matches!(
327            SdHeaderView::parse(&buf),
328            Err(crate::protocol::Error::Sd(SdError::IncorrectOptionsSize(5)))
329        ));
330    }
331
332    // --- SdHeaderView accessors ---
333
334    #[test]
335    fn sd_header_view_entry_count() {
336        let entries = [
337            Entry::FindService(ServiceEntry::find(0x0001)),
338            Entry::FindService(ServiceEntry::find(0x0002)),
339        ];
340        let h = Header::new(Flags::new_sd(RebootFlag::RecentlyRebooted), &entries, &[]);
341        let mut buf = [0u8; 64];
342        h.encode(&mut buf.as_mut_slice()).unwrap();
343        let view = SdHeaderView::parse(&buf[..h.required_size()]).unwrap();
344        assert_eq!(view.entry_count(), 2);
345    }
346
347    #[test]
348    fn sd_header_view_flags() {
349        let h = Header::new(Flags::new_sd(RebootFlag::RecentlyRebooted), &[], &[]);
350        let mut buf = [0u8; 16];
351        h.encode(&mut buf.as_mut_slice()).unwrap();
352        let view = SdHeaderView::parse(&buf[..h.required_size()]).unwrap();
353        assert_eq!(view.flags(), h.flags);
354    }
355
356    #[test]
357    fn parse_incorrect_entries_size_returns_error() {
358        let mut buf = [0u8; 12];
359        buf[4..8].copy_from_slice(&5u32.to_be_bytes());
360        assert!(matches!(
361            SdHeaderView::parse(&buf),
362            Err(crate::protocol::Error::Sd(SdError::IncorrectEntriesSize(5)))
363        ));
364    }
365
366    #[test]
367    fn parse_rejects_ipv4_option_with_invalid_transport_protocol() {
368        // An IPv4 endpoint option with an otherwise-valid wire layout but a
369        // transport protocol byte that is neither UDP (0x11) nor TCP (0x06)
370        // must be rejected by parse, so downstream `as_ipv4()` calls cannot
371        // observe a bad protocol byte at walk time.
372        const SD_HEADER_PREFIX_SIZE: usize = 12;
373        let options_size = u32::try_from(IPV4_OPTION_WIRE_SIZE).expect("wire size fits u32");
374        let prefix = raw_header(0, options_size);
375        let option = ipv4_endpoint_bytes([10, 0, 0, 1], 0xAB, 30490);
376        let mut buf = [0u8; SD_HEADER_PREFIX_SIZE + IPV4_OPTION_WIRE_SIZE];
377        buf[..SD_HEADER_PREFIX_SIZE].copy_from_slice(&prefix);
378        buf[SD_HEADER_PREFIX_SIZE..].copy_from_slice(&option);
379        assert!(matches!(
380            SdHeaderView::parse(&buf),
381            Err(crate::protocol::Error::Sd(
382                SdError::InvalidOptionTransportProtocol(0xAB)
383            ))
384        ));
385    }
386}