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
7pub const MAX_CONFIGURATION_STRING_LENGTH: usize = 256;
9
10pub(crate) const OPTION_HEADER_SIZE: usize = 4;
20pub(crate) const OPTION_LENGTH_SIZE_DELTA: usize = 3;
22const OPTION_TYPE_OFFSET: usize = 2;
24const OPTION_PAYLOAD_OFFSET: usize = 4;
26
27pub(crate) const IPV4_OPTION_WIRE_SIZE: usize = 12;
30pub(crate) const IPV4_OPTION_LENGTH_FIELD: u16 = 9;
32pub(crate) const IPV4_OPTION_IP_OFFSET: usize = OPTION_PAYLOAD_OFFSET;
34pub(crate) const IPV4_OPTION_PROTOCOL_OFFSET: usize = 9;
36pub(crate) const IPV4_OPTION_PORT_OFFSET: usize = 10;
38
39pub(crate) const IPV6_OPTION_WIRE_SIZE: usize = 24;
42pub(crate) const IPV6_OPTION_LENGTH_FIELD: u16 = 21;
44const IPV6_OPTION_IP_OFFSET: usize = OPTION_PAYLOAD_OFFSET;
46const IPV6_OPTION_IP_END: usize = IPV6_OPTION_IP_OFFSET + 16;
48pub(crate) const IPV6_OPTION_PROTOCOL_OFFSET: usize = 21;
50const IPV6_OPTION_PORT_OFFSET: usize = 22;
52
53const LOAD_BALANCING_OPTION_WIRE_SIZE: usize = 8;
56pub(crate) const LOAD_BALANCING_OPTION_LENGTH_FIELD: u16 = 5;
58
59const 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
89pub enum OptionType {
90 Configuration,
92 LoadBalancing,
94 IpV4Endpoint,
96 IpV6Endpoint,
98 IpV4Multicast,
100 IpV6Multicast,
102 IpV4SD,
104 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#[allow(clippy::large_enum_variant)]
142#[derive(Clone, Debug, Eq, PartialEq)]
144pub enum Options {
145 Configuration {
147 configuration_string: heapless::Vec<u8, MAX_CONFIGURATION_STRING_LENGTH>,
149 },
150 LoadBalancing {
152 priority: u16,
154 weight: u16,
156 },
157 IpV4Endpoint {
159 ip: Ipv4Addr,
161 protocol: TransportProtocol,
163 port: u16,
165 },
166 IpV6Endpoint {
168 ip: Ipv6Addr,
170 protocol: TransportProtocol,
172 port: u16,
174 },
175 IpV4Multicast {
177 ip: Ipv4Addr,
179 protocol: TransportProtocol,
181 port: u16,
183 },
184 IpV6Multicast {
186 ip: Ipv6Addr,
188 protocol: TransportProtocol,
190 port: u16,
192 },
193 IpV4SD {
195 ip: Ipv4Addr,
197 protocol: TransportProtocol,
199 port: u16,
201 },
202 IpV6SD {
204 ip: Ipv6Addr,
206 protocol: TransportProtocol,
208 port: u16,
210 },
211}
212
213impl Options {
214 #[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 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#[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#[derive(Clone, Copy, Debug)]
349pub struct OptionView<'a>(&'a [u8]);
350
351impl<'a> OptionView<'a> {
352 pub fn option_type(&self) -> Result<OptionType, Error> {
358 OptionType::try_from(self.0[OPTION_TYPE_OFFSET])
359 }
360
361 #[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 pub(crate) fn validate(&self) -> Result<(), Error> {
380 validate_option(self.0).map(|_| ())
381 }
382
383 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 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 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 #[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 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 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 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 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 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#[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
633pub(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 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 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 #[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 #[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 #[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 #[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 #[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 #[test]
803 fn option_view_ipv4_endpoint_tcp() {
804 let buf: [u8; 12] = [
805 0x00, 0x09, 0x04, 0x00, 192, 168, 0, 1, 0x00, 0x06, 0x04, 0xD2, ];
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]; let view = OptionView(&buf);
826 assert!(matches!(
827 view.to_owned(),
828 Err(Error::InvalidOptionType(0xFF))
829 ));
830 }
831
832 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 #[test]
934 fn load_balancing_invalid_length_returns_error() {
935 let mut buf = [0u8; 6];
937 buf[0] = 0x00;
938 buf[1] = 0x03; buf[2] = 0x02; buf[3] = 0x00; 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 let mut buf = [0u8; 8];
955 buf[0] = 0x00;
956 buf[1] = 0x05; buf[2] = 0x04; 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 let mut buf = [0u8; 12];
973 buf[0] = 0x00;
974 buf[1] = 0x09; buf[2] = 0x06; 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 let mut buf = [0u8; 8];
991 buf[0] = 0x00;
992 buf[1] = 0x05;
993 buf[2] = 0x14; 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 let mut buf = [0u8; 12];
1009 buf[0] = 0x00;
1010 buf[1] = 0x09;
1011 buf[2] = 0x16; 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 let mut buf = [0u8; 8];
1027 buf[0] = 0x00;
1028 buf[1] = 0x05;
1029 buf[2] = 0x24; 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 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 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 let mut buf = [0u8; 12];
1126 buf[0] = 0x00;
1127 buf[1] = 0x09;
1128 buf[2] = 0x26; 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 #[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]; 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 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 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 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 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 let _ = iter.next().unwrap();
1236
1237 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 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 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 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 let buf: [u8; 4] = [0x00, 0x01, 0xFF, 0x00]; 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 #[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]; 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}