Skip to main content

simple_someip/protocol/sd/
options.rs

1use core::net::{Ipv4Addr, Ipv6Addr};
2
3use super::Error;
4use crate::protocol::byte_order::WriteBytesExt;
5use automotive_wire_codec::{Decode, DecodeIter, Encode, ensure_len, take};
6
7/// Maximum length of an SD configuration option string in bytes.
8pub const MAX_CONFIGURATION_STRING_LENGTH: usize = 256;
9
10// --- SD option wire-layout constants ---
11//
12// Every SD option begins with a 4-byte fixed header:
13//   [0..2]: length (u16 BE)        — value is `wire_size - OPTION_LENGTH_SIZE_DELTA`
14//   [2]:    option type (u8)
15//   [3]:    reserved/discard flag (u8)
16// Per-type payload follows starting at offset `OPTION_PAYLOAD_OFFSET`.
17
18/// Size of the fixed SD option header (length + type + discard flag).
19pub(crate) const OPTION_HEADER_SIZE: usize = 4;
20/// The SD option length field encodes `wire_size - OPTION_LENGTH_SIZE_DELTA`.
21pub(crate) const OPTION_LENGTH_SIZE_DELTA: usize = 3;
22/// Byte offset of the option type byte inside the fixed header.
23const OPTION_TYPE_OFFSET: usize = 2;
24/// Byte offset at which per-type payload begins.
25const OPTION_PAYLOAD_OFFSET: usize = 4;
26
27// IPv4 endpoint / multicast / SD options.
28/// Total wire size of an IPv4 endpoint/multicast/SD option.
29pub(crate) const IPV4_OPTION_WIRE_SIZE: usize = 12;
30/// Length-field value stored on the wire for an IPv4 option.
31pub(crate) const IPV4_OPTION_LENGTH_FIELD: u16 = 9;
32/// Byte offset of the 4-octet IPv4 address within the option.
33pub(crate) const IPV4_OPTION_IP_OFFSET: usize = OPTION_PAYLOAD_OFFSET;
34/// Byte offset of the transport protocol byte inside an IPv4 option.
35pub(crate) const IPV4_OPTION_PROTOCOL_OFFSET: usize = 9;
36/// Byte offset of the port (u16 BE) inside an IPv4 option.
37pub(crate) const IPV4_OPTION_PORT_OFFSET: usize = 10;
38
39// IPv6 endpoint / multicast / SD options.
40/// Total wire size of an IPv6 endpoint/multicast/SD option.
41pub(crate) const IPV6_OPTION_WIRE_SIZE: usize = 24;
42/// Length-field value stored on the wire for an IPv6 option.
43pub(crate) const IPV6_OPTION_LENGTH_FIELD: u16 = 21;
44/// Byte offset of the 16-octet IPv6 address within the option.
45const IPV6_OPTION_IP_OFFSET: usize = OPTION_PAYLOAD_OFFSET;
46/// Byte offset (exclusive) marking the end of the 16-octet IPv6 address.
47const IPV6_OPTION_IP_END: usize = IPV6_OPTION_IP_OFFSET + 16;
48/// Byte offset of the transport protocol byte inside an IPv6 option.
49pub(crate) const IPV6_OPTION_PROTOCOL_OFFSET: usize = 21;
50/// Byte offset of the port (u16 BE) inside an IPv6 option.
51const IPV6_OPTION_PORT_OFFSET: usize = 22;
52
53// Load-balancing option.
54/// Total wire size of a load-balancing option.
55const LOAD_BALANCING_OPTION_WIRE_SIZE: usize = 8;
56/// Length-field value stored on the wire for a load-balancing option.
57pub(crate) const LOAD_BALANCING_OPTION_LENGTH_FIELD: u16 = 5;
58
59// Configuration option.
60/// The configuration option's length field value is `1 + string_len`
61/// (the `+1` accounts for the trailing null terminator byte).
62const CONFIGURATION_OPTION_LENGTH_STRING_DELTA: u16 = 1;
63
64pub use crate::net_endpoint::TransportProtocol;
65
66impl TryFrom<u8> for TransportProtocol {
67    type Error = Error;
68    fn try_from(value: u8) -> Result<Self, Error> {
69        match value {
70            0x11 => Ok(TransportProtocol::Udp),
71            0x06 => Ok(TransportProtocol::Tcp),
72            _ => Err(Error::InvalidOptionTransportProtocol(value)),
73        }
74    }
75}
76
77impl TryFrom<TransportProtocol> for u8 {
78    type Error = Error;
79    fn try_from(value: TransportProtocol) -> Result<u8, Error> {
80        match value {
81            TransportProtocol::Udp => Ok(0x11),
82            TransportProtocol::Tcp => Ok(0x06),
83        }
84    }
85}
86
87/// The type of an SD option.
88#[derive(Clone, Copy, Debug, Eq, PartialEq)]
89pub enum OptionType {
90    /// Configuration option (0x01).
91    Configuration,
92    /// Load balancing option (0x02).
93    LoadBalancing,
94    /// IPv4 endpoint option (0x04).
95    IpV4Endpoint,
96    /// IPv6 endpoint option (0x06).
97    IpV6Endpoint,
98    /// IPv4 multicast option (0x14).
99    IpV4Multicast,
100    /// IPv6 multicast option (0x16).
101    IpV6Multicast,
102    /// IPv4 SD option (0x24).
103    IpV4SD,
104    /// IPv6 SD option (0x26).
105    IpV6SD,
106}
107
108impl TryFrom<u8> for OptionType {
109    type Error = Error;
110    fn try_from(value: u8) -> Result<Self, Error> {
111        match value {
112            0x01 => Ok(OptionType::Configuration),
113            0x02 => Ok(OptionType::LoadBalancing),
114            0x04 => Ok(OptionType::IpV4Endpoint),
115            0x06 => Ok(OptionType::IpV6Endpoint),
116            0x14 => Ok(OptionType::IpV4Multicast),
117            0x16 => Ok(OptionType::IpV6Multicast),
118            0x24 => Ok(OptionType::IpV4SD),
119            0x26 => Ok(OptionType::IpV6SD),
120            _ => Err(Error::InvalidOptionType(value)),
121        }
122    }
123}
124
125impl From<OptionType> for u8 {
126    fn from(option_type: OptionType) -> u8 {
127        match option_type {
128            OptionType::Configuration => 0x01,
129            OptionType::LoadBalancing => 0x02,
130            OptionType::IpV4Endpoint => 0x04,
131            OptionType::IpV6Endpoint => 0x06,
132            OptionType::IpV4Multicast => 0x14,
133            OptionType::IpV6Multicast => 0x16,
134            OptionType::IpV4SD => 0x24,
135            OptionType::IpV6SD => 0x26,
136        }
137    }
138}
139
140// Boxing is not available in no_std, so allow the large variant.
141#[allow(clippy::large_enum_variant)]
142/// A decoded SD option.
143#[derive(Clone, Debug, Eq, PartialEq)]
144pub enum Options {
145    /// A configuration key-value string.
146    Configuration {
147        /// The raw configuration string bytes.
148        configuration_string: heapless::Vec<u8, MAX_CONFIGURATION_STRING_LENGTH>,
149    },
150    /// Load balancing parameters.
151    LoadBalancing {
152        /// The priority value.
153        priority: u16,
154        /// The weight value.
155        weight: u16,
156    },
157    /// An IPv4 endpoint.
158    IpV4Endpoint {
159        /// The IPv4 address.
160        ip: Ipv4Addr,
161        /// The transport protocol (UDP or TCP).
162        protocol: TransportProtocol,
163        /// The port number.
164        port: u16,
165    },
166    /// An IPv6 endpoint.
167    IpV6Endpoint {
168        /// The IPv6 address.
169        ip: Ipv6Addr,
170        /// The transport protocol (UDP or TCP).
171        protocol: TransportProtocol,
172        /// The port number.
173        port: u16,
174    },
175    /// An IPv4 multicast address.
176    IpV4Multicast {
177        /// The IPv4 multicast address.
178        ip: Ipv4Addr,
179        /// The transport protocol (UDP or TCP).
180        protocol: TransportProtocol,
181        /// The port number.
182        port: u16,
183    },
184    /// An IPv6 multicast address.
185    IpV6Multicast {
186        /// The IPv6 multicast address.
187        ip: Ipv6Addr,
188        /// The transport protocol (UDP or TCP).
189        protocol: TransportProtocol,
190        /// The port number.
191        port: u16,
192    },
193    /// An IPv4 SD endpoint.
194    IpV4SD {
195        /// The IPv4 address.
196        ip: Ipv4Addr,
197        /// The transport protocol (UDP or TCP).
198        protocol: TransportProtocol,
199        /// The port number.
200        port: u16,
201    },
202    /// An IPv6 SD endpoint.
203    IpV6SD {
204        /// The IPv6 address.
205        ip: Ipv6Addr,
206        /// The transport protocol (UDP or TCP).
207        protocol: TransportProtocol,
208        /// The port number.
209        port: u16,
210    },
211}
212
213impl Options {
214    /// Returns the total wire size of this option in bytes.
215    #[must_use]
216    pub fn size(&self) -> usize {
217        match self {
218            Options::Configuration {
219                configuration_string,
220            } => OPTION_HEADER_SIZE + configuration_string.len(),
221            Options::LoadBalancing { .. } => LOAD_BALANCING_OPTION_WIRE_SIZE,
222            Options::IpV4Endpoint { .. }
223            | Options::IpV4Multicast { .. }
224            | Options::IpV4SD { .. } => IPV4_OPTION_WIRE_SIZE,
225            Options::IpV6Endpoint { .. }
226            | Options::IpV6Multicast { .. }
227            | Options::IpV6SD { .. } => IPV6_OPTION_WIRE_SIZE,
228        }
229    }
230}
231
232impl Encode for Options {
233    type Error = crate::protocol::Error;
234
235    fn encoded_size(&self) -> Result<usize, Self::Error> {
236        Ok(self.size())
237    }
238
239    /// Serializes this option to a writer.
240    ///
241    /// # Errors
242    ///
243    /// Returns an error if writing to the writer fails.
244    ///
245    /// # Panics
246    ///
247    /// Panics if the option size minus `OPTION_LENGTH_SIZE_DELTA` exceeds `u16::MAX`
248    /// (unreachable in practice).
249    fn encode(&self, writer: &mut impl embedded_io::Write) -> Result<usize, Self::Error> {
250        writer.write_u16_be(
251            u16::try_from(self.size() - OPTION_LENGTH_SIZE_DELTA).expect("option size fits u16"),
252        )?;
253        match self {
254            Options::Configuration {
255                configuration_string,
256            } => {
257                writer.write_u8(u8::from(OptionType::Configuration))?;
258                writer.write_u8(0)?;
259                writer.write_bytes(configuration_string)?;
260                Ok(self.size())
261            }
262            Options::LoadBalancing { priority, weight } => {
263                writer.write_u8(u8::from(OptionType::LoadBalancing))?;
264                writer.write_u8(0)?;
265                writer.write_u16_be(*priority)?;
266                writer.write_u16_be(*weight)?;
267                Ok(LOAD_BALANCING_OPTION_WIRE_SIZE)
268            }
269            Options::IpV4Endpoint { ip, protocol, port } => {
270                write_ipv4_option(writer, OptionType::IpV4Endpoint, *ip, *protocol, *port)
271            }
272            Options::IpV6Endpoint { ip, protocol, port } => {
273                write_ipv6_option(writer, OptionType::IpV6Endpoint, *ip, *protocol, *port)
274            }
275            Options::IpV4Multicast { ip, protocol, port } => {
276                write_ipv4_option(writer, OptionType::IpV4Multicast, *ip, *protocol, *port)
277            }
278            Options::IpV6Multicast { ip, protocol, port } => {
279                write_ipv6_option(writer, OptionType::IpV6Multicast, *ip, *protocol, *port)
280            }
281            Options::IpV4SD { ip, protocol, port } => {
282                write_ipv4_option(writer, OptionType::IpV4SD, *ip, *protocol, *port)
283            }
284            Options::IpV6SD { ip, protocol, port } => {
285                write_ipv6_option(writer, OptionType::IpV6SD, *ip, *protocol, *port)
286            }
287        }
288    }
289}
290
291fn write_ipv4_option<T: embedded_io::Write>(
292    writer: &mut T,
293    option_type: OptionType,
294    ip: Ipv4Addr,
295    protocol: TransportProtocol,
296    port: u16,
297) -> Result<usize, crate::protocol::Error> {
298    writer.write_u8(u8::from(option_type))?;
299    writer.write_u8(0)?;
300    writer.write_u32_be(ip.to_bits())?;
301    writer.write_u8(0)?;
302    writer.write_u8(u8::try_from(protocol)?)?;
303    writer.write_u16_be(port)?;
304    Ok(IPV4_OPTION_WIRE_SIZE)
305}
306
307fn write_ipv6_option<T: embedded_io::Write>(
308    writer: &mut T,
309    option_type: OptionType,
310    ip: Ipv6Addr,
311    protocol: TransportProtocol,
312    port: u16,
313) -> Result<usize, crate::protocol::Error> {
314    writer.write_u8(u8::from(option_type))?;
315    writer.write_u8(0)?;
316    writer.write_bytes(&ip.octets())?;
317    writer.write_u8(0)?;
318    writer.write_u8(u8::try_from(protocol)?)?;
319    writer.write_u16_be(port)?;
320    Ok(IPV6_OPTION_WIRE_SIZE)
321}
322
323/// Extract the first `IpV4Endpoint` (socket address + transport
324/// protocol) from a slice of owned options.
325///
326/// Returns `None` if no `IpV4Endpoint` option is present.
327#[must_use]
328pub fn extract_ipv4_endpoint(
329    options: &[Options],
330) -> Option<(core::net::SocketAddrV4, TransportProtocol)> {
331    options.iter().find_map(|opt| match opt {
332        Options::IpV4Endpoint { ip, protocol, port } => {
333            Some((core::net::SocketAddrV4::new(*ip, *port), *protocol))
334        }
335        _ => None,
336    })
337}
338
339// --- Zero-copy view types ---
340
341/// Zero-copy view into a variable-length SD option in a buffer.
342///
343/// Wire layout:
344/// - `[0..2]`: length (u16 BE) = `total_size` - 3
345/// - `[2]`: option type (u8)
346/// - `[3]`: reserved/discard flag (u8)
347/// - `[4..]`: type-specific data
348#[derive(Clone, Copy, Debug)]
349pub struct OptionView<'a>(&'a [u8]);
350
351impl<'a> OptionView<'a> {
352    /// Returns the option type.
353    ///
354    /// # Errors
355    ///
356    /// Returns [`Error::InvalidOptionType`] if the type byte is unrecognized.
357    pub fn option_type(&self) -> Result<OptionType, Error> {
358        OptionType::try_from(self.0[OPTION_TYPE_OFFSET])
359    }
360
361    /// Total wire size of this option (length field value + `OPTION_LENGTH_SIZE_DELTA`).
362    #[must_use]
363    pub fn wire_size(&self) -> usize {
364        let length = u16::from_be_bytes([self.0[0], self.0[1]]);
365        usize::from(length) + OPTION_LENGTH_SIZE_DELTA
366    }
367
368    /// Fully validate this option's wire format (type, per-type length, and
369    /// transport-protocol byte for IP-bearing options).
370    ///
371    /// Used by the eager L2 validation walk in
372    /// [`SdHeaderView::parse`](super::SdHeaderView::parse) so that its
373    /// infallible option accessors can trust the buffer thereafter.
374    ///
375    /// # Errors
376    ///
377    /// Returns an error if the option type, length, or transport-protocol byte
378    /// is invalid.
379    pub(crate) fn validate(&self) -> Result<(), Error> {
380        validate_option(self.0).map(|_| ())
381    }
382
383    /// A view is only guaranteed to hold the 4-byte option header -- `decode`
384    /// is deliberately lazy about the type and per-type length -- so each
385    /// accessor that reads a body checks its own span before indexing.
386    fn ensure_body_len(&self, needed: usize) -> Result<(), Error> {
387        if self.0.len() < needed {
388            return Err(Error::IncorrectOptionsSize {
389                needed,
390                available: self.0.len(),
391            });
392        }
393        Ok(())
394    }
395
396    /// Parse as IPv4 endpoint/multicast/SD option.
397    /// Returns `(ip, protocol, port)`.
398    ///
399    /// # Errors
400    ///
401    /// Returns [`Error::InvalidOptionTransportProtocol`] if the protocol byte is unrecognized.
402    /// Views obtained via [`SdHeaderView::parse`](super::SdHeaderView::parse) have already
403    /// had their protocol byte validated, so this error cannot occur for those callers —
404    /// it is retained only to keep the API usable if an `OptionView` is ever constructed
405    /// outside the validated parse path.
406    pub fn as_ipv4(&self) -> Result<(Ipv4Addr, TransportProtocol, u16), Error> {
407        self.ensure_body_len(IPV4_OPTION_WIRE_SIZE)?;
408        let ip = Ipv4Addr::from_bits(u32::from_be_bytes([
409            self.0[IPV4_OPTION_IP_OFFSET],
410            self.0[IPV4_OPTION_IP_OFFSET + 1],
411            self.0[IPV4_OPTION_IP_OFFSET + 2],
412            self.0[IPV4_OPTION_IP_OFFSET + 3],
413        ]));
414        let protocol = TransportProtocol::try_from(self.0[IPV4_OPTION_PROTOCOL_OFFSET])?;
415        let port = u16::from_be_bytes([
416            self.0[IPV4_OPTION_PORT_OFFSET],
417            self.0[IPV4_OPTION_PORT_OFFSET + 1],
418        ]);
419        Ok((ip, protocol, port))
420    }
421
422    /// Parse as IPv6 endpoint/multicast/SD option.
423    /// Returns `(ip, protocol, port)`.
424    ///
425    /// # Errors
426    ///
427    /// Returns [`Error::InvalidOptionTransportProtocol`] if the protocol byte is unrecognized.
428    /// Views obtained via [`SdHeaderView::parse`](super::SdHeaderView::parse) have already
429    /// had their protocol byte validated, so this error cannot occur for those callers —
430    /// it is retained only to keep the API usable if an `OptionView` is ever constructed
431    /// outside the validated parse path.
432    pub fn as_ipv6(&self) -> Result<(Ipv6Addr, TransportProtocol, u16), Error> {
433        self.ensure_body_len(IPV6_OPTION_WIRE_SIZE)?;
434        let mut octets = [0u8; 16];
435        octets.copy_from_slice(&self.0[IPV6_OPTION_IP_OFFSET..IPV6_OPTION_IP_END]);
436        let ip = Ipv6Addr::from(octets);
437        let protocol = TransportProtocol::try_from(self.0[IPV6_OPTION_PROTOCOL_OFFSET])?;
438        let port = u16::from_be_bytes([
439            self.0[IPV6_OPTION_PORT_OFFSET],
440            self.0[IPV6_OPTION_PORT_OFFSET + 1],
441        ]);
442        Ok((ip, protocol, port))
443    }
444
445    /// Raw configuration bytes (for Configuration options).
446    #[must_use]
447    pub fn configuration_bytes(&self) -> &'a [u8] {
448        let length = u16::from_be_bytes([self.0[0], self.0[1]]);
449        let string_len = length.saturating_sub(CONFIGURATION_OPTION_LENGTH_STRING_DELTA);
450        &self.0[OPTION_PAYLOAD_OFFSET..OPTION_PAYLOAD_OFFSET + usize::from(string_len)]
451    }
452
453    /// Parse as load-balancing option. Returns `(priority, weight)`.
454    ///
455    /// # Errors
456    ///
457    /// Currently always succeeds; the `Result` return type is reserved for future validation.
458    pub fn as_load_balancing(&self) -> Result<(u16, u16), Error> {
459        self.ensure_body_len(LOAD_BALANCING_OPTION_WIRE_SIZE)?;
460        let priority = u16::from_be_bytes([
461            self.0[OPTION_PAYLOAD_OFFSET],
462            self.0[OPTION_PAYLOAD_OFFSET + 1],
463        ]);
464        let weight = u16::from_be_bytes([
465            self.0[OPTION_PAYLOAD_OFFSET + 2],
466            self.0[OPTION_PAYLOAD_OFFSET + 3],
467        ]);
468        Ok((priority, weight))
469    }
470
471    /// Converts this view into an owned [`Options`].
472    ///
473    /// # Errors
474    ///
475    /// Returns an error if the option type is unrecognized, the transport protocol byte
476    /// is invalid, or the configuration string exceeds [`MAX_CONFIGURATION_STRING_LENGTH`].
477    ///
478    /// # Panics
479    ///
480    /// Panics if a configuration string passes the length check but fails to fit into the
481    /// heapless buffer (unreachable in practice).
482    pub fn to_owned(&self) -> Result<Options, Error> {
483        let option_type = self.option_type()?;
484        match option_type {
485            OptionType::Configuration => {
486                let config_bytes = self.configuration_bytes();
487                if config_bytes.len() > MAX_CONFIGURATION_STRING_LENGTH {
488                    return Err(Error::ConfigurationStringTooLong(config_bytes.len()));
489                }
490                let mut configuration_string =
491                    heapless::Vec::<u8, MAX_CONFIGURATION_STRING_LENGTH>::new();
492                configuration_string
493                    .extend_from_slice(config_bytes)
494                    .expect("length validated above");
495                Ok(Options::Configuration {
496                    configuration_string,
497                })
498            }
499            OptionType::LoadBalancing => {
500                let (priority, weight) = self.as_load_balancing()?;
501                Ok(Options::LoadBalancing { priority, weight })
502            }
503            OptionType::IpV4Endpoint => {
504                let (ip, protocol, port) = self.as_ipv4()?;
505                Ok(Options::IpV4Endpoint { ip, protocol, port })
506            }
507            OptionType::IpV6Endpoint => {
508                let (ip, protocol, port) = self.as_ipv6()?;
509                Ok(Options::IpV6Endpoint { ip, protocol, port })
510            }
511            OptionType::IpV4Multicast => {
512                let (ip, protocol, port) = self.as_ipv4()?;
513                Ok(Options::IpV4Multicast { ip, protocol, port })
514            }
515            OptionType::IpV6Multicast => {
516                let (ip, protocol, port) = self.as_ipv6()?;
517                Ok(Options::IpV6Multicast { ip, protocol, port })
518            }
519            OptionType::IpV4SD => {
520                let (ip, protocol, port) = self.as_ipv4()?;
521                Ok(Options::IpV4SD { ip, protocol, port })
522            }
523            OptionType::IpV6SD => {
524                let (ip, protocol, port) = self.as_ipv6()?;
525                Ok(Options::IpV6SD { ip, protocol, port })
526            }
527        }
528    }
529}
530
531impl<'a> Decode<'a> for OptionView<'a> {
532    type Error = crate::protocol::Error;
533
534    /// Decode a single variable-length SD option from the front of `buf`.
535    ///
536    /// The stride comes from the option's 2-byte length field. This slices
537    /// only; it does NOT validate the option type, per-type length, or
538    /// transport-protocol byte. Validation is deferred to the accessors
539    /// (`option_type` / `as_ipv4` / `to_owned`) — the L2 validation pass —
540    /// keeping this a lazy zero-copy view.
541    ///
542    /// # Errors
543    ///
544    /// Returns [`Incomplete`](automotive_wire_codec::Incomplete) if fewer than
545    /// the fixed option header remains, or if the declared wire size exceeds
546    /// the remaining bytes, and
547    /// [`IncorrectOptionsSize`](Error::IncorrectOptionsSize) if the declared
548    /// wire size is smaller than the option header itself.
549    fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Self::Error> {
550        ensure_len(buf, OPTION_HEADER_SIZE)?;
551        let length = u16::from_be_bytes([buf[0], buf[1]]);
552        let wire_size = usize::from(length) + OPTION_LENGTH_SIZE_DELTA;
553        // `wire_size` is `length + 3`, so a declared `length` below 1 produces
554        // a view shorter than the 4-byte header this function just required --
555        // a view that contradicts its own header. `configuration_bytes` reads
556        // from `OPTION_PAYLOAD_OFFSET` unconditionally and would index past the
557        // end of such a view. Reject it here so no accessor can see one.
558        if wire_size < OPTION_HEADER_SIZE {
559            return Err(Error::IncorrectOptionsSize {
560                needed: OPTION_HEADER_SIZE,
561                available: wire_size,
562            }
563            .into());
564        }
565        let (head, rest) = take(buf, wire_size)?;
566        Ok((OptionView(head), rest))
567    }
568}
569
570impl<'a> DecodeIter<'a> for OptionView<'a> {
571    type Error = crate::protocol::Error;
572
573    // Variable stride (from the length field): keep the default WIRE_SIZE = None.
574
575    /// Decode the next option, or `Ok(None)` at a clean end of buffer.
576    ///
577    /// A partial/truncated trailing option after a good start is surfaced as an
578    /// `Err` rather than silently dropped.
579    ///
580    /// # Errors
581    ///
582    /// Returns [`Incomplete`](automotive_wire_codec::Incomplete) if a partial
583    /// option remains after a good start.
584    fn decode_next(buf: &'a [u8]) -> Result<Option<(Self, &'a [u8])>, Self::Error> {
585        if buf.is_empty() {
586            return Ok(None);
587        }
588        Self::decode(buf).map(Some)
589    }
590}
591
592/// Iterator over variable-length SD options in a validated buffer.
593/// Options are guaranteed valid (validated upfront in `SdHeaderView::parse`).
594///
595/// `OptionIter` is a thin wrapper around a borrowed byte slice and is
596/// `Clone`, so callers that need to walk the same options multiple
597/// times (e.g. to extract the subset referenced by a particular entry's
598/// options run) can explicitly clone the iterator. It is deliberately
599/// **not** `Copy` — making an iterator `Copy` is a footgun because
600/// advancing the original does not advance the hidden copies, which
601/// makes "this iterator is already exhausted" invariants easy to break
602/// accidentally. Clone when you mean to reuse; don't let the compiler
603/// duplicate for you.
604#[derive(Clone)]
605pub struct OptionIter<'a> {
606    remaining: &'a [u8],
607}
608
609impl<'a> OptionIter<'a> {
610    pub(crate) fn new(buf: &'a [u8]) -> Self {
611        Self { remaining: buf }
612    }
613}
614
615impl<'a> Iterator for OptionIter<'a> {
616    type Item = OptionView<'a>;
617
618    fn next(&mut self) -> Option<Self::Item> {
619        if self.remaining.len() < OPTION_HEADER_SIZE {
620            return None;
621        }
622        let length = u16::from_be_bytes([self.remaining[0], self.remaining[1]]);
623        let wire_size = usize::from(length) + OPTION_LENGTH_SIZE_DELTA;
624        if wire_size > self.remaining.len() {
625            return None;
626        }
627        let view = OptionView(&self.remaining[..wire_size]);
628        self.remaining = &self.remaining[wire_size..];
629        Some(view)
630    }
631}
632
633/// Validate a single option's wire format and return its wire size.
634/// Used during `SdHeaderView::parse` for upfront validation.
635///
636/// In addition to length/type checks, this validates the transport protocol
637/// byte of IP-bearing options so that `OptionView::as_ipv4` / `as_ipv6` on
638/// views obtained through `SdHeaderView::parse` cannot observe an unknown
639/// protocol byte.
640pub(crate) fn validate_option(buf: &[u8]) -> Result<usize, Error> {
641    if buf.len() < OPTION_HEADER_SIZE {
642        return Err(Error::IncorrectOptionsSize {
643            needed: OPTION_HEADER_SIZE,
644            available: buf.len(),
645        });
646    }
647    let length = u16::from_be_bytes([buf[0], buf[1]]);
648    let wire_size = usize::from(length) + OPTION_LENGTH_SIZE_DELTA;
649    if wire_size > buf.len() {
650        return Err(Error::IncorrectOptionsSize {
651            needed: wire_size,
652            available: buf.len(),
653        });
654    }
655    let option_type_byte = buf[OPTION_TYPE_OFFSET];
656    let option_type = OptionType::try_from(option_type_byte)?;
657    // Validate expected lengths for fixed-size options
658    match option_type {
659        OptionType::IpV4Endpoint | OptionType::IpV4Multicast | OptionType::IpV4SD => {
660            if length != IPV4_OPTION_LENGTH_FIELD {
661                return Err(Error::InvalidOptionLength {
662                    option_type: option_type_byte,
663                    expected: IPV4_OPTION_LENGTH_FIELD,
664                    actual: length,
665                });
666            }
667            TransportProtocol::try_from(buf[IPV4_OPTION_PROTOCOL_OFFSET])?;
668        }
669        OptionType::IpV6Endpoint | OptionType::IpV6Multicast | OptionType::IpV6SD => {
670            if length != IPV6_OPTION_LENGTH_FIELD {
671                return Err(Error::InvalidOptionLength {
672                    option_type: option_type_byte,
673                    expected: IPV6_OPTION_LENGTH_FIELD,
674                    actual: length,
675                });
676            }
677            TransportProtocol::try_from(buf[IPV6_OPTION_PROTOCOL_OFFSET])?;
678        }
679        OptionType::LoadBalancing => {
680            if length != LOAD_BALANCING_OPTION_LENGTH_FIELD {
681                return Err(Error::InvalidOptionLength {
682                    option_type: option_type_byte,
683                    expected: LOAD_BALANCING_OPTION_LENGTH_FIELD,
684                    actual: length,
685                });
686            }
687        }
688        OptionType::Configuration => {
689            // Configuration strings are variable length; just check it doesn't exceed max
690            let string_len = length.saturating_sub(CONFIGURATION_OPTION_LENGTH_STRING_DELTA);
691            if usize::from(string_len) > MAX_CONFIGURATION_STRING_LENGTH {
692                return Err(Error::ConfigurationStringTooLong(string_len.into()));
693            }
694        }
695    }
696    Ok(wire_size)
697}
698
699#[cfg(test)]
700mod tests {
701    use core::net::{Ipv4Addr, Ipv6Addr};
702
703    use super::*;
704
705    // --- Under-length option views (PR #153 review, blocking finding) ---
706    //
707    // `decode` required OPTION_HEADER_SIZE (4) but then took
708    // `length + OPTION_LENGTH_SIZE_DELTA` (3), so a small declared `length`
709    // produced a view shorter than the header `decode` had just insisted on,
710    // and the accessors indexed it unconditionally.
711
712    /// `length = 0` yields a 3-byte view — shorter than the 4-byte header
713    /// `decode` just required. `configuration_bytes` then indexed past its end.
714    #[test]
715    fn decode_rejects_a_wire_size_below_the_option_header() {
716        let buf = [0x00, 0x00, 0x01, 0x00];
717        assert!(
718            matches!(
719                OptionView::decode(&buf),
720                Err(crate::protocol::Error::Sd(
721                    Error::IncorrectOptionsSize { .. }
722                ))
723            ),
724            "a 3-byte view cannot satisfy the 4-byte option header",
725        );
726    }
727
728    /// The review's repro: `length = 2` gives a 5-byte view typed as IPv4
729    /// Endpoint (0x04), which needs 12.
730    #[test]
731    fn as_ipv4_rejects_a_view_too_short_for_an_ipv4_option() {
732        let buf = [0x00, 0x02, 0x04, 0x00, 0x00];
733        let (view, _) = OptionView::decode(&buf).expect("header-sized view decodes");
734        assert!(matches!(
735            view.as_ipv4(),
736            Err(Error::IncorrectOptionsSize { .. })
737        ));
738    }
739
740    /// Same shape reached through the documented lazy path the review cites.
741    #[test]
742    fn to_owned_rejects_a_short_ipv4_option_instead_of_panicking() {
743        let buf = [0x00, 0x02, 0x04, 0x00, 0x00];
744        let (view, _) = OptionView::decode(&buf).expect("header-sized view decodes");
745        assert!(view.to_owned().is_err());
746    }
747
748    #[test]
749    fn as_ipv6_rejects_a_view_too_short_for_an_ipv6_option() {
750        let buf = [0x00, 0x02, 0x06, 0x00, 0x00];
751        let (view, _) = OptionView::decode(&buf).expect("header-sized view decodes");
752        assert!(matches!(
753            view.as_ipv6(),
754            Err(Error::IncorrectOptionsSize { .. })
755        ));
756    }
757
758    #[test]
759    fn as_load_balancing_rejects_a_view_too_short_for_the_option() {
760        let buf = [0x00, 0x02, 0x02, 0x00, 0x00];
761        let (view, _) = OptionView::decode(&buf).expect("header-sized view decodes");
762        assert!(matches!(
763            view.as_load_balancing(),
764            Err(Error::IncorrectOptionsSize { .. })
765        ));
766    }
767
768    /// A well-formed option must keep decoding — the guards must not reject
769    /// valid input.
770    #[test]
771    fn a_well_formed_ipv4_option_still_decodes() {
772        let mut buf = [0u8; IPV4_OPTION_WIRE_SIZE];
773        buf[0..2].copy_from_slice(&IPV4_OPTION_LENGTH_FIELD.to_be_bytes());
774        buf[OPTION_TYPE_OFFSET] = 0x04;
775        buf[IPV4_OPTION_PROTOCOL_OFFSET] = 0x11;
776        let (view, rest) = OptionView::decode(&buf).expect("valid option decodes");
777        assert!(rest.is_empty());
778        assert!(view.as_ipv4().is_ok());
779    }
780
781    // --- TransportProtocol ---
782
783    #[test]
784    fn transport_protocol_tcp_round_trip() {
785        assert_eq!(
786            TransportProtocol::try_from(0x06).unwrap(),
787            TransportProtocol::Tcp
788        );
789        assert_eq!(u8::try_from(TransportProtocol::Tcp).unwrap(), 0x06);
790    }
791
792    #[test]
793    fn transport_protocol_invalid_returns_error() {
794        assert!(matches!(
795            TransportProtocol::try_from(0xFF),
796            Err(Error::InvalidOptionTransportProtocol(0xFF))
797        ));
798    }
799
800    // --- OptionView: parse from encoded bytes ---
801
802    #[test]
803    fn option_view_ipv4_endpoint_tcp() {
804        let buf: [u8; 12] = [
805            0x00, 0x09, // length = 9
806            0x04, // type = IpV4Endpoint
807            0x00, // discard flag
808            192, 168, 0, 1,    // ip
809            0x00, // reserved
810            0x06, // protocol = TCP
811            0x04, 0xD2, // port = 1234
812        ];
813        let view = OptionView(&buf);
814        assert_eq!(view.option_type().unwrap(), OptionType::IpV4Endpoint);
815        assert_eq!(view.wire_size(), 12);
816        let (ip, protocol, port) = view.as_ipv4().unwrap();
817        assert_eq!(ip, Ipv4Addr::new(192, 168, 0, 1));
818        assert_eq!(protocol, TransportProtocol::Tcp);
819        assert_eq!(port, 1234);
820    }
821
822    #[test]
823    fn option_view_to_owned_invalid_type() {
824        let buf: [u8; 4] = [0x00, 0x00, 0xFF, 0x00]; // type = 0xFF (invalid)
825        let view = OptionView(&buf);
826        assert!(matches!(
827            view.to_owned(),
828            Err(Error::InvalidOptionType(0xFF))
829        ));
830    }
831
832    // --- Round-trip tests for all option types ---
833
834    fn round_trip(option: &Options) {
835        let size = option.size();
836        let mut buf = [0u8; 4 + MAX_CONFIGURATION_STRING_LENGTH];
837        let written = option.encode(&mut &mut buf[..size]).unwrap();
838        assert_eq!(written, size);
839        let view = OptionView(&buf[..size]);
840        let parsed = view.to_owned().unwrap();
841        assert_eq!(*option, parsed);
842    }
843
844    #[test]
845    fn configuration_round_trip() {
846        let mut config_string = heapless::Vec::<u8, MAX_CONFIGURATION_STRING_LENGTH>::new();
847        config_string.extend_from_slice(b"test=value").unwrap();
848        let option = Options::Configuration {
849            configuration_string: config_string,
850        };
851        round_trip(&option);
852    }
853
854    #[test]
855    fn configuration_empty_round_trip() {
856        let option = Options::Configuration {
857            configuration_string: heapless::Vec::new(),
858        };
859        round_trip(&option);
860    }
861
862    #[test]
863    fn load_balancing_round_trip() {
864        let option = Options::LoadBalancing {
865            priority: 100,
866            weight: 200,
867        };
868        round_trip(&option);
869    }
870
871    #[test]
872    fn ipv4_endpoint_round_trip() {
873        let option = Options::IpV4Endpoint {
874            ip: Ipv4Addr::new(10, 0, 0, 1),
875            protocol: TransportProtocol::Udp,
876            port: 30490,
877        };
878        round_trip(&option);
879    }
880
881    #[test]
882    fn ipv6_endpoint_round_trip() {
883        let option = Options::IpV6Endpoint {
884            ip: Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
885            protocol: TransportProtocol::Tcp,
886            port: 8080,
887        };
888        round_trip(&option);
889    }
890
891    #[test]
892    fn ipv4_multicast_round_trip() {
893        let option = Options::IpV4Multicast {
894            ip: Ipv4Addr::new(239, 0, 0, 1),
895            protocol: TransportProtocol::Udp,
896            port: 30490,
897        };
898        round_trip(&option);
899    }
900
901    #[test]
902    fn ipv6_multicast_round_trip() {
903        let option = Options::IpV6Multicast {
904            ip: Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
905            protocol: TransportProtocol::Udp,
906            port: 30490,
907        };
908        round_trip(&option);
909    }
910
911    #[test]
912    fn ipv4_sd_round_trip() {
913        let option = Options::IpV4SD {
914            ip: Ipv4Addr::new(172, 16, 0, 1),
915            protocol: TransportProtocol::Udp,
916            port: 30490,
917        };
918        round_trip(&option);
919    }
920
921    #[test]
922    fn ipv6_sd_round_trip() {
923        let option = Options::IpV6SD {
924            ip: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
925            protocol: TransportProtocol::Tcp,
926            port: 9999,
927        };
928        round_trip(&option);
929    }
930
931    // --- Error cases ---
932
933    #[test]
934    fn load_balancing_invalid_length_returns_error() {
935        // length = 3 (wrong, should be 5), wire_size = 6
936        let mut buf = [0u8; 6];
937        buf[0] = 0x00;
938        buf[1] = 0x03; // length = 3
939        buf[2] = 0x02; // type = LoadBalancing
940        buf[3] = 0x00; // discard flag
941        assert!(matches!(
942            validate_option(&buf),
943            Err(Error::InvalidOptionLength {
944                option_type: 0x02,
945                expected: 5,
946                actual: 3,
947            })
948        ));
949    }
950
951    #[test]
952    fn ipv4_endpoint_invalid_length_returns_error() {
953        // length = 5 (wrong, should be 9), wire_size = 8
954        let mut buf = [0u8; 8];
955        buf[0] = 0x00;
956        buf[1] = 0x05; // length = 5
957        buf[2] = 0x04; // type = IpV4Endpoint
958        buf[3] = 0x00;
959        assert!(matches!(
960            validate_option(&buf),
961            Err(Error::InvalidOptionLength {
962                option_type: 0x04,
963                expected: 9,
964                actual: 5,
965            })
966        ));
967    }
968
969    #[test]
970    fn ipv6_endpoint_invalid_length_returns_error() {
971        // length = 9 (wrong, should be 21), wire_size = 12
972        let mut buf = [0u8; 12];
973        buf[0] = 0x00;
974        buf[1] = 0x09; // length = 9
975        buf[2] = 0x06; // type = IpV6Endpoint
976        buf[3] = 0x00;
977        assert!(matches!(
978            validate_option(&buf),
979            Err(Error::InvalidOptionLength {
980                option_type: 0x06,
981                expected: 21,
982                actual: 9,
983            })
984        ));
985    }
986
987    #[test]
988    fn ipv4_multicast_invalid_length_returns_error() {
989        // length = 5 (wrong, should be 9), wire_size = 8
990        let mut buf = [0u8; 8];
991        buf[0] = 0x00;
992        buf[1] = 0x05;
993        buf[2] = 0x14; // type = IpV4Multicast
994        buf[3] = 0x00;
995        assert!(matches!(
996            validate_option(&buf),
997            Err(Error::InvalidOptionLength {
998                option_type: 0x14,
999                expected: 9,
1000                actual: 5,
1001            })
1002        ));
1003    }
1004
1005    #[test]
1006    fn ipv6_multicast_invalid_length_returns_error() {
1007        // length = 9 (wrong, should be 21), wire_size = 12
1008        let mut buf = [0u8; 12];
1009        buf[0] = 0x00;
1010        buf[1] = 0x09;
1011        buf[2] = 0x16; // type = IpV6Multicast
1012        buf[3] = 0x00;
1013        assert!(matches!(
1014            validate_option(&buf),
1015            Err(Error::InvalidOptionLength {
1016                option_type: 0x16,
1017                expected: 21,
1018                actual: 9,
1019            })
1020        ));
1021    }
1022
1023    #[test]
1024    fn ipv4_sd_invalid_length_returns_error() {
1025        // length = 5 (wrong, should be 9), wire_size = 8
1026        let mut buf = [0u8; 8];
1027        buf[0] = 0x00;
1028        buf[1] = 0x05;
1029        buf[2] = 0x24; // type = IpV4SD
1030        buf[3] = 0x00;
1031        assert!(matches!(
1032            validate_option(&buf),
1033            Err(Error::InvalidOptionLength {
1034                option_type: 0x24,
1035                expected: 9,
1036                actual: 5,
1037            })
1038        ));
1039    }
1040
1041    /// Build a well-formed IPv4 option wire buffer (length + type correct) with a
1042    /// caller-chosen transport protocol byte — used to exercise protocol-byte
1043    /// validation without hand-rolling wire offsets.
1044    fn ipv4_option_with_protocol(
1045        option_type: OptionType,
1046        protocol_byte: u8,
1047    ) -> [u8; IPV4_OPTION_WIRE_SIZE] {
1048        let mut buf = [0u8; IPV4_OPTION_WIRE_SIZE];
1049        buf[0..2].copy_from_slice(&IPV4_OPTION_LENGTH_FIELD.to_be_bytes());
1050        buf[OPTION_TYPE_OFFSET] = u8::from(option_type);
1051        buf[IPV4_OPTION_PROTOCOL_OFFSET] = protocol_byte;
1052        buf
1053    }
1054
1055    /// Build a well-formed IPv6 option wire buffer (length + type correct) with a
1056    /// caller-chosen transport protocol byte.
1057    fn ipv6_option_with_protocol(
1058        option_type: OptionType,
1059        protocol_byte: u8,
1060    ) -> [u8; IPV6_OPTION_WIRE_SIZE] {
1061        let mut buf = [0u8; IPV6_OPTION_WIRE_SIZE];
1062        buf[0..2].copy_from_slice(&IPV6_OPTION_LENGTH_FIELD.to_be_bytes());
1063        buf[OPTION_TYPE_OFFSET] = u8::from(option_type);
1064        buf[IPV6_OPTION_PROTOCOL_OFFSET] = protocol_byte;
1065        buf
1066    }
1067
1068    #[test]
1069    fn ipv4_endpoint_invalid_transport_protocol_returns_error() {
1070        let buf = ipv4_option_with_protocol(OptionType::IpV4Endpoint, 0xAB);
1071        assert!(matches!(
1072            validate_option(&buf),
1073            Err(Error::InvalidOptionTransportProtocol(0xAB))
1074        ));
1075    }
1076
1077    #[test]
1078    fn ipv4_multicast_invalid_transport_protocol_returns_error() {
1079        let buf = ipv4_option_with_protocol(OptionType::IpV4Multicast, 0x42);
1080        assert!(matches!(
1081            validate_option(&buf),
1082            Err(Error::InvalidOptionTransportProtocol(0x42))
1083        ));
1084    }
1085
1086    #[test]
1087    fn ipv4_sd_invalid_transport_protocol_returns_error() {
1088        let buf = ipv4_option_with_protocol(OptionType::IpV4SD, 0x01);
1089        assert!(matches!(
1090            validate_option(&buf),
1091            Err(Error::InvalidOptionTransportProtocol(0x01))
1092        ));
1093    }
1094
1095    #[test]
1096    fn ipv6_endpoint_invalid_transport_protocol_returns_error() {
1097        let buf = ipv6_option_with_protocol(OptionType::IpV6Endpoint, 0x99);
1098        assert!(matches!(
1099            validate_option(&buf),
1100            Err(Error::InvalidOptionTransportProtocol(0x99))
1101        ));
1102    }
1103
1104    #[test]
1105    fn ipv6_multicast_invalid_transport_protocol_returns_error() {
1106        let buf = ipv6_option_with_protocol(OptionType::IpV6Multicast, 0x00);
1107        assert!(matches!(
1108            validate_option(&buf),
1109            Err(Error::InvalidOptionTransportProtocol(0x00))
1110        ));
1111    }
1112
1113    #[test]
1114    fn ipv6_sd_invalid_transport_protocol_returns_error() {
1115        let buf = ipv6_option_with_protocol(OptionType::IpV6SD, 0xFE);
1116        assert!(matches!(
1117            validate_option(&buf),
1118            Err(Error::InvalidOptionTransportProtocol(0xFE))
1119        ));
1120    }
1121
1122    #[test]
1123    fn ipv6_sd_invalid_length_returns_error() {
1124        // length = 9 (wrong, should be 21), wire_size = 12
1125        let mut buf = [0u8; 12];
1126        buf[0] = 0x00;
1127        buf[1] = 0x09;
1128        buf[2] = 0x26; // type = IpV6SD
1129        buf[3] = 0x00;
1130        assert!(matches!(
1131            validate_option(&buf),
1132            Err(Error::InvalidOptionLength {
1133                option_type: 0x26,
1134                expected: 21,
1135                actual: 9,
1136            })
1137        ));
1138    }
1139
1140    // --- OptionIter ---
1141
1142    #[test]
1143    fn option_iter_empty() {
1144        let iter = OptionIter::new(&[]);
1145        assert_eq!(iter.count(), 0);
1146    }
1147
1148    #[test]
1149    fn option_iter_two_options() {
1150        let opt1 = Options::IpV4Endpoint {
1151            ip: Ipv4Addr::new(10, 0, 0, 1),
1152            protocol: TransportProtocol::Udp,
1153            port: 30490,
1154        };
1155        let opt2 = Options::LoadBalancing {
1156            priority: 100,
1157            weight: 200,
1158        };
1159        let mut buf = [0u8; 24]; // 12 + 8 = 20
1160        let n1 = opt1.encode(&mut &mut buf[..12]).unwrap();
1161        let n2 = opt2.encode(&mut &mut buf[12..20]).unwrap();
1162        let total = n1 + n2;
1163
1164        let mut iter = OptionIter::new(&buf[..total]);
1165        let v1 = iter.next().unwrap();
1166        assert_eq!(v1.to_owned().unwrap(), opt1);
1167        let v2 = iter.next().unwrap();
1168        assert_eq!(v2.to_owned().unwrap(), opt2);
1169        assert!(iter.next().is_none());
1170    }
1171
1172    #[test]
1173    fn option_iter_clone_allows_reuse() {
1174        // Cloning should snapshot the iterator state — advancing the
1175        // original must not affect the clone, and the clone must be
1176        // able to walk the full sequence independently.
1177        let opt1 = Options::IpV4Endpoint {
1178            ip: Ipv4Addr::new(10, 0, 0, 1),
1179            protocol: TransportProtocol::Udp,
1180            port: 30490,
1181        };
1182        let opt2 = Options::IpV4Endpoint {
1183            ip: Ipv4Addr::new(10, 0, 0, 2),
1184            protocol: TransportProtocol::Udp,
1185            port: 30491,
1186        };
1187        let mut buf = [0u8; 24];
1188        let n1 = opt1.encode(&mut &mut buf[..12]).unwrap();
1189        let n2 = opt2.encode(&mut &mut buf[12..24]).unwrap();
1190        let total = n1 + n2;
1191
1192        let iter = OptionIter::new(&buf[..total]);
1193        let clone = iter.clone();
1194
1195        // Walk the original: it should produce opt1 then opt2.
1196        let mut walker = iter;
1197        let a = walker.next().unwrap().to_owned().unwrap();
1198        let b = walker.next().unwrap().to_owned().unwrap();
1199        assert!(walker.next().is_none());
1200        assert_eq!(a, opt1);
1201        assert_eq!(b, opt2);
1202
1203        // The clone is untouched by the original's advance — it still
1204        // starts from the beginning and yields both options.
1205        let mut walker2 = clone;
1206        let a2 = walker2.next().unwrap().to_owned().unwrap();
1207        let b2 = walker2.next().unwrap().to_owned().unwrap();
1208        assert!(walker2.next().is_none());
1209        assert_eq!(a2, opt1);
1210        assert_eq!(b2, opt2);
1211    }
1212
1213    #[test]
1214    fn option_iter_clone_mid_walk_preserves_position() {
1215        // After partially walking the original iterator, cloning it
1216        // should yield a new iterator that starts from the current
1217        // position of the original — not from the beginning.
1218        let opt1 = Options::IpV4Endpoint {
1219            ip: Ipv4Addr::new(10, 0, 0, 1),
1220            protocol: TransportProtocol::Udp,
1221            port: 30490,
1222        };
1223        let opt2 = Options::IpV4Endpoint {
1224            ip: Ipv4Addr::new(10, 0, 0, 2),
1225            protocol: TransportProtocol::Udp,
1226            port: 30491,
1227        };
1228        let mut buf = [0u8; 24];
1229        let n1 = opt1.encode(&mut &mut buf[..12]).unwrap();
1230        let n2 = opt2.encode(&mut &mut buf[12..24]).unwrap();
1231        let total = n1 + n2;
1232
1233        let mut iter = OptionIter::new(&buf[..total]);
1234        // Advance past opt1.
1235        let _ = iter.next().unwrap();
1236
1237        // Clone from this mid-walk position; the clone should yield
1238        // only opt2 (and then end).
1239        let mut clone = iter.clone();
1240        let remaining = clone.next().unwrap().to_owned().unwrap();
1241        assert!(clone.next().is_none());
1242        assert_eq!(remaining, opt2);
1243    }
1244
1245    // --- Decode / DecodeIter (Phase 3 lazy L1) ---
1246
1247    fn two_option_buf() -> ([u8; 24], usize, Options, Options) {
1248        let opt1 = Options::IpV4Endpoint {
1249            ip: Ipv4Addr::new(10, 0, 0, 1),
1250            protocol: TransportProtocol::Udp,
1251            port: 30490,
1252        };
1253        let opt2 = Options::LoadBalancing {
1254            priority: 100,
1255            weight: 200,
1256        };
1257        let mut buf = [0u8; 24];
1258        let n1 = opt1.encode(&mut &mut buf[..12]).unwrap();
1259        let n2 = opt2.encode(&mut &mut buf[12..20]).unwrap();
1260        (buf, n1 + n2, opt1, opt2)
1261    }
1262
1263    #[test]
1264    fn decode_yields_option_and_remainder() {
1265        let (buf, total, opt1, opt2) = two_option_buf();
1266        let (view, rest) = OptionView::decode(&buf[..total]).unwrap();
1267        assert_eq!(view.to_owned().unwrap(), opt1);
1268        assert_eq!(rest.len(), 8);
1269        let (view2, rest2) = OptionView::decode(rest).unwrap();
1270        assert_eq!(view2.to_owned().unwrap(), opt2);
1271        assert!(rest2.is_empty());
1272    }
1273
1274    #[test]
1275    fn decode_short_header_is_incomplete() {
1276        assert!(matches!(
1277            OptionView::decode(&[0x00, 0x09, 0x04]),
1278            Err(crate::protocol::Error::Incomplete(
1279                automotive_wire_codec::Incomplete {
1280                    needed: 4,
1281                    available: 3,
1282                }
1283            ))
1284        ));
1285    }
1286
1287    #[test]
1288    fn decode_truncated_body_is_incomplete() {
1289        let (buf, _total, _opt1, _opt2) = two_option_buf();
1290        // A well-formed 12-byte IPv4 option header declaring 12 bytes, but
1291        // only 8 present.
1292        assert!(matches!(
1293            OptionView::decode(&buf[..8]),
1294            Err(crate::protocol::Error::Incomplete(
1295                automotive_wire_codec::Incomplete {
1296                    needed: 12,
1297                    available: 8,
1298                }
1299            ))
1300        ));
1301    }
1302
1303    #[test]
1304    fn decode_iter_yields_all_then_none() {
1305        let (buf, total, opt1, opt2) = two_option_buf();
1306        let mut iter = OptionView::iter(&buf[..total]);
1307        assert_eq!(iter.next().unwrap().unwrap().to_owned().unwrap(), opt1);
1308        assert_eq!(iter.next().unwrap().unwrap().to_owned().unwrap(), opt2);
1309        assert!(iter.next().is_none());
1310    }
1311
1312    #[test]
1313    fn decode_iter_surfaces_truncated_tail_as_err() {
1314        let (buf, total, _opt1, _opt2) = two_option_buf();
1315        // First option (12 bytes) is complete; chop the second short.
1316        let mut iter = OptionView::iter(&buf[..total - 2]);
1317        assert!(matches!(iter.next(), Some(Ok(_))));
1318        assert!(matches!(
1319            iter.next(),
1320            Some(Err(crate::protocol::Error::Incomplete(_)))
1321        ));
1322        assert!(iter.next().is_none());
1323    }
1324
1325    #[test]
1326    fn decode_iter_empty_is_immediately_none() {
1327        let mut iter = OptionView::iter(&[]);
1328        assert!(iter.next().is_none());
1329    }
1330
1331    #[test]
1332    fn decode_iter_variable_width_has_no_remaining_len() {
1333        let (buf, total, _opt1, _opt2) = two_option_buf();
1334        let iter = OptionView::iter(&buf[..total]);
1335        assert_eq!(iter.remaining_len(), None);
1336    }
1337
1338    #[test]
1339    fn decode_does_not_validate_option_type() {
1340        // Option type byte 0xFF is invalid, but decode only slices by length.
1341        let buf: [u8; 4] = [0x00, 0x01, 0xFF, 0x00]; // length = 1, wire_size = 4
1342        let (view, rest) = OptionView::decode(&buf).unwrap();
1343        assert!(rest.is_empty());
1344        assert!(matches!(
1345            view.option_type(),
1346            Err(Error::InvalidOptionType(0xFF))
1347        ));
1348    }
1349
1350    // --- Encode size-exactness invariant ---
1351
1352    #[test]
1353    fn encoded_size_matches_bytes_written_for_each_variant() {
1354        use automotive_wire_codec::CountingSink;
1355        let mut config_string = heapless::Vec::<u8, MAX_CONFIGURATION_STRING_LENGTH>::new();
1356        config_string.extend_from_slice(b"k=v").unwrap();
1357        let options = [
1358            Options::Configuration {
1359                configuration_string: config_string,
1360            },
1361            Options::LoadBalancing {
1362                priority: 1,
1363                weight: 2,
1364            },
1365            Options::IpV4Endpoint {
1366                ip: Ipv4Addr::new(10, 0, 0, 1),
1367                protocol: TransportProtocol::Udp,
1368                port: 30490,
1369            },
1370            Options::IpV6Endpoint {
1371                ip: Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
1372                protocol: TransportProtocol::Tcp,
1373                port: 8080,
1374            },
1375        ];
1376        for option in &options {
1377            let mut sink = CountingSink::new();
1378            let written = option.encode(&mut sink).unwrap();
1379            assert_eq!(written, option.encoded_size().unwrap());
1380            assert_eq!(written, sink.count());
1381        }
1382    }
1383
1384    #[test]
1385    fn encode_to_slice_too_small_yields_insufficient_buffer() {
1386        use automotive_wire_codec::{EncodeToSliceError, InsufficientBuffer};
1387        let option = Options::IpV4Endpoint {
1388            ip: Ipv4Addr::new(10, 0, 0, 1),
1389            protocol: TransportProtocol::Udp,
1390            port: 30490,
1391        };
1392        let mut buf = [0u8; 4]; // needs 12
1393        let err = option.encode_to_slice(&mut buf).unwrap_err();
1394        assert!(matches!(
1395            err,
1396            EncodeToSliceError::InsufficientBuffer(InsufficientBuffer {
1397                needed: 12,
1398                available: 4,
1399            })
1400        ));
1401    }
1402}