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
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
11pub enum TransportProtocol {
12 Udp,
14 Tcp,
16}
17
18impl TryFrom<u8> for TransportProtocol {
19 type Error = Error;
20 fn try_from(value: u8) -> Result<Self, Error> {
21 match value {
22 0x11 => Ok(TransportProtocol::Udp),
23 0x06 => Ok(TransportProtocol::Tcp),
24 _ => Err(Error::InvalidOptionTransportProtocol(value)),
25 }
26 }
27}
28
29impl From<TransportProtocol> for u8 {
30 fn from(transport_protocol: TransportProtocol) -> u8 {
31 match transport_protocol {
32 TransportProtocol::Udp => 0x11,
33 TransportProtocol::Tcp => 0x06,
34 }
35 }
36}
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub enum OptionType {
41 Configuration,
43 LoadBalancing,
45 IpV4Endpoint,
47 IpV6Endpoint,
49 IpV4Multicast,
51 IpV6Multicast,
53 IpV4SD,
55 IpV6SD,
57}
58
59impl TryFrom<u8> for OptionType {
60 type Error = Error;
61 fn try_from(value: u8) -> Result<Self, Error> {
62 match value {
63 0x01 => Ok(OptionType::Configuration),
64 0x02 => Ok(OptionType::LoadBalancing),
65 0x04 => Ok(OptionType::IpV4Endpoint),
66 0x06 => Ok(OptionType::IpV6Endpoint),
67 0x14 => Ok(OptionType::IpV4Multicast),
68 0x16 => Ok(OptionType::IpV6Multicast),
69 0x24 => Ok(OptionType::IpV4SD),
70 0x26 => Ok(OptionType::IpV6SD),
71 _ => Err(Error::InvalidOptionType(value)),
72 }
73 }
74}
75
76impl From<OptionType> for u8 {
77 fn from(option_type: OptionType) -> u8 {
78 match option_type {
79 OptionType::Configuration => 0x01,
80 OptionType::LoadBalancing => 0x02,
81 OptionType::IpV4Endpoint => 0x04,
82 OptionType::IpV6Endpoint => 0x06,
83 OptionType::IpV4Multicast => 0x14,
84 OptionType::IpV6Multicast => 0x16,
85 OptionType::IpV4SD => 0x24,
86 OptionType::IpV6SD => 0x26,
87 }
88 }
89}
90
91#[allow(clippy::large_enum_variant)]
93#[derive(Clone, Debug, Eq, PartialEq)]
95pub enum Options {
96 Configuration {
98 configuration_string: heapless::Vec<u8, MAX_CONFIGURATION_STRING_LENGTH>,
100 },
101 LoadBalancing {
103 priority: u16,
105 weight: u16,
107 },
108 IpV4Endpoint {
110 ip: Ipv4Addr,
112 protocol: TransportProtocol,
114 port: u16,
116 },
117 IpV6Endpoint {
119 ip: Ipv6Addr,
121 protocol: TransportProtocol,
123 port: u16,
125 },
126 IpV4Multicast {
128 ip: Ipv4Addr,
130 protocol: TransportProtocol,
132 port: u16,
134 },
135 IpV6Multicast {
137 ip: Ipv6Addr,
139 protocol: TransportProtocol,
141 port: u16,
143 },
144 IpV4SD {
146 ip: Ipv4Addr,
148 protocol: TransportProtocol,
150 port: u16,
152 },
153 IpV6SD {
155 ip: Ipv6Addr,
157 protocol: TransportProtocol,
159 port: u16,
161 },
162}
163
164impl Options {
165 #[must_use]
167 pub fn size(&self) -> usize {
168 match self {
169 Options::Configuration {
170 configuration_string,
171 } => 4 + configuration_string.len(),
172 Options::LoadBalancing { .. } => 8,
173 Options::IpV4Endpoint { .. }
174 | Options::IpV4Multicast { .. }
175 | Options::IpV4SD { .. } => 12,
176 Options::IpV6Endpoint { .. }
177 | Options::IpV6Multicast { .. }
178 | Options::IpV6SD { .. } => 24,
179 }
180 }
181
182 pub fn write<T: embedded_io::Write>(
192 &self,
193 writer: &mut T,
194 ) -> Result<usize, crate::protocol::Error> {
195 writer.write_u16_be(u16::try_from(self.size() - 3).expect("option size fits u16"))?;
196 match self {
197 Options::Configuration {
198 configuration_string,
199 } => {
200 writer.write_u8(u8::from(OptionType::Configuration))?;
201 writer.write_u8(0)?;
202 writer.write_bytes(configuration_string)?;
203 Ok(self.size())
204 }
205 Options::LoadBalancing { priority, weight } => {
206 writer.write_u8(u8::from(OptionType::LoadBalancing))?;
207 writer.write_u8(0)?;
208 writer.write_u16_be(*priority)?;
209 writer.write_u16_be(*weight)?;
210 Ok(8)
211 }
212 Options::IpV4Endpoint { ip, protocol, port } => {
213 write_ipv4_option(writer, OptionType::IpV4Endpoint, *ip, *protocol, *port)
214 }
215 Options::IpV6Endpoint { ip, protocol, port } => {
216 write_ipv6_option(writer, OptionType::IpV6Endpoint, *ip, *protocol, *port)
217 }
218 Options::IpV4Multicast { ip, protocol, port } => {
219 write_ipv4_option(writer, OptionType::IpV4Multicast, *ip, *protocol, *port)
220 }
221 Options::IpV6Multicast { ip, protocol, port } => {
222 write_ipv6_option(writer, OptionType::IpV6Multicast, *ip, *protocol, *port)
223 }
224 Options::IpV4SD { ip, protocol, port } => {
225 write_ipv4_option(writer, OptionType::IpV4SD, *ip, *protocol, *port)
226 }
227 Options::IpV6SD { ip, protocol, port } => {
228 write_ipv6_option(writer, OptionType::IpV6SD, *ip, *protocol, *port)
229 }
230 }
231 }
232}
233
234fn write_ipv4_option<T: embedded_io::Write>(
235 writer: &mut T,
236 option_type: OptionType,
237 ip: Ipv4Addr,
238 protocol: TransportProtocol,
239 port: u16,
240) -> Result<usize, crate::protocol::Error> {
241 writer.write_u8(u8::from(option_type))?;
242 writer.write_u8(0)?;
243 writer.write_u32_be(ip.to_bits())?;
244 writer.write_u8(0)?;
245 writer.write_u8(u8::from(protocol))?;
246 writer.write_u16_be(port)?;
247 Ok(12)
248}
249
250fn write_ipv6_option<T: embedded_io::Write>(
251 writer: &mut T,
252 option_type: OptionType,
253 ip: Ipv6Addr,
254 protocol: TransportProtocol,
255 port: u16,
256) -> Result<usize, crate::protocol::Error> {
257 writer.write_u8(u8::from(option_type))?;
258 writer.write_u8(0)?;
259 writer.write_bytes(&ip.octets())?;
260 writer.write_u8(0)?;
261 writer.write_u8(u8::from(protocol))?;
262 writer.write_u16_be(port)?;
263 Ok(24)
264}
265
266#[must_use]
270pub fn extract_ipv4_endpoint(options: &[Options]) -> Option<core::net::SocketAddrV4> {
271 options.iter().find_map(|opt| match opt {
272 Options::IpV4Endpoint { ip, port, .. } => Some(core::net::SocketAddrV4::new(*ip, *port)),
273 _ => None,
274 })
275}
276
277#[derive(Clone, Copy, Debug)]
287pub struct OptionView<'a>(&'a [u8]);
288
289impl<'a> OptionView<'a> {
290 pub fn option_type(&self) -> Result<OptionType, Error> {
296 OptionType::try_from(self.0[2])
297 }
298
299 #[must_use]
301 pub fn wire_size(&self) -> usize {
302 let length = u16::from_be_bytes([self.0[0], self.0[1]]);
303 usize::from(length) + 3
304 }
305
306 pub fn as_ipv4(&self) -> Result<(Ipv4Addr, TransportProtocol, u16), Error> {
313 let ip = Ipv4Addr::from_bits(u32::from_be_bytes([
314 self.0[4], self.0[5], self.0[6], self.0[7],
315 ]));
316 let protocol = TransportProtocol::try_from(self.0[9])?;
318 let port = u16::from_be_bytes([self.0[10], self.0[11]]);
319 Ok((ip, protocol, port))
320 }
321
322 pub fn as_ipv6(&self) -> Result<(Ipv6Addr, TransportProtocol, u16), Error> {
329 let mut octets = [0u8; 16];
330 octets.copy_from_slice(&self.0[4..20]);
331 let ip = Ipv6Addr::from(octets);
332 let protocol = TransportProtocol::try_from(self.0[21])?;
334 let port = u16::from_be_bytes([self.0[22], self.0[23]]);
335 Ok((ip, protocol, port))
336 }
337
338 #[must_use]
340 pub fn configuration_bytes(&self) -> &'a [u8] {
341 let length = u16::from_be_bytes([self.0[0], self.0[1]]);
342 let string_len = length.saturating_sub(1);
343 &self.0[4..4 + usize::from(string_len)]
344 }
345
346 pub fn as_load_balancing(&self) -> Result<(u16, u16), Error> {
352 let priority = u16::from_be_bytes([self.0[4], self.0[5]]);
353 let weight = u16::from_be_bytes([self.0[6], self.0[7]]);
354 Ok((priority, weight))
355 }
356
357 pub fn to_owned(&self) -> Result<Options, Error> {
369 let option_type = self.option_type()?;
370 match option_type {
371 OptionType::Configuration => {
372 let config_bytes = self.configuration_bytes();
373 if config_bytes.len() > MAX_CONFIGURATION_STRING_LENGTH {
374 return Err(Error::ConfigurationStringTooLong(config_bytes.len()));
375 }
376 let mut configuration_string =
377 heapless::Vec::<u8, MAX_CONFIGURATION_STRING_LENGTH>::new();
378 configuration_string
379 .extend_from_slice(config_bytes)
380 .expect("length validated above");
381 Ok(Options::Configuration {
382 configuration_string,
383 })
384 }
385 OptionType::LoadBalancing => {
386 let (priority, weight) = self.as_load_balancing()?;
387 Ok(Options::LoadBalancing { priority, weight })
388 }
389 OptionType::IpV4Endpoint => {
390 let (ip, protocol, port) = self.as_ipv4()?;
391 Ok(Options::IpV4Endpoint { ip, protocol, port })
392 }
393 OptionType::IpV6Endpoint => {
394 let (ip, protocol, port) = self.as_ipv6()?;
395 Ok(Options::IpV6Endpoint { ip, protocol, port })
396 }
397 OptionType::IpV4Multicast => {
398 let (ip, protocol, port) = self.as_ipv4()?;
399 Ok(Options::IpV4Multicast { ip, protocol, port })
400 }
401 OptionType::IpV6Multicast => {
402 let (ip, protocol, port) = self.as_ipv6()?;
403 Ok(Options::IpV6Multicast { ip, protocol, port })
404 }
405 OptionType::IpV4SD => {
406 let (ip, protocol, port) = self.as_ipv4()?;
407 Ok(Options::IpV4SD { ip, protocol, port })
408 }
409 OptionType::IpV6SD => {
410 let (ip, protocol, port) = self.as_ipv6()?;
411 Ok(Options::IpV6SD { ip, protocol, port })
412 }
413 }
414 }
415}
416
417pub struct OptionIter<'a> {
420 remaining: &'a [u8],
421}
422
423impl<'a> OptionIter<'a> {
424 pub(crate) fn new(buf: &'a [u8]) -> Self {
425 Self { remaining: buf }
426 }
427}
428
429impl<'a> Iterator for OptionIter<'a> {
430 type Item = OptionView<'a>;
431
432 fn next(&mut self) -> Option<Self::Item> {
433 if self.remaining.len() < 4 {
434 return None;
435 }
436 let length = u16::from_be_bytes([self.remaining[0], self.remaining[1]]);
437 let wire_size = usize::from(length) + 3;
438 if wire_size > self.remaining.len() {
439 return None;
440 }
441 let view = OptionView(&self.remaining[..wire_size]);
442 self.remaining = &self.remaining[wire_size..];
443 Some(view)
444 }
445}
446
447pub(crate) fn validate_option(buf: &[u8]) -> Result<usize, Error> {
450 if buf.len() < 4 {
451 return Err(Error::IncorrectOptionsSize(buf.len()));
452 }
453 let length = u16::from_be_bytes([buf[0], buf[1]]);
454 let wire_size = usize::from(length) + 3;
455 if wire_size > buf.len() {
456 return Err(Error::IncorrectOptionsSize(buf.len()));
457 }
458 let option_type = OptionType::try_from(buf[2])?;
459 match option_type {
461 OptionType::IpV4Endpoint | OptionType::IpV4Multicast | OptionType::IpV4SD => {
462 if length != 9 {
463 return Err(Error::InvalidOptionLength {
464 option_type: buf[2],
465 expected: 9,
466 actual: length,
467 });
468 }
469 }
470 OptionType::IpV6Endpoint | OptionType::IpV6Multicast | OptionType::IpV6SD => {
471 if length != 21 {
472 return Err(Error::InvalidOptionLength {
473 option_type: buf[2],
474 expected: 21,
475 actual: length,
476 });
477 }
478 }
479 OptionType::LoadBalancing => {
480 if length != 5 {
481 return Err(Error::InvalidOptionLength {
482 option_type: buf[2],
483 expected: 5,
484 actual: length,
485 });
486 }
487 }
488 OptionType::Configuration => {
489 let string_len = length.saturating_sub(1);
491 if usize::from(string_len) > MAX_CONFIGURATION_STRING_LENGTH {
492 return Err(Error::ConfigurationStringTooLong(string_len.into()));
493 }
494 }
495 }
496 Ok(wire_size)
497}
498
499#[cfg(test)]
500mod tests {
501 use core::net::{Ipv4Addr, Ipv6Addr};
502
503 use super::*;
504
505 #[test]
508 fn transport_protocol_tcp_round_trip() {
509 assert_eq!(
510 TransportProtocol::try_from(0x06).unwrap(),
511 TransportProtocol::Tcp
512 );
513 assert_eq!(u8::from(TransportProtocol::Tcp), 0x06);
514 }
515
516 #[test]
517 fn transport_protocol_invalid_returns_error() {
518 assert!(matches!(
519 TransportProtocol::try_from(0xFF),
520 Err(Error::InvalidOptionTransportProtocol(0xFF))
521 ));
522 }
523
524 #[test]
527 fn option_view_ipv4_endpoint_tcp() {
528 let buf: [u8; 12] = [
529 0x00, 0x09, 0x04, 0x00, 192, 168, 0, 1, 0x00, 0x06, 0x04, 0xD2, ];
537 let view = OptionView(&buf);
538 assert_eq!(view.option_type().unwrap(), OptionType::IpV4Endpoint);
539 assert_eq!(view.wire_size(), 12);
540 let (ip, protocol, port) = view.as_ipv4().unwrap();
541 assert_eq!(ip, Ipv4Addr::new(192, 168, 0, 1));
542 assert_eq!(protocol, TransportProtocol::Tcp);
543 assert_eq!(port, 1234);
544 }
545
546 #[test]
547 fn option_view_to_owned_invalid_type() {
548 let buf: [u8; 4] = [0x00, 0x00, 0xFF, 0x00]; let view = OptionView(&buf);
550 assert!(matches!(
551 view.to_owned(),
552 Err(Error::InvalidOptionType(0xFF))
553 ));
554 }
555
556 fn round_trip(option: &Options) {
559 let size = option.size();
560 let mut buf = [0u8; 4 + MAX_CONFIGURATION_STRING_LENGTH];
561 let written = option.write(&mut &mut buf[..size]).unwrap();
562 assert_eq!(written, size);
563 let view = OptionView(&buf[..size]);
564 let parsed = view.to_owned().unwrap();
565 assert_eq!(*option, parsed);
566 }
567
568 #[test]
569 fn configuration_round_trip() {
570 let mut config_string = heapless::Vec::<u8, MAX_CONFIGURATION_STRING_LENGTH>::new();
571 config_string.extend_from_slice(b"test=value").unwrap();
572 let option = Options::Configuration {
573 configuration_string: config_string,
574 };
575 round_trip(&option);
576 }
577
578 #[test]
579 fn configuration_empty_round_trip() {
580 let option = Options::Configuration {
581 configuration_string: heapless::Vec::new(),
582 };
583 round_trip(&option);
584 }
585
586 #[test]
587 fn load_balancing_round_trip() {
588 let option = Options::LoadBalancing {
589 priority: 100,
590 weight: 200,
591 };
592 round_trip(&option);
593 }
594
595 #[test]
596 fn ipv4_endpoint_round_trip() {
597 let option = Options::IpV4Endpoint {
598 ip: Ipv4Addr::new(10, 0, 0, 1),
599 protocol: TransportProtocol::Udp,
600 port: 30490,
601 };
602 round_trip(&option);
603 }
604
605 #[test]
606 fn ipv6_endpoint_round_trip() {
607 let option = Options::IpV6Endpoint {
608 ip: Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
609 protocol: TransportProtocol::Tcp,
610 port: 8080,
611 };
612 round_trip(&option);
613 }
614
615 #[test]
616 fn ipv4_multicast_round_trip() {
617 let option = Options::IpV4Multicast {
618 ip: Ipv4Addr::new(239, 0, 0, 1),
619 protocol: TransportProtocol::Udp,
620 port: 30490,
621 };
622 round_trip(&option);
623 }
624
625 #[test]
626 fn ipv6_multicast_round_trip() {
627 let option = Options::IpV6Multicast {
628 ip: Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
629 protocol: TransportProtocol::Udp,
630 port: 30490,
631 };
632 round_trip(&option);
633 }
634
635 #[test]
636 fn ipv4_sd_round_trip() {
637 let option = Options::IpV4SD {
638 ip: Ipv4Addr::new(172, 16, 0, 1),
639 protocol: TransportProtocol::Udp,
640 port: 30490,
641 };
642 round_trip(&option);
643 }
644
645 #[test]
646 fn ipv6_sd_round_trip() {
647 let option = Options::IpV6SD {
648 ip: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
649 protocol: TransportProtocol::Tcp,
650 port: 9999,
651 };
652 round_trip(&option);
653 }
654
655 #[test]
658 fn load_balancing_invalid_length_returns_error() {
659 let mut buf = [0u8; 6];
661 buf[0] = 0x00;
662 buf[1] = 0x03; buf[2] = 0x02; buf[3] = 0x00; assert!(matches!(
666 validate_option(&buf),
667 Err(Error::InvalidOptionLength {
668 option_type: 0x02,
669 expected: 5,
670 actual: 3,
671 })
672 ));
673 }
674
675 #[test]
676 fn ipv4_endpoint_invalid_length_returns_error() {
677 let mut buf = [0u8; 8];
679 buf[0] = 0x00;
680 buf[1] = 0x05; buf[2] = 0x04; buf[3] = 0x00;
683 assert!(matches!(
684 validate_option(&buf),
685 Err(Error::InvalidOptionLength {
686 option_type: 0x04,
687 expected: 9,
688 actual: 5,
689 })
690 ));
691 }
692
693 #[test]
694 fn ipv6_endpoint_invalid_length_returns_error() {
695 let mut buf = [0u8; 12];
697 buf[0] = 0x00;
698 buf[1] = 0x09; buf[2] = 0x06; buf[3] = 0x00;
701 assert!(matches!(
702 validate_option(&buf),
703 Err(Error::InvalidOptionLength {
704 option_type: 0x06,
705 expected: 21,
706 actual: 9,
707 })
708 ));
709 }
710
711 #[test]
712 fn ipv4_multicast_invalid_length_returns_error() {
713 let mut buf = [0u8; 8];
715 buf[0] = 0x00;
716 buf[1] = 0x05;
717 buf[2] = 0x14; buf[3] = 0x00;
719 assert!(matches!(
720 validate_option(&buf),
721 Err(Error::InvalidOptionLength {
722 option_type: 0x14,
723 expected: 9,
724 actual: 5,
725 })
726 ));
727 }
728
729 #[test]
730 fn ipv6_multicast_invalid_length_returns_error() {
731 let mut buf = [0u8; 12];
733 buf[0] = 0x00;
734 buf[1] = 0x09;
735 buf[2] = 0x16; buf[3] = 0x00;
737 assert!(matches!(
738 validate_option(&buf),
739 Err(Error::InvalidOptionLength {
740 option_type: 0x16,
741 expected: 21,
742 actual: 9,
743 })
744 ));
745 }
746
747 #[test]
748 fn ipv4_sd_invalid_length_returns_error() {
749 let mut buf = [0u8; 8];
751 buf[0] = 0x00;
752 buf[1] = 0x05;
753 buf[2] = 0x24; buf[3] = 0x00;
755 assert!(matches!(
756 validate_option(&buf),
757 Err(Error::InvalidOptionLength {
758 option_type: 0x24,
759 expected: 9,
760 actual: 5,
761 })
762 ));
763 }
764
765 #[test]
766 fn ipv6_sd_invalid_length_returns_error() {
767 let mut buf = [0u8; 12];
769 buf[0] = 0x00;
770 buf[1] = 0x09;
771 buf[2] = 0x26; buf[3] = 0x00;
773 assert!(matches!(
774 validate_option(&buf),
775 Err(Error::InvalidOptionLength {
776 option_type: 0x26,
777 expected: 21,
778 actual: 9,
779 })
780 ));
781 }
782
783 #[test]
786 fn option_iter_empty() {
787 let iter = OptionIter::new(&[]);
788 assert_eq!(iter.count(), 0);
789 }
790
791 #[test]
792 fn option_iter_two_options() {
793 let opt1 = Options::IpV4Endpoint {
794 ip: Ipv4Addr::new(10, 0, 0, 1),
795 protocol: TransportProtocol::Udp,
796 port: 30490,
797 };
798 let opt2 = Options::LoadBalancing {
799 priority: 100,
800 weight: 200,
801 };
802 let mut buf = [0u8; 24]; let n1 = opt1.write(&mut &mut buf[..12]).unwrap();
804 let n2 = opt2.write(&mut &mut buf[12..20]).unwrap();
805 let total = n1 + n2;
806
807 let mut iter = OptionIter::new(&buf[..total]);
808 let v1 = iter.next().unwrap();
809 assert_eq!(v1.to_owned().unwrap(), opt1);
810 let v2 = iter.next().unwrap();
811 assert_eq!(v2.to_owned().unwrap(), opt2);
812 assert!(iter.next().is_none());
813 }
814}