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
63#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
65pub enum TransportProtocol {
66 Udp,
68 Tcp,
70}
71
72impl TryFrom<u8> for TransportProtocol {
73 type Error = Error;
74 fn try_from(value: u8) -> Result<Self, Error> {
75 match value {
76 0x11 => Ok(TransportProtocol::Udp),
77 0x06 => Ok(TransportProtocol::Tcp),
78 _ => Err(Error::InvalidOptionTransportProtocol(value)),
79 }
80 }
81}
82
83impl From<TransportProtocol> for u8 {
84 fn from(transport_protocol: TransportProtocol) -> u8 {
85 match transport_protocol {
86 TransportProtocol::Udp => 0x11,
87 TransportProtocol::Tcp => 0x06,
88 }
89 }
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
94pub enum OptionType {
95 Configuration,
97 LoadBalancing,
99 IpV4Endpoint,
101 IpV6Endpoint,
103 IpV4Multicast,
105 IpV6Multicast,
107 IpV4SD,
109 IpV6SD,
111}
112
113impl TryFrom<u8> for OptionType {
114 type Error = Error;
115 fn try_from(value: u8) -> Result<Self, Error> {
116 match value {
117 0x01 => Ok(OptionType::Configuration),
118 0x02 => Ok(OptionType::LoadBalancing),
119 0x04 => Ok(OptionType::IpV4Endpoint),
120 0x06 => Ok(OptionType::IpV6Endpoint),
121 0x14 => Ok(OptionType::IpV4Multicast),
122 0x16 => Ok(OptionType::IpV6Multicast),
123 0x24 => Ok(OptionType::IpV4SD),
124 0x26 => Ok(OptionType::IpV6SD),
125 _ => Err(Error::InvalidOptionType(value)),
126 }
127 }
128}
129
130impl From<OptionType> for u8 {
131 fn from(option_type: OptionType) -> u8 {
132 match option_type {
133 OptionType::Configuration => 0x01,
134 OptionType::LoadBalancing => 0x02,
135 OptionType::IpV4Endpoint => 0x04,
136 OptionType::IpV6Endpoint => 0x06,
137 OptionType::IpV4Multicast => 0x14,
138 OptionType::IpV6Multicast => 0x16,
139 OptionType::IpV4SD => 0x24,
140 OptionType::IpV6SD => 0x26,
141 }
142 }
143}
144
145#[allow(clippy::large_enum_variant)]
147#[derive(Clone, Debug, Eq, PartialEq)]
149pub enum Options {
150 Configuration {
152 configuration_string: heapless::Vec<u8, MAX_CONFIGURATION_STRING_LENGTH>,
154 },
155 LoadBalancing {
157 priority: u16,
159 weight: u16,
161 },
162 IpV4Endpoint {
164 ip: Ipv4Addr,
166 protocol: TransportProtocol,
168 port: u16,
170 },
171 IpV6Endpoint {
173 ip: Ipv6Addr,
175 protocol: TransportProtocol,
177 port: u16,
179 },
180 IpV4Multicast {
182 ip: Ipv4Addr,
184 protocol: TransportProtocol,
186 port: u16,
188 },
189 IpV6Multicast {
191 ip: Ipv6Addr,
193 protocol: TransportProtocol,
195 port: u16,
197 },
198 IpV4SD {
200 ip: Ipv4Addr,
202 protocol: TransportProtocol,
204 port: u16,
206 },
207 IpV6SD {
209 ip: Ipv6Addr,
211 protocol: TransportProtocol,
213 port: u16,
215 },
216}
217
218impl Options {
219 #[must_use]
221 pub fn size(&self) -> usize {
222 match self {
223 Options::Configuration {
224 configuration_string,
225 } => OPTION_HEADER_SIZE + configuration_string.len(),
226 Options::LoadBalancing { .. } => LOAD_BALANCING_OPTION_WIRE_SIZE,
227 Options::IpV4Endpoint { .. }
228 | Options::IpV4Multicast { .. }
229 | Options::IpV4SD { .. } => IPV4_OPTION_WIRE_SIZE,
230 Options::IpV6Endpoint { .. }
231 | Options::IpV6Multicast { .. }
232 | Options::IpV6SD { .. } => IPV6_OPTION_WIRE_SIZE,
233 }
234 }
235
236 pub fn write<T: embedded_io::Write>(
247 &self,
248 writer: &mut T,
249 ) -> Result<usize, crate::protocol::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::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::from(protocol))?;
319 writer.write_u16_be(port)?;
320 Ok(IPV6_OPTION_WIRE_SIZE)
321}
322
323#[must_use]
327pub fn extract_ipv4_endpoint(options: &[Options]) -> Option<core::net::SocketAddrV4> {
328 options.iter().find_map(|opt| match opt {
329 Options::IpV4Endpoint { ip, port, .. } => Some(core::net::SocketAddrV4::new(*ip, *port)),
330 _ => None,
331 })
332}
333
334#[derive(Clone, Copy, Debug)]
344pub struct OptionView<'a>(&'a [u8]);
345
346impl<'a> OptionView<'a> {
347 pub fn option_type(&self) -> Result<OptionType, Error> {
353 OptionType::try_from(self.0[OPTION_TYPE_OFFSET])
354 }
355
356 #[must_use]
358 pub fn wire_size(&self) -> usize {
359 let length = u16::from_be_bytes([self.0[0], self.0[1]]);
360 usize::from(length) + OPTION_LENGTH_SIZE_DELTA
361 }
362
363 pub fn as_ipv4(&self) -> Result<(Ipv4Addr, TransportProtocol, u16), Error> {
374 let ip = Ipv4Addr::from_bits(u32::from_be_bytes([
375 self.0[IPV4_OPTION_IP_OFFSET],
376 self.0[IPV4_OPTION_IP_OFFSET + 1],
377 self.0[IPV4_OPTION_IP_OFFSET + 2],
378 self.0[IPV4_OPTION_IP_OFFSET + 3],
379 ]));
380 let protocol = TransportProtocol::try_from(self.0[IPV4_OPTION_PROTOCOL_OFFSET])?;
381 let port = u16::from_be_bytes([
382 self.0[IPV4_OPTION_PORT_OFFSET],
383 self.0[IPV4_OPTION_PORT_OFFSET + 1],
384 ]);
385 Ok((ip, protocol, port))
386 }
387
388 pub fn as_ipv6(&self) -> Result<(Ipv6Addr, TransportProtocol, u16), Error> {
399 let mut octets = [0u8; 16];
400 octets.copy_from_slice(&self.0[IPV6_OPTION_IP_OFFSET..IPV6_OPTION_IP_END]);
401 let ip = Ipv6Addr::from(octets);
402 let protocol = TransportProtocol::try_from(self.0[IPV6_OPTION_PROTOCOL_OFFSET])?;
403 let port = u16::from_be_bytes([
404 self.0[IPV6_OPTION_PORT_OFFSET],
405 self.0[IPV6_OPTION_PORT_OFFSET + 1],
406 ]);
407 Ok((ip, protocol, port))
408 }
409
410 #[must_use]
412 pub fn configuration_bytes(&self) -> &'a [u8] {
413 let length = u16::from_be_bytes([self.0[0], self.0[1]]);
414 let string_len = length.saturating_sub(CONFIGURATION_OPTION_LENGTH_STRING_DELTA);
415 &self.0[OPTION_PAYLOAD_OFFSET..OPTION_PAYLOAD_OFFSET + usize::from(string_len)]
416 }
417
418 pub fn as_load_balancing(&self) -> Result<(u16, u16), Error> {
424 let priority = u16::from_be_bytes([
425 self.0[OPTION_PAYLOAD_OFFSET],
426 self.0[OPTION_PAYLOAD_OFFSET + 1],
427 ]);
428 let weight = u16::from_be_bytes([
429 self.0[OPTION_PAYLOAD_OFFSET + 2],
430 self.0[OPTION_PAYLOAD_OFFSET + 3],
431 ]);
432 Ok((priority, weight))
433 }
434
435 pub fn to_owned(&self) -> Result<Options, Error> {
447 let option_type = self.option_type()?;
448 match option_type {
449 OptionType::Configuration => {
450 let config_bytes = self.configuration_bytes();
451 if config_bytes.len() > MAX_CONFIGURATION_STRING_LENGTH {
452 return Err(Error::ConfigurationStringTooLong(config_bytes.len()));
453 }
454 let mut configuration_string =
455 heapless::Vec::<u8, MAX_CONFIGURATION_STRING_LENGTH>::new();
456 configuration_string
457 .extend_from_slice(config_bytes)
458 .expect("length validated above");
459 Ok(Options::Configuration {
460 configuration_string,
461 })
462 }
463 OptionType::LoadBalancing => {
464 let (priority, weight) = self.as_load_balancing()?;
465 Ok(Options::LoadBalancing { priority, weight })
466 }
467 OptionType::IpV4Endpoint => {
468 let (ip, protocol, port) = self.as_ipv4()?;
469 Ok(Options::IpV4Endpoint { ip, protocol, port })
470 }
471 OptionType::IpV6Endpoint => {
472 let (ip, protocol, port) = self.as_ipv6()?;
473 Ok(Options::IpV6Endpoint { ip, protocol, port })
474 }
475 OptionType::IpV4Multicast => {
476 let (ip, protocol, port) = self.as_ipv4()?;
477 Ok(Options::IpV4Multicast { ip, protocol, port })
478 }
479 OptionType::IpV6Multicast => {
480 let (ip, protocol, port) = self.as_ipv6()?;
481 Ok(Options::IpV6Multicast { ip, protocol, port })
482 }
483 OptionType::IpV4SD => {
484 let (ip, protocol, port) = self.as_ipv4()?;
485 Ok(Options::IpV4SD { ip, protocol, port })
486 }
487 OptionType::IpV6SD => {
488 let (ip, protocol, port) = self.as_ipv6()?;
489 Ok(Options::IpV6SD { ip, protocol, port })
490 }
491 }
492 }
493}
494
495#[derive(Clone)]
508pub struct OptionIter<'a> {
509 remaining: &'a [u8],
510}
511
512impl<'a> OptionIter<'a> {
513 pub(crate) fn new(buf: &'a [u8]) -> Self {
514 Self { remaining: buf }
515 }
516}
517
518impl<'a> Iterator for OptionIter<'a> {
519 type Item = OptionView<'a>;
520
521 fn next(&mut self) -> Option<Self::Item> {
522 if self.remaining.len() < OPTION_HEADER_SIZE {
523 return None;
524 }
525 let length = u16::from_be_bytes([self.remaining[0], self.remaining[1]]);
526 let wire_size = usize::from(length) + OPTION_LENGTH_SIZE_DELTA;
527 if wire_size > self.remaining.len() {
528 return None;
529 }
530 let view = OptionView(&self.remaining[..wire_size]);
531 self.remaining = &self.remaining[wire_size..];
532 Some(view)
533 }
534}
535
536pub(crate) fn validate_option(buf: &[u8]) -> Result<usize, Error> {
544 if buf.len() < OPTION_HEADER_SIZE {
545 return Err(Error::IncorrectOptionsSize(buf.len()));
546 }
547 let length = u16::from_be_bytes([buf[0], buf[1]]);
548 let wire_size = usize::from(length) + OPTION_LENGTH_SIZE_DELTA;
549 if wire_size > buf.len() {
550 return Err(Error::IncorrectOptionsSize(buf.len()));
551 }
552 let option_type_byte = buf[OPTION_TYPE_OFFSET];
553 let option_type = OptionType::try_from(option_type_byte)?;
554 match option_type {
556 OptionType::IpV4Endpoint | OptionType::IpV4Multicast | OptionType::IpV4SD => {
557 if length != IPV4_OPTION_LENGTH_FIELD {
558 return Err(Error::InvalidOptionLength {
559 option_type: option_type_byte,
560 expected: IPV4_OPTION_LENGTH_FIELD,
561 actual: length,
562 });
563 }
564 TransportProtocol::try_from(buf[IPV4_OPTION_PROTOCOL_OFFSET])?;
565 }
566 OptionType::IpV6Endpoint | OptionType::IpV6Multicast | OptionType::IpV6SD => {
567 if length != IPV6_OPTION_LENGTH_FIELD {
568 return Err(Error::InvalidOptionLength {
569 option_type: option_type_byte,
570 expected: IPV6_OPTION_LENGTH_FIELD,
571 actual: length,
572 });
573 }
574 TransportProtocol::try_from(buf[IPV6_OPTION_PROTOCOL_OFFSET])?;
575 }
576 OptionType::LoadBalancing => {
577 if length != LOAD_BALANCING_OPTION_LENGTH_FIELD {
578 return Err(Error::InvalidOptionLength {
579 option_type: option_type_byte,
580 expected: LOAD_BALANCING_OPTION_LENGTH_FIELD,
581 actual: length,
582 });
583 }
584 }
585 OptionType::Configuration => {
586 let string_len = length.saturating_sub(CONFIGURATION_OPTION_LENGTH_STRING_DELTA);
588 if usize::from(string_len) > MAX_CONFIGURATION_STRING_LENGTH {
589 return Err(Error::ConfigurationStringTooLong(string_len.into()));
590 }
591 }
592 }
593 Ok(wire_size)
594}
595
596#[cfg(test)]
597mod tests {
598 use core::net::{Ipv4Addr, Ipv6Addr};
599
600 use super::*;
601
602 #[test]
605 fn transport_protocol_tcp_round_trip() {
606 assert_eq!(
607 TransportProtocol::try_from(0x06).unwrap(),
608 TransportProtocol::Tcp
609 );
610 assert_eq!(u8::from(TransportProtocol::Tcp), 0x06);
611 }
612
613 #[test]
614 fn transport_protocol_invalid_returns_error() {
615 assert!(matches!(
616 TransportProtocol::try_from(0xFF),
617 Err(Error::InvalidOptionTransportProtocol(0xFF))
618 ));
619 }
620
621 #[test]
624 fn option_view_ipv4_endpoint_tcp() {
625 let buf: [u8; 12] = [
626 0x00, 0x09, 0x04, 0x00, 192, 168, 0, 1, 0x00, 0x06, 0x04, 0xD2, ];
634 let view = OptionView(&buf);
635 assert_eq!(view.option_type().unwrap(), OptionType::IpV4Endpoint);
636 assert_eq!(view.wire_size(), 12);
637 let (ip, protocol, port) = view.as_ipv4().unwrap();
638 assert_eq!(ip, Ipv4Addr::new(192, 168, 0, 1));
639 assert_eq!(protocol, TransportProtocol::Tcp);
640 assert_eq!(port, 1234);
641 }
642
643 #[test]
644 fn option_view_to_owned_invalid_type() {
645 let buf: [u8; 4] = [0x00, 0x00, 0xFF, 0x00]; let view = OptionView(&buf);
647 assert!(matches!(
648 view.to_owned(),
649 Err(Error::InvalidOptionType(0xFF))
650 ));
651 }
652
653 fn round_trip(option: &Options) {
656 let size = option.size();
657 let mut buf = [0u8; 4 + MAX_CONFIGURATION_STRING_LENGTH];
658 let written = option.write(&mut &mut buf[..size]).unwrap();
659 assert_eq!(written, size);
660 let view = OptionView(&buf[..size]);
661 let parsed = view.to_owned().unwrap();
662 assert_eq!(*option, parsed);
663 }
664
665 #[test]
666 fn configuration_round_trip() {
667 let mut config_string = heapless::Vec::<u8, MAX_CONFIGURATION_STRING_LENGTH>::new();
668 config_string.extend_from_slice(b"test=value").unwrap();
669 let option = Options::Configuration {
670 configuration_string: config_string,
671 };
672 round_trip(&option);
673 }
674
675 #[test]
676 fn configuration_empty_round_trip() {
677 let option = Options::Configuration {
678 configuration_string: heapless::Vec::new(),
679 };
680 round_trip(&option);
681 }
682
683 #[test]
684 fn load_balancing_round_trip() {
685 let option = Options::LoadBalancing {
686 priority: 100,
687 weight: 200,
688 };
689 round_trip(&option);
690 }
691
692 #[test]
693 fn ipv4_endpoint_round_trip() {
694 let option = Options::IpV4Endpoint {
695 ip: Ipv4Addr::new(10, 0, 0, 1),
696 protocol: TransportProtocol::Udp,
697 port: 30490,
698 };
699 round_trip(&option);
700 }
701
702 #[test]
703 fn ipv6_endpoint_round_trip() {
704 let option = Options::IpV6Endpoint {
705 ip: Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
706 protocol: TransportProtocol::Tcp,
707 port: 8080,
708 };
709 round_trip(&option);
710 }
711
712 #[test]
713 fn ipv4_multicast_round_trip() {
714 let option = Options::IpV4Multicast {
715 ip: Ipv4Addr::new(239, 0, 0, 1),
716 protocol: TransportProtocol::Udp,
717 port: 30490,
718 };
719 round_trip(&option);
720 }
721
722 #[test]
723 fn ipv6_multicast_round_trip() {
724 let option = Options::IpV6Multicast {
725 ip: Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
726 protocol: TransportProtocol::Udp,
727 port: 30490,
728 };
729 round_trip(&option);
730 }
731
732 #[test]
733 fn ipv4_sd_round_trip() {
734 let option = Options::IpV4SD {
735 ip: Ipv4Addr::new(172, 16, 0, 1),
736 protocol: TransportProtocol::Udp,
737 port: 30490,
738 };
739 round_trip(&option);
740 }
741
742 #[test]
743 fn ipv6_sd_round_trip() {
744 let option = Options::IpV6SD {
745 ip: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
746 protocol: TransportProtocol::Tcp,
747 port: 9999,
748 };
749 round_trip(&option);
750 }
751
752 #[test]
755 fn load_balancing_invalid_length_returns_error() {
756 let mut buf = [0u8; 6];
758 buf[0] = 0x00;
759 buf[1] = 0x03; buf[2] = 0x02; buf[3] = 0x00; assert!(matches!(
763 validate_option(&buf),
764 Err(Error::InvalidOptionLength {
765 option_type: 0x02,
766 expected: 5,
767 actual: 3,
768 })
769 ));
770 }
771
772 #[test]
773 fn ipv4_endpoint_invalid_length_returns_error() {
774 let mut buf = [0u8; 8];
776 buf[0] = 0x00;
777 buf[1] = 0x05; buf[2] = 0x04; buf[3] = 0x00;
780 assert!(matches!(
781 validate_option(&buf),
782 Err(Error::InvalidOptionLength {
783 option_type: 0x04,
784 expected: 9,
785 actual: 5,
786 })
787 ));
788 }
789
790 #[test]
791 fn ipv6_endpoint_invalid_length_returns_error() {
792 let mut buf = [0u8; 12];
794 buf[0] = 0x00;
795 buf[1] = 0x09; buf[2] = 0x06; buf[3] = 0x00;
798 assert!(matches!(
799 validate_option(&buf),
800 Err(Error::InvalidOptionLength {
801 option_type: 0x06,
802 expected: 21,
803 actual: 9,
804 })
805 ));
806 }
807
808 #[test]
809 fn ipv4_multicast_invalid_length_returns_error() {
810 let mut buf = [0u8; 8];
812 buf[0] = 0x00;
813 buf[1] = 0x05;
814 buf[2] = 0x14; buf[3] = 0x00;
816 assert!(matches!(
817 validate_option(&buf),
818 Err(Error::InvalidOptionLength {
819 option_type: 0x14,
820 expected: 9,
821 actual: 5,
822 })
823 ));
824 }
825
826 #[test]
827 fn ipv6_multicast_invalid_length_returns_error() {
828 let mut buf = [0u8; 12];
830 buf[0] = 0x00;
831 buf[1] = 0x09;
832 buf[2] = 0x16; buf[3] = 0x00;
834 assert!(matches!(
835 validate_option(&buf),
836 Err(Error::InvalidOptionLength {
837 option_type: 0x16,
838 expected: 21,
839 actual: 9,
840 })
841 ));
842 }
843
844 #[test]
845 fn ipv4_sd_invalid_length_returns_error() {
846 let mut buf = [0u8; 8];
848 buf[0] = 0x00;
849 buf[1] = 0x05;
850 buf[2] = 0x24; buf[3] = 0x00;
852 assert!(matches!(
853 validate_option(&buf),
854 Err(Error::InvalidOptionLength {
855 option_type: 0x24,
856 expected: 9,
857 actual: 5,
858 })
859 ));
860 }
861
862 fn ipv4_option_with_protocol(
866 option_type: OptionType,
867 protocol_byte: u8,
868 ) -> [u8; IPV4_OPTION_WIRE_SIZE] {
869 let mut buf = [0u8; IPV4_OPTION_WIRE_SIZE];
870 buf[0..2].copy_from_slice(&IPV4_OPTION_LENGTH_FIELD.to_be_bytes());
871 buf[OPTION_TYPE_OFFSET] = u8::from(option_type);
872 buf[IPV4_OPTION_PROTOCOL_OFFSET] = protocol_byte;
873 buf
874 }
875
876 fn ipv6_option_with_protocol(
879 option_type: OptionType,
880 protocol_byte: u8,
881 ) -> [u8; IPV6_OPTION_WIRE_SIZE] {
882 let mut buf = [0u8; IPV6_OPTION_WIRE_SIZE];
883 buf[0..2].copy_from_slice(&IPV6_OPTION_LENGTH_FIELD.to_be_bytes());
884 buf[OPTION_TYPE_OFFSET] = u8::from(option_type);
885 buf[IPV6_OPTION_PROTOCOL_OFFSET] = protocol_byte;
886 buf
887 }
888
889 #[test]
890 fn ipv4_endpoint_invalid_transport_protocol_returns_error() {
891 let buf = ipv4_option_with_protocol(OptionType::IpV4Endpoint, 0xAB);
892 assert!(matches!(
893 validate_option(&buf),
894 Err(Error::InvalidOptionTransportProtocol(0xAB))
895 ));
896 }
897
898 #[test]
899 fn ipv4_multicast_invalid_transport_protocol_returns_error() {
900 let buf = ipv4_option_with_protocol(OptionType::IpV4Multicast, 0x42);
901 assert!(matches!(
902 validate_option(&buf),
903 Err(Error::InvalidOptionTransportProtocol(0x42))
904 ));
905 }
906
907 #[test]
908 fn ipv4_sd_invalid_transport_protocol_returns_error() {
909 let buf = ipv4_option_with_protocol(OptionType::IpV4SD, 0x01);
910 assert!(matches!(
911 validate_option(&buf),
912 Err(Error::InvalidOptionTransportProtocol(0x01))
913 ));
914 }
915
916 #[test]
917 fn ipv6_endpoint_invalid_transport_protocol_returns_error() {
918 let buf = ipv6_option_with_protocol(OptionType::IpV6Endpoint, 0x99);
919 assert!(matches!(
920 validate_option(&buf),
921 Err(Error::InvalidOptionTransportProtocol(0x99))
922 ));
923 }
924
925 #[test]
926 fn ipv6_multicast_invalid_transport_protocol_returns_error() {
927 let buf = ipv6_option_with_protocol(OptionType::IpV6Multicast, 0x00);
928 assert!(matches!(
929 validate_option(&buf),
930 Err(Error::InvalidOptionTransportProtocol(0x00))
931 ));
932 }
933
934 #[test]
935 fn ipv6_sd_invalid_transport_protocol_returns_error() {
936 let buf = ipv6_option_with_protocol(OptionType::IpV6SD, 0xFE);
937 assert!(matches!(
938 validate_option(&buf),
939 Err(Error::InvalidOptionTransportProtocol(0xFE))
940 ));
941 }
942
943 #[test]
944 fn ipv6_sd_invalid_length_returns_error() {
945 let mut buf = [0u8; 12];
947 buf[0] = 0x00;
948 buf[1] = 0x09;
949 buf[2] = 0x26; buf[3] = 0x00;
951 assert!(matches!(
952 validate_option(&buf),
953 Err(Error::InvalidOptionLength {
954 option_type: 0x26,
955 expected: 21,
956 actual: 9,
957 })
958 ));
959 }
960
961 #[test]
964 fn option_iter_empty() {
965 let iter = OptionIter::new(&[]);
966 assert_eq!(iter.count(), 0);
967 }
968
969 #[test]
970 fn option_iter_two_options() {
971 let opt1 = Options::IpV4Endpoint {
972 ip: Ipv4Addr::new(10, 0, 0, 1),
973 protocol: TransportProtocol::Udp,
974 port: 30490,
975 };
976 let opt2 = Options::LoadBalancing {
977 priority: 100,
978 weight: 200,
979 };
980 let mut buf = [0u8; 24]; let n1 = opt1.write(&mut &mut buf[..12]).unwrap();
982 let n2 = opt2.write(&mut &mut buf[12..20]).unwrap();
983 let total = n1 + n2;
984
985 let mut iter = OptionIter::new(&buf[..total]);
986 let v1 = iter.next().unwrap();
987 assert_eq!(v1.to_owned().unwrap(), opt1);
988 let v2 = iter.next().unwrap();
989 assert_eq!(v2.to_owned().unwrap(), opt2);
990 assert!(iter.next().is_none());
991 }
992
993 #[test]
994 fn option_iter_clone_allows_reuse() {
995 let opt1 = Options::IpV4Endpoint {
999 ip: Ipv4Addr::new(10, 0, 0, 1),
1000 protocol: TransportProtocol::Udp,
1001 port: 30490,
1002 };
1003 let opt2 = Options::IpV4Endpoint {
1004 ip: Ipv4Addr::new(10, 0, 0, 2),
1005 protocol: TransportProtocol::Udp,
1006 port: 30491,
1007 };
1008 let mut buf = [0u8; 24];
1009 let n1 = opt1.write(&mut &mut buf[..12]).unwrap();
1010 let n2 = opt2.write(&mut &mut buf[12..24]).unwrap();
1011 let total = n1 + n2;
1012
1013 let iter = OptionIter::new(&buf[..total]);
1014 let clone = iter.clone();
1015
1016 let mut walker = iter;
1018 let a = walker.next().unwrap().to_owned().unwrap();
1019 let b = walker.next().unwrap().to_owned().unwrap();
1020 assert!(walker.next().is_none());
1021 assert_eq!(a, opt1);
1022 assert_eq!(b, opt2);
1023
1024 let mut walker2 = clone;
1027 let a2 = walker2.next().unwrap().to_owned().unwrap();
1028 let b2 = walker2.next().unwrap().to_owned().unwrap();
1029 assert!(walker2.next().is_none());
1030 assert_eq!(a2, opt1);
1031 assert_eq!(b2, opt2);
1032 }
1033
1034 #[test]
1035 fn option_iter_clone_mid_walk_preserves_position() {
1036 let opt1 = Options::IpV4Endpoint {
1040 ip: Ipv4Addr::new(10, 0, 0, 1),
1041 protocol: TransportProtocol::Udp,
1042 port: 30490,
1043 };
1044 let opt2 = Options::IpV4Endpoint {
1045 ip: Ipv4Addr::new(10, 0, 0, 2),
1046 protocol: TransportProtocol::Udp,
1047 port: 30491,
1048 };
1049 let mut buf = [0u8; 24];
1050 let n1 = opt1.write(&mut &mut buf[..12]).unwrap();
1051 let n2 = opt2.write(&mut &mut buf[12..24]).unwrap();
1052 let total = n1 + n2;
1053
1054 let mut iter = OptionIter::new(&buf[..total]);
1055 let _ = iter.next().unwrap();
1057
1058 let mut clone = iter.clone();
1061 let remaining = clone.next().unwrap().to_owned().unwrap();
1062 assert!(clone.next().is_none());
1063 assert_eq!(remaining, opt2);
1064 }
1065}