1use core::net::{Ipv4Addr, Ipv6Addr};
2
3use super::Error;
4use crate::protocol::byte_order::WriteBytesExt;
5
6pub const MAX_CONFIGURATION_STRING_LENGTH: usize = 256;
8
9pub(crate) const OPTION_HEADER_SIZE: usize = 4;
19pub(crate) const OPTION_LENGTH_SIZE_DELTA: usize = 3;
21const OPTION_TYPE_OFFSET: usize = 2;
23const OPTION_PAYLOAD_OFFSET: usize = 4;
25
26pub(crate) const IPV4_OPTION_WIRE_SIZE: usize = 12;
29pub(crate) const IPV4_OPTION_LENGTH_FIELD: u16 = 9;
31pub(crate) const IPV4_OPTION_IP_OFFSET: usize = OPTION_PAYLOAD_OFFSET;
33pub(crate) const IPV4_OPTION_PROTOCOL_OFFSET: usize = 9;
35pub(crate) const IPV4_OPTION_PORT_OFFSET: usize = 10;
37
38pub(crate) const IPV6_OPTION_WIRE_SIZE: usize = 24;
41pub(crate) const IPV6_OPTION_LENGTH_FIELD: u16 = 21;
43const IPV6_OPTION_IP_OFFSET: usize = OPTION_PAYLOAD_OFFSET;
45const IPV6_OPTION_IP_END: usize = IPV6_OPTION_IP_OFFSET + 16;
47pub(crate) const IPV6_OPTION_PROTOCOL_OFFSET: usize = 21;
49const IPV6_OPTION_PORT_OFFSET: usize = 22;
51
52const LOAD_BALANCING_OPTION_WIRE_SIZE: usize = 8;
55pub(crate) const LOAD_BALANCING_OPTION_LENGTH_FIELD: u16 = 5;
57
58const CONFIGURATION_OPTION_LENGTH_STRING_DELTA: u16 = 1;
62
63pub use crate::net_endpoint::TransportProtocol;
64
65impl TryFrom<u8> for TransportProtocol {
66 type Error = Error;
67 fn try_from(value: u8) -> Result<Self, Error> {
68 match value {
69 0x11 => Ok(TransportProtocol::Udp),
70 0x06 => Ok(TransportProtocol::Tcp),
71 _ => Err(Error::InvalidOptionTransportProtocol(value)),
72 }
73 }
74}
75
76impl TryFrom<TransportProtocol> for u8 {
77 type Error = Error;
78 fn try_from(value: TransportProtocol) -> Result<u8, Error> {
79 match value {
80 TransportProtocol::Udp => Ok(0x11),
81 TransportProtocol::Tcp => Ok(0x06),
82 }
83 }
84}
85
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88pub enum OptionType {
89 Configuration,
91 LoadBalancing,
93 IpV4Endpoint,
95 IpV6Endpoint,
97 IpV4Multicast,
99 IpV6Multicast,
101 IpV4SD,
103 IpV6SD,
105}
106
107impl TryFrom<u8> for OptionType {
108 type Error = Error;
109 fn try_from(value: u8) -> Result<Self, Error> {
110 match value {
111 0x01 => Ok(OptionType::Configuration),
112 0x02 => Ok(OptionType::LoadBalancing),
113 0x04 => Ok(OptionType::IpV4Endpoint),
114 0x06 => Ok(OptionType::IpV6Endpoint),
115 0x14 => Ok(OptionType::IpV4Multicast),
116 0x16 => Ok(OptionType::IpV6Multicast),
117 0x24 => Ok(OptionType::IpV4SD),
118 0x26 => Ok(OptionType::IpV6SD),
119 _ => Err(Error::InvalidOptionType(value)),
120 }
121 }
122}
123
124impl From<OptionType> for u8 {
125 fn from(option_type: OptionType) -> u8 {
126 match option_type {
127 OptionType::Configuration => 0x01,
128 OptionType::LoadBalancing => 0x02,
129 OptionType::IpV4Endpoint => 0x04,
130 OptionType::IpV6Endpoint => 0x06,
131 OptionType::IpV4Multicast => 0x14,
132 OptionType::IpV6Multicast => 0x16,
133 OptionType::IpV4SD => 0x24,
134 OptionType::IpV6SD => 0x26,
135 }
136 }
137}
138
139#[allow(clippy::large_enum_variant)]
141#[derive(Clone, Debug, Eq, PartialEq)]
143pub enum Options {
144 Configuration {
146 configuration_string: heapless::Vec<u8, MAX_CONFIGURATION_STRING_LENGTH>,
148 },
149 LoadBalancing {
151 priority: u16,
153 weight: u16,
155 },
156 IpV4Endpoint {
158 ip: Ipv4Addr,
160 protocol: TransportProtocol,
162 port: u16,
164 },
165 IpV6Endpoint {
167 ip: Ipv6Addr,
169 protocol: TransportProtocol,
171 port: u16,
173 },
174 IpV4Multicast {
176 ip: Ipv4Addr,
178 protocol: TransportProtocol,
180 port: u16,
182 },
183 IpV6Multicast {
185 ip: Ipv6Addr,
187 protocol: TransportProtocol,
189 port: u16,
191 },
192 IpV4SD {
194 ip: Ipv4Addr,
196 protocol: TransportProtocol,
198 port: u16,
200 },
201 IpV6SD {
203 ip: Ipv6Addr,
205 protocol: TransportProtocol,
207 port: u16,
209 },
210}
211
212impl Options {
213 #[must_use]
215 pub fn size(&self) -> usize {
216 match self {
217 Options::Configuration {
218 configuration_string,
219 } => OPTION_HEADER_SIZE + configuration_string.len(),
220 Options::LoadBalancing { .. } => LOAD_BALANCING_OPTION_WIRE_SIZE,
221 Options::IpV4Endpoint { .. }
222 | Options::IpV4Multicast { .. }
223 | Options::IpV4SD { .. } => IPV4_OPTION_WIRE_SIZE,
224 Options::IpV6Endpoint { .. }
225 | Options::IpV6Multicast { .. }
226 | Options::IpV6SD { .. } => IPV6_OPTION_WIRE_SIZE,
227 }
228 }
229
230 pub fn write<T: embedded_io::Write>(
241 &self,
242 writer: &mut T,
243 ) -> Result<usize, crate::protocol::Error> {
244 writer.write_u16_be(
245 u16::try_from(self.size() - OPTION_LENGTH_SIZE_DELTA).expect("option size fits u16"),
246 )?;
247 match self {
248 Options::Configuration {
249 configuration_string,
250 } => {
251 writer.write_u8(u8::from(OptionType::Configuration))?;
252 writer.write_u8(0)?;
253 writer.write_bytes(configuration_string)?;
254 Ok(self.size())
255 }
256 Options::LoadBalancing { priority, weight } => {
257 writer.write_u8(u8::from(OptionType::LoadBalancing))?;
258 writer.write_u8(0)?;
259 writer.write_u16_be(*priority)?;
260 writer.write_u16_be(*weight)?;
261 Ok(LOAD_BALANCING_OPTION_WIRE_SIZE)
262 }
263 Options::IpV4Endpoint { ip, protocol, port } => {
264 write_ipv4_option(writer, OptionType::IpV4Endpoint, *ip, *protocol, *port)
265 }
266 Options::IpV6Endpoint { ip, protocol, port } => {
267 write_ipv6_option(writer, OptionType::IpV6Endpoint, *ip, *protocol, *port)
268 }
269 Options::IpV4Multicast { ip, protocol, port } => {
270 write_ipv4_option(writer, OptionType::IpV4Multicast, *ip, *protocol, *port)
271 }
272 Options::IpV6Multicast { ip, protocol, port } => {
273 write_ipv6_option(writer, OptionType::IpV6Multicast, *ip, *protocol, *port)
274 }
275 Options::IpV4SD { ip, protocol, port } => {
276 write_ipv4_option(writer, OptionType::IpV4SD, *ip, *protocol, *port)
277 }
278 Options::IpV6SD { ip, protocol, port } => {
279 write_ipv6_option(writer, OptionType::IpV6SD, *ip, *protocol, *port)
280 }
281 }
282 }
283}
284
285fn write_ipv4_option<T: embedded_io::Write>(
286 writer: &mut T,
287 option_type: OptionType,
288 ip: Ipv4Addr,
289 protocol: TransportProtocol,
290 port: u16,
291) -> Result<usize, crate::protocol::Error> {
292 writer.write_u8(u8::from(option_type))?;
293 writer.write_u8(0)?;
294 writer.write_u32_be(ip.to_bits())?;
295 writer.write_u8(0)?;
296 writer.write_u8(u8::try_from(protocol)?)?;
297 writer.write_u16_be(port)?;
298 Ok(IPV4_OPTION_WIRE_SIZE)
299}
300
301fn write_ipv6_option<T: embedded_io::Write>(
302 writer: &mut T,
303 option_type: OptionType,
304 ip: Ipv6Addr,
305 protocol: TransportProtocol,
306 port: u16,
307) -> Result<usize, crate::protocol::Error> {
308 writer.write_u8(u8::from(option_type))?;
309 writer.write_u8(0)?;
310 writer.write_bytes(&ip.octets())?;
311 writer.write_u8(0)?;
312 writer.write_u8(u8::try_from(protocol)?)?;
313 writer.write_u16_be(port)?;
314 Ok(IPV6_OPTION_WIRE_SIZE)
315}
316
317#[must_use]
322pub fn extract_ipv4_endpoint(
323 options: &[Options],
324) -> Option<(core::net::SocketAddrV4, TransportProtocol)> {
325 options.iter().find_map(|opt| match opt {
326 Options::IpV4Endpoint { ip, protocol, port } => {
327 Some((core::net::SocketAddrV4::new(*ip, *port), *protocol))
328 }
329 _ => None,
330 })
331}
332
333#[derive(Clone, Copy, Debug)]
343pub struct OptionView<'a>(&'a [u8]);
344
345impl<'a> OptionView<'a> {
346 pub fn option_type(&self) -> Result<OptionType, Error> {
352 OptionType::try_from(self.0[OPTION_TYPE_OFFSET])
353 }
354
355 #[must_use]
357 pub fn wire_size(&self) -> usize {
358 let length = u16::from_be_bytes([self.0[0], self.0[1]]);
359 usize::from(length) + OPTION_LENGTH_SIZE_DELTA
360 }
361
362 pub fn as_ipv4(&self) -> Result<(Ipv4Addr, TransportProtocol, u16), Error> {
373 let ip = Ipv4Addr::from_bits(u32::from_be_bytes([
374 self.0[IPV4_OPTION_IP_OFFSET],
375 self.0[IPV4_OPTION_IP_OFFSET + 1],
376 self.0[IPV4_OPTION_IP_OFFSET + 2],
377 self.0[IPV4_OPTION_IP_OFFSET + 3],
378 ]));
379 let protocol = TransportProtocol::try_from(self.0[IPV4_OPTION_PROTOCOL_OFFSET])?;
380 let port = u16::from_be_bytes([
381 self.0[IPV4_OPTION_PORT_OFFSET],
382 self.0[IPV4_OPTION_PORT_OFFSET + 1],
383 ]);
384 Ok((ip, protocol, port))
385 }
386
387 pub fn as_ipv6(&self) -> Result<(Ipv6Addr, TransportProtocol, u16), Error> {
398 let mut octets = [0u8; 16];
399 octets.copy_from_slice(&self.0[IPV6_OPTION_IP_OFFSET..IPV6_OPTION_IP_END]);
400 let ip = Ipv6Addr::from(octets);
401 let protocol = TransportProtocol::try_from(self.0[IPV6_OPTION_PROTOCOL_OFFSET])?;
402 let port = u16::from_be_bytes([
403 self.0[IPV6_OPTION_PORT_OFFSET],
404 self.0[IPV6_OPTION_PORT_OFFSET + 1],
405 ]);
406 Ok((ip, protocol, port))
407 }
408
409 #[must_use]
411 pub fn configuration_bytes(&self) -> &'a [u8] {
412 let length = u16::from_be_bytes([self.0[0], self.0[1]]);
413 let string_len = length.saturating_sub(CONFIGURATION_OPTION_LENGTH_STRING_DELTA);
414 &self.0[OPTION_PAYLOAD_OFFSET..OPTION_PAYLOAD_OFFSET + usize::from(string_len)]
415 }
416
417 pub fn as_load_balancing(&self) -> Result<(u16, u16), Error> {
423 let priority = u16::from_be_bytes([
424 self.0[OPTION_PAYLOAD_OFFSET],
425 self.0[OPTION_PAYLOAD_OFFSET + 1],
426 ]);
427 let weight = u16::from_be_bytes([
428 self.0[OPTION_PAYLOAD_OFFSET + 2],
429 self.0[OPTION_PAYLOAD_OFFSET + 3],
430 ]);
431 Ok((priority, weight))
432 }
433
434 pub fn to_owned(&self) -> Result<Options, Error> {
446 let option_type = self.option_type()?;
447 match option_type {
448 OptionType::Configuration => {
449 let config_bytes = self.configuration_bytes();
450 if config_bytes.len() > MAX_CONFIGURATION_STRING_LENGTH {
451 return Err(Error::ConfigurationStringTooLong(config_bytes.len()));
452 }
453 let mut configuration_string =
454 heapless::Vec::<u8, MAX_CONFIGURATION_STRING_LENGTH>::new();
455 configuration_string
456 .extend_from_slice(config_bytes)
457 .expect("length validated above");
458 Ok(Options::Configuration {
459 configuration_string,
460 })
461 }
462 OptionType::LoadBalancing => {
463 let (priority, weight) = self.as_load_balancing()?;
464 Ok(Options::LoadBalancing { priority, weight })
465 }
466 OptionType::IpV4Endpoint => {
467 let (ip, protocol, port) = self.as_ipv4()?;
468 Ok(Options::IpV4Endpoint { ip, protocol, port })
469 }
470 OptionType::IpV6Endpoint => {
471 let (ip, protocol, port) = self.as_ipv6()?;
472 Ok(Options::IpV6Endpoint { ip, protocol, port })
473 }
474 OptionType::IpV4Multicast => {
475 let (ip, protocol, port) = self.as_ipv4()?;
476 Ok(Options::IpV4Multicast { ip, protocol, port })
477 }
478 OptionType::IpV6Multicast => {
479 let (ip, protocol, port) = self.as_ipv6()?;
480 Ok(Options::IpV6Multicast { ip, protocol, port })
481 }
482 OptionType::IpV4SD => {
483 let (ip, protocol, port) = self.as_ipv4()?;
484 Ok(Options::IpV4SD { ip, protocol, port })
485 }
486 OptionType::IpV6SD => {
487 let (ip, protocol, port) = self.as_ipv6()?;
488 Ok(Options::IpV6SD { ip, protocol, port })
489 }
490 }
491 }
492}
493
494#[derive(Clone)]
507pub struct OptionIter<'a> {
508 remaining: &'a [u8],
509}
510
511impl<'a> OptionIter<'a> {
512 pub(crate) fn new(buf: &'a [u8]) -> Self {
513 Self { remaining: buf }
514 }
515}
516
517impl<'a> Iterator for OptionIter<'a> {
518 type Item = OptionView<'a>;
519
520 fn next(&mut self) -> Option<Self::Item> {
521 if self.remaining.len() < OPTION_HEADER_SIZE {
522 return None;
523 }
524 let length = u16::from_be_bytes([self.remaining[0], self.remaining[1]]);
525 let wire_size = usize::from(length) + OPTION_LENGTH_SIZE_DELTA;
526 if wire_size > self.remaining.len() {
527 return None;
528 }
529 let view = OptionView(&self.remaining[..wire_size]);
530 self.remaining = &self.remaining[wire_size..];
531 Some(view)
532 }
533}
534
535pub(crate) fn validate_option(buf: &[u8]) -> Result<usize, Error> {
543 if buf.len() < OPTION_HEADER_SIZE {
544 return Err(Error::IncorrectOptionsSize(buf.len()));
545 }
546 let length = u16::from_be_bytes([buf[0], buf[1]]);
547 let wire_size = usize::from(length) + OPTION_LENGTH_SIZE_DELTA;
548 if wire_size > buf.len() {
549 return Err(Error::IncorrectOptionsSize(buf.len()));
550 }
551 let option_type_byte = buf[OPTION_TYPE_OFFSET];
552 let option_type = OptionType::try_from(option_type_byte)?;
553 match option_type {
555 OptionType::IpV4Endpoint | OptionType::IpV4Multicast | OptionType::IpV4SD => {
556 if length != IPV4_OPTION_LENGTH_FIELD {
557 return Err(Error::InvalidOptionLength {
558 option_type: option_type_byte,
559 expected: IPV4_OPTION_LENGTH_FIELD,
560 actual: length,
561 });
562 }
563 TransportProtocol::try_from(buf[IPV4_OPTION_PROTOCOL_OFFSET])?;
564 }
565 OptionType::IpV6Endpoint | OptionType::IpV6Multicast | OptionType::IpV6SD => {
566 if length != IPV6_OPTION_LENGTH_FIELD {
567 return Err(Error::InvalidOptionLength {
568 option_type: option_type_byte,
569 expected: IPV6_OPTION_LENGTH_FIELD,
570 actual: length,
571 });
572 }
573 TransportProtocol::try_from(buf[IPV6_OPTION_PROTOCOL_OFFSET])?;
574 }
575 OptionType::LoadBalancing => {
576 if length != LOAD_BALANCING_OPTION_LENGTH_FIELD {
577 return Err(Error::InvalidOptionLength {
578 option_type: option_type_byte,
579 expected: LOAD_BALANCING_OPTION_LENGTH_FIELD,
580 actual: length,
581 });
582 }
583 }
584 OptionType::Configuration => {
585 let string_len = length.saturating_sub(CONFIGURATION_OPTION_LENGTH_STRING_DELTA);
587 if usize::from(string_len) > MAX_CONFIGURATION_STRING_LENGTH {
588 return Err(Error::ConfigurationStringTooLong(string_len.into()));
589 }
590 }
591 }
592 Ok(wire_size)
593}
594
595#[cfg(test)]
596mod tests {
597 use core::net::{Ipv4Addr, Ipv6Addr};
598
599 use super::*;
600
601 #[test]
604 fn transport_protocol_tcp_round_trip() {
605 assert_eq!(
606 TransportProtocol::try_from(0x06).unwrap(),
607 TransportProtocol::Tcp
608 );
609 assert_eq!(u8::try_from(TransportProtocol::Tcp).unwrap(), 0x06);
610 }
611
612 #[test]
613 fn transport_protocol_invalid_returns_error() {
614 assert!(matches!(
615 TransportProtocol::try_from(0xFF),
616 Err(Error::InvalidOptionTransportProtocol(0xFF))
617 ));
618 }
619
620 #[test]
623 fn option_view_ipv4_endpoint_tcp() {
624 let buf: [u8; 12] = [
625 0x00, 0x09, 0x04, 0x00, 192, 168, 0, 1, 0x00, 0x06, 0x04, 0xD2, ];
633 let view = OptionView(&buf);
634 assert_eq!(view.option_type().unwrap(), OptionType::IpV4Endpoint);
635 assert_eq!(view.wire_size(), 12);
636 let (ip, protocol, port) = view.as_ipv4().unwrap();
637 assert_eq!(ip, Ipv4Addr::new(192, 168, 0, 1));
638 assert_eq!(protocol, TransportProtocol::Tcp);
639 assert_eq!(port, 1234);
640 }
641
642 #[test]
643 fn option_view_to_owned_invalid_type() {
644 let buf: [u8; 4] = [0x00, 0x00, 0xFF, 0x00]; let view = OptionView(&buf);
646 assert!(matches!(
647 view.to_owned(),
648 Err(Error::InvalidOptionType(0xFF))
649 ));
650 }
651
652 fn round_trip(option: &Options) {
655 let size = option.size();
656 let mut buf = [0u8; 4 + MAX_CONFIGURATION_STRING_LENGTH];
657 let written = option.write(&mut &mut buf[..size]).unwrap();
658 assert_eq!(written, size);
659 let view = OptionView(&buf[..size]);
660 let parsed = view.to_owned().unwrap();
661 assert_eq!(*option, parsed);
662 }
663
664 #[test]
665 fn configuration_round_trip() {
666 let mut config_string = heapless::Vec::<u8, MAX_CONFIGURATION_STRING_LENGTH>::new();
667 config_string.extend_from_slice(b"test=value").unwrap();
668 let option = Options::Configuration {
669 configuration_string: config_string,
670 };
671 round_trip(&option);
672 }
673
674 #[test]
675 fn configuration_empty_round_trip() {
676 let option = Options::Configuration {
677 configuration_string: heapless::Vec::new(),
678 };
679 round_trip(&option);
680 }
681
682 #[test]
683 fn load_balancing_round_trip() {
684 let option = Options::LoadBalancing {
685 priority: 100,
686 weight: 200,
687 };
688 round_trip(&option);
689 }
690
691 #[test]
692 fn ipv4_endpoint_round_trip() {
693 let option = Options::IpV4Endpoint {
694 ip: Ipv4Addr::new(10, 0, 0, 1),
695 protocol: TransportProtocol::Udp,
696 port: 30490,
697 };
698 round_trip(&option);
699 }
700
701 #[test]
702 fn ipv6_endpoint_round_trip() {
703 let option = Options::IpV6Endpoint {
704 ip: Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
705 protocol: TransportProtocol::Tcp,
706 port: 8080,
707 };
708 round_trip(&option);
709 }
710
711 #[test]
712 fn ipv4_multicast_round_trip() {
713 let option = Options::IpV4Multicast {
714 ip: Ipv4Addr::new(239, 0, 0, 1),
715 protocol: TransportProtocol::Udp,
716 port: 30490,
717 };
718 round_trip(&option);
719 }
720
721 #[test]
722 fn ipv6_multicast_round_trip() {
723 let option = Options::IpV6Multicast {
724 ip: Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
725 protocol: TransportProtocol::Udp,
726 port: 30490,
727 };
728 round_trip(&option);
729 }
730
731 #[test]
732 fn ipv4_sd_round_trip() {
733 let option = Options::IpV4SD {
734 ip: Ipv4Addr::new(172, 16, 0, 1),
735 protocol: TransportProtocol::Udp,
736 port: 30490,
737 };
738 round_trip(&option);
739 }
740
741 #[test]
742 fn ipv6_sd_round_trip() {
743 let option = Options::IpV6SD {
744 ip: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
745 protocol: TransportProtocol::Tcp,
746 port: 9999,
747 };
748 round_trip(&option);
749 }
750
751 #[test]
754 fn load_balancing_invalid_length_returns_error() {
755 let mut buf = [0u8; 6];
757 buf[0] = 0x00;
758 buf[1] = 0x03; buf[2] = 0x02; buf[3] = 0x00; assert!(matches!(
762 validate_option(&buf),
763 Err(Error::InvalidOptionLength {
764 option_type: 0x02,
765 expected: 5,
766 actual: 3,
767 })
768 ));
769 }
770
771 #[test]
772 fn ipv4_endpoint_invalid_length_returns_error() {
773 let mut buf = [0u8; 8];
775 buf[0] = 0x00;
776 buf[1] = 0x05; buf[2] = 0x04; buf[3] = 0x00;
779 assert!(matches!(
780 validate_option(&buf),
781 Err(Error::InvalidOptionLength {
782 option_type: 0x04,
783 expected: 9,
784 actual: 5,
785 })
786 ));
787 }
788
789 #[test]
790 fn ipv6_endpoint_invalid_length_returns_error() {
791 let mut buf = [0u8; 12];
793 buf[0] = 0x00;
794 buf[1] = 0x09; buf[2] = 0x06; buf[3] = 0x00;
797 assert!(matches!(
798 validate_option(&buf),
799 Err(Error::InvalidOptionLength {
800 option_type: 0x06,
801 expected: 21,
802 actual: 9,
803 })
804 ));
805 }
806
807 #[test]
808 fn ipv4_multicast_invalid_length_returns_error() {
809 let mut buf = [0u8; 8];
811 buf[0] = 0x00;
812 buf[1] = 0x05;
813 buf[2] = 0x14; buf[3] = 0x00;
815 assert!(matches!(
816 validate_option(&buf),
817 Err(Error::InvalidOptionLength {
818 option_type: 0x14,
819 expected: 9,
820 actual: 5,
821 })
822 ));
823 }
824
825 #[test]
826 fn ipv6_multicast_invalid_length_returns_error() {
827 let mut buf = [0u8; 12];
829 buf[0] = 0x00;
830 buf[1] = 0x09;
831 buf[2] = 0x16; buf[3] = 0x00;
833 assert!(matches!(
834 validate_option(&buf),
835 Err(Error::InvalidOptionLength {
836 option_type: 0x16,
837 expected: 21,
838 actual: 9,
839 })
840 ));
841 }
842
843 #[test]
844 fn ipv4_sd_invalid_length_returns_error() {
845 let mut buf = [0u8; 8];
847 buf[0] = 0x00;
848 buf[1] = 0x05;
849 buf[2] = 0x24; buf[3] = 0x00;
851 assert!(matches!(
852 validate_option(&buf),
853 Err(Error::InvalidOptionLength {
854 option_type: 0x24,
855 expected: 9,
856 actual: 5,
857 })
858 ));
859 }
860
861 fn ipv4_option_with_protocol(
865 option_type: OptionType,
866 protocol_byte: u8,
867 ) -> [u8; IPV4_OPTION_WIRE_SIZE] {
868 let mut buf = [0u8; IPV4_OPTION_WIRE_SIZE];
869 buf[0..2].copy_from_slice(&IPV4_OPTION_LENGTH_FIELD.to_be_bytes());
870 buf[OPTION_TYPE_OFFSET] = u8::from(option_type);
871 buf[IPV4_OPTION_PROTOCOL_OFFSET] = protocol_byte;
872 buf
873 }
874
875 fn ipv6_option_with_protocol(
878 option_type: OptionType,
879 protocol_byte: u8,
880 ) -> [u8; IPV6_OPTION_WIRE_SIZE] {
881 let mut buf = [0u8; IPV6_OPTION_WIRE_SIZE];
882 buf[0..2].copy_from_slice(&IPV6_OPTION_LENGTH_FIELD.to_be_bytes());
883 buf[OPTION_TYPE_OFFSET] = u8::from(option_type);
884 buf[IPV6_OPTION_PROTOCOL_OFFSET] = protocol_byte;
885 buf
886 }
887
888 #[test]
889 fn ipv4_endpoint_invalid_transport_protocol_returns_error() {
890 let buf = ipv4_option_with_protocol(OptionType::IpV4Endpoint, 0xAB);
891 assert!(matches!(
892 validate_option(&buf),
893 Err(Error::InvalidOptionTransportProtocol(0xAB))
894 ));
895 }
896
897 #[test]
898 fn ipv4_multicast_invalid_transport_protocol_returns_error() {
899 let buf = ipv4_option_with_protocol(OptionType::IpV4Multicast, 0x42);
900 assert!(matches!(
901 validate_option(&buf),
902 Err(Error::InvalidOptionTransportProtocol(0x42))
903 ));
904 }
905
906 #[test]
907 fn ipv4_sd_invalid_transport_protocol_returns_error() {
908 let buf = ipv4_option_with_protocol(OptionType::IpV4SD, 0x01);
909 assert!(matches!(
910 validate_option(&buf),
911 Err(Error::InvalidOptionTransportProtocol(0x01))
912 ));
913 }
914
915 #[test]
916 fn ipv6_endpoint_invalid_transport_protocol_returns_error() {
917 let buf = ipv6_option_with_protocol(OptionType::IpV6Endpoint, 0x99);
918 assert!(matches!(
919 validate_option(&buf),
920 Err(Error::InvalidOptionTransportProtocol(0x99))
921 ));
922 }
923
924 #[test]
925 fn ipv6_multicast_invalid_transport_protocol_returns_error() {
926 let buf = ipv6_option_with_protocol(OptionType::IpV6Multicast, 0x00);
927 assert!(matches!(
928 validate_option(&buf),
929 Err(Error::InvalidOptionTransportProtocol(0x00))
930 ));
931 }
932
933 #[test]
934 fn ipv6_sd_invalid_transport_protocol_returns_error() {
935 let buf = ipv6_option_with_protocol(OptionType::IpV6SD, 0xFE);
936 assert!(matches!(
937 validate_option(&buf),
938 Err(Error::InvalidOptionTransportProtocol(0xFE))
939 ));
940 }
941
942 #[test]
943 fn ipv6_sd_invalid_length_returns_error() {
944 let mut buf = [0u8; 12];
946 buf[0] = 0x00;
947 buf[1] = 0x09;
948 buf[2] = 0x26; buf[3] = 0x00;
950 assert!(matches!(
951 validate_option(&buf),
952 Err(Error::InvalidOptionLength {
953 option_type: 0x26,
954 expected: 21,
955 actual: 9,
956 })
957 ));
958 }
959
960 #[test]
963 fn option_iter_empty() {
964 let iter = OptionIter::new(&[]);
965 assert_eq!(iter.count(), 0);
966 }
967
968 #[test]
969 fn option_iter_two_options() {
970 let opt1 = Options::IpV4Endpoint {
971 ip: Ipv4Addr::new(10, 0, 0, 1),
972 protocol: TransportProtocol::Udp,
973 port: 30490,
974 };
975 let opt2 = Options::LoadBalancing {
976 priority: 100,
977 weight: 200,
978 };
979 let mut buf = [0u8; 24]; let n1 = opt1.write(&mut &mut buf[..12]).unwrap();
981 let n2 = opt2.write(&mut &mut buf[12..20]).unwrap();
982 let total = n1 + n2;
983
984 let mut iter = OptionIter::new(&buf[..total]);
985 let v1 = iter.next().unwrap();
986 assert_eq!(v1.to_owned().unwrap(), opt1);
987 let v2 = iter.next().unwrap();
988 assert_eq!(v2.to_owned().unwrap(), opt2);
989 assert!(iter.next().is_none());
990 }
991
992 #[test]
993 fn option_iter_clone_allows_reuse() {
994 let opt1 = Options::IpV4Endpoint {
998 ip: Ipv4Addr::new(10, 0, 0, 1),
999 protocol: TransportProtocol::Udp,
1000 port: 30490,
1001 };
1002 let opt2 = Options::IpV4Endpoint {
1003 ip: Ipv4Addr::new(10, 0, 0, 2),
1004 protocol: TransportProtocol::Udp,
1005 port: 30491,
1006 };
1007 let mut buf = [0u8; 24];
1008 let n1 = opt1.write(&mut &mut buf[..12]).unwrap();
1009 let n2 = opt2.write(&mut &mut buf[12..24]).unwrap();
1010 let total = n1 + n2;
1011
1012 let iter = OptionIter::new(&buf[..total]);
1013 let clone = iter.clone();
1014
1015 let mut walker = iter;
1017 let a = walker.next().unwrap().to_owned().unwrap();
1018 let b = walker.next().unwrap().to_owned().unwrap();
1019 assert!(walker.next().is_none());
1020 assert_eq!(a, opt1);
1021 assert_eq!(b, opt2);
1022
1023 let mut walker2 = clone;
1026 let a2 = walker2.next().unwrap().to_owned().unwrap();
1027 let b2 = walker2.next().unwrap().to_owned().unwrap();
1028 assert!(walker2.next().is_none());
1029 assert_eq!(a2, opt1);
1030 assert_eq!(b2, opt2);
1031 }
1032
1033 #[test]
1034 fn option_iter_clone_mid_walk_preserves_position() {
1035 let opt1 = Options::IpV4Endpoint {
1039 ip: Ipv4Addr::new(10, 0, 0, 1),
1040 protocol: TransportProtocol::Udp,
1041 port: 30490,
1042 };
1043 let opt2 = Options::IpV4Endpoint {
1044 ip: Ipv4Addr::new(10, 0, 0, 2),
1045 protocol: TransportProtocol::Udp,
1046 port: 30491,
1047 };
1048 let mut buf = [0u8; 24];
1049 let n1 = opt1.write(&mut &mut buf[..12]).unwrap();
1050 let n2 = opt2.write(&mut &mut buf[12..24]).unwrap();
1051 let total = n1 + n2;
1052
1053 let mut iter = OptionIter::new(&buf[..total]);
1054 let _ = iter.next().unwrap();
1056
1057 let mut clone = iter.clone();
1060 let remaining = clone.next().unwrap().to_owned().unwrap();
1061 assert!(clone.next().is_none());
1062 assert_eq!(remaining, opt2);
1063 }
1064}