someip_sd_wire/options.rs
1/// Option types for SOME/IP-SD
2///
3/// This module provides zero-copy wrappers around various option types
4/// used in SOME/IP Service Discovery messages. Options provide additional
5/// information like endpoint addresses, load balancing parameters, and
6/// configuration strings.
7
8use crate::error::Error;
9use crate::field;
10use byteorder::{ByteOrder, NetworkEndian};
11
12/// Result type alias using the crate's Error type.
13pub type Result<T> = core::result::Result<T, Error>;
14
15/// Option type enumeration for SOME/IP-SD options.
16///
17/// Defines the type field in option headers which determines how to
18/// interpret the option payload.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(u8)]
21pub enum OptionType {
22 /// Configuration option (0x01) - DNS-SD TXT record style key=value pairs
23 Configuration = 0x01,
24 /// Load balancing option (0x02) - Priority and weight for load balancing
25 LoadBalancing = 0x02,
26 /// IPv4 endpoint option (0x04) - IPv4 address and port
27 IPv4Endpoint = 0x04,
28 /// IPv6 endpoint option (0x06) - IPv6 address and port
29 IPv6Endpoint = 0x06,
30 /// IPv4 multicast option (0x14) - IPv4 multicast address and port
31 IPv4Multicast = 0x14,
32 /// IPv6 multicast option (0x16) - IPv6 multicast address and port
33 IPv6Multicast = 0x16,
34 /// IPv4 SD endpoint option (0x24) - IPv4 address and port for SD messages
35 IPv4SdEndpoint = 0x24,
36 /// IPv6 SD endpoint option (0x26) - IPv6 address and port for SD messages
37 IPv6SdEndpoint = 0x26,
38}
39
40impl OptionType {
41 /// Convert a u8 value to an OptionType.
42 ///
43 /// # Parameters
44 /// * `value` - The byte value to convert
45 ///
46 /// # Returns
47 /// * `Some(OptionType)` if value matches a known option type
48 /// * `None` if value is not a valid option type
49 pub fn from_u8(value: u8) -> Option<Self> {
50 match value {
51 0x01 => Some(OptionType::Configuration),
52 0x02 => Some(OptionType::LoadBalancing),
53 0x04 => Some(OptionType::IPv4Endpoint),
54 0x06 => Some(OptionType::IPv6Endpoint),
55 0x14 => Some(OptionType::IPv4Multicast),
56 0x16 => Some(OptionType::IPv6Multicast),
57 0x24 => Some(OptionType::IPv4SdEndpoint),
58 0x26 => Some(OptionType::IPv6SdEndpoint),
59 _ => None,
60 }
61 }
62
63 /// Convert the OptionType to its u8 representation.
64 ///
65 /// # Returns
66 /// The byte value of this option type
67 pub fn as_u8(&self) -> u8 {
68 *self as u8
69 }
70}
71
72/// Transport protocol enumeration.
73///
74/// Based on IANA protocol numbers for IP protocols.
75/// Used in endpoint options to specify TCP or UDP.
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77#[repr(u8)]
78pub enum TransportProtocol {
79 /// TCP protocol (0x06)
80 TCP = 0x06,
81 /// UDP protocol (0x11)
82 UDP = 0x11,
83}
84
85impl TransportProtocol {
86 /// Convert a u8 value to a TransportProtocol.
87 ///
88 /// # Parameters
89 /// * `value` - The byte value to convert (IANA protocol number)
90 ///
91 /// # Returns
92 /// * `Some(TransportProtocol)` if value is 0x06 (TCP) or 0x11 (UDP)
93 /// * `None` if value is not a supported protocol
94 pub fn from_u8(value: u8) -> Option<Self> {
95 match value {
96 0x06 => Some(TransportProtocol::TCP),
97 0x11 => Some(TransportProtocol::UDP),
98 _ => None,
99 }
100 }
101
102 /// Convert the TransportProtocol to its u8 representation.
103 ///
104 /// # Returns
105 /// The IANA protocol number (0x06 for TCP, 0x11 for UDP)
106 pub fn as_u8(&self) -> u8 {
107 *self as u8
108 }
109}
110
111/// 1-bit discardable flag + 7 reserved bits packed into a u8.
112///
113/// The discardable flag indicates whether an option can be safely ignored
114/// by receivers that don't understand it. The remaining 7 bits are reserved
115/// and should be set to 0.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub struct DiscardableFlag(u8);
118
119impl DiscardableFlag {
120 /// Create a new DiscardableFlag with all bits set to 0.
121 ///
122 /// # Returns
123 /// A DiscardableFlag with discardable=false and reserved=0
124 pub fn new() -> Self {
125 DiscardableFlag(0)
126 }
127
128 /// Create a DiscardableFlag from a boolean value.
129 ///
130 /// # Parameters
131 /// * `discardable` - True to set the discardable bit, false to clear it
132 ///
133 /// # Returns
134 /// A DiscardableFlag with the specified discardable bit and reserved=0
135 pub fn from_bool(discardable: bool) -> Self {
136 DiscardableFlag(if discardable { 0x80 } else { 0x00 })
137 }
138
139 /// Check if the discardable bit is set.
140 ///
141 /// # Returns
142 /// True if the option can be discarded, false otherwise
143 pub fn is_discardable(&self) -> bool {
144 (self.0 & 0x80) != 0
145 }
146
147 /// Set or clear the discardable bit.
148 ///
149 /// # Parameters
150 /// * `discardable` - True to set the bit, false to clear it
151 pub fn set_discardable(&mut self, discardable: bool) {
152 if discardable {
153 self.0 |= 0x80;
154 } else {
155 self.0 &= 0x7F;
156 }
157 }
158
159 /// Get the 7-bit reserved field value.
160 ///
161 /// # Returns
162 /// The lower 7 bits (should be 0 in well-formed packets)
163 pub fn reserved(&self) -> u8 {
164 self.0 & 0x7F
165 }
166
167 /// Convert to the u8 wire format representation.
168 ///
169 /// # Returns
170 /// The packed byte with discardable bit (MSB) and reserved bits
171 pub fn as_u8(&self) -> u8 {
172 self.0
173 }
174
175 /// Create a DiscardableFlag from a u8 value.
176 ///
177 /// # Parameters
178 /// * `value` - The byte value (bit 7 = discardable, bits 6-0 = reserved)
179 ///
180 /// # Returns
181 /// A DiscardableFlag with the specified bit pattern
182 pub fn from_u8(value: u8) -> Self {
183 DiscardableFlag(value)
184 }
185}
186
187/// Zero-copy wrapper around Option header (4 bytes).
188///
189/// All SOME/IP-SD options start with this 4-byte header containing
190/// the length, type, and discardable flag.
191///
192/// Wire format (4 bytes):
193/// ```text
194/// 0 1 2 3
195/// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
196/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
197/// | Length | Type |D| Reserved |
198/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
199/// ```
200#[derive(Debug, Clone, Copy)]
201pub struct OptionHeader<T: AsRef<[u8]>> {
202 buffer: T,
203}
204
205impl<T: AsRef<[u8]>> OptionHeader<T> {
206 /// Option header wire format size in bytes.
207 pub const LENGTH: usize = 4;
208
209 /// Create an OptionHeader without validation.
210 ///
211 /// # Parameters
212 /// * `buffer` - The buffer containing the 4-byte header
213 ///
214 /// # Safety
215 /// This does not validate buffer length. Use `new_checked` for validation.
216 pub fn new_unchecked(buffer: T) -> Self {
217 OptionHeader { buffer }
218 }
219
220 /// Create an OptionHeader from a buffer with length validation.
221 ///
222 /// # Parameters
223 /// * `buffer` - The buffer containing the 4-byte header
224 ///
225 /// # Returns
226 /// * `Ok(OptionHeader)` if buffer is at least 4 bytes
227 /// * `Err(Error)` if buffer is too short
228 pub fn new_checked(buffer: T) -> Result<Self> {
229 let header = Self::new_unchecked(buffer);
230 header.check_len()?;
231 Ok(header)
232 }
233
234 /// Validate that the buffer is at least 4 bytes long.
235 ///
236 /// # Returns
237 /// * `Ok(())` if buffer meets minimum length requirement
238 /// * `Err(Error)` if buffer is too short
239 pub fn check_len(&self) -> Result<()> {
240 if self.buffer.as_ref().len() < Self::LENGTH {
241 return Err(Error::BufferTooShort);
242 }
243 Ok(())
244 }
245
246 /// Validate the option type field contains a known option type.
247 ///
248 /// # Returns
249 /// * `Ok(())` if option type is valid
250 /// * `Err(Error::InvalidOptionType)` if option type is unknown
251 pub fn check_option_type(&self) -> Result<()> {
252 let type_val = self.option_type();
253 OptionType::from_u8(type_val)
254 .map(|_| ())
255 .ok_or(Error::InvalidOptionType(type_val))
256 }
257
258 /// Get the Length field (2 bytes at offset 0-1, network byte order).
259 ///
260 /// # Returns
261 /// Length of the option data (excluding the 4-byte header itself)
262 pub fn length(&self) -> u16 {
263 NetworkEndian::read_u16(&self.buffer.as_ref()[field::option_header::LENGTH])
264 }
265
266 /// Get the Type field (1 byte at offset 2).
267 ///
268 /// # Returns
269 /// Option type value (use OptionType::from_u8 to parse)
270 pub fn option_type(&self) -> u8 {
271 self.buffer.as_ref()[field::option_header::TYPE.start]
272 }
273
274 /// Get the Discardable flag and reserved bits (1 byte at offset 3).
275 ///
276 /// # Returns
277 /// DiscardableFlag containing the discardable bit and reserved bits
278 pub fn discardable_flag(&self) -> DiscardableFlag {
279 DiscardableFlag::from_u8(self.buffer.as_ref()[field::option_header::DISCARDABLE_FLAG_AND_RESERVED.start])
280 }
281}
282
283impl<T: AsRef<[u8]> + AsMut<[u8]>> OptionHeader<T> {
284 /// Set the Length field (2 bytes at offset 0-1, network byte order).
285 ///
286 /// # Parameters
287 /// * `value` - Length of option data (excluding the 4-byte header)
288 pub fn set_length(&mut self, value: u16) {
289 NetworkEndian::write_u16(&mut self.buffer.as_mut()[field::option_header::LENGTH], value);
290 }
291
292 /// Set the Type field (1 byte at offset 2).
293 ///
294 /// # Parameters
295 /// * `value` - Option type value (use OptionType::as_u8 for enum values)
296 pub fn set_option_type(&mut self, value: u8) {
297 self.buffer.as_mut()[field::option_header::TYPE.start] = value;
298 }
299
300 /// Set the Discardable flag and reserved bits (1 byte at offset 3).
301 ///
302 /// # Parameters
303 /// * `value` - DiscardableFlag with the desired bit pattern
304 pub fn set_discardable_flag(&mut self, value: DiscardableFlag) {
305 self.buffer.as_mut()[field::option_header::DISCARDABLE_FLAG_AND_RESERVED.start] = value.as_u8();
306 }
307}
308
309/// Zero-copy wrapper around IPv4 Endpoint Option (12 bytes total: 4 header + 8 data).
310///
311/// IPv4 endpoint options convey IPv4 address, port, and transport protocol
312/// for service endpoints.
313///
314/// Wire format (12 bytes):
315/// ```text
316/// 0 1 2 3
317/// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
318/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
319/// | Length | Type |D| Reserved |
320/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
321/// | IPv4 Address |
322/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
323/// | Reserved | Protocol | Port |
324/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
325/// ```
326#[derive(Debug, Clone, Copy)]
327pub struct IPv4EndpointOption<T: AsRef<[u8]>> {
328 buffer: T,
329}
330
331impl<T: AsRef<[u8]>> IPv4EndpointOption<T> {
332 /// IPv4 endpoint option wire format size in bytes (4 header + 8 data).
333 pub const LENGTH: usize = 12;
334
335 /// Create an IPv4EndpointOption without validation.
336 ///
337 /// # Parameters
338 /// * `buffer` - The buffer containing the 12-byte option
339 ///
340 /// # Safety
341 /// This does not validate buffer length. Use `new_checked` for validation.
342 pub fn new_unchecked(buffer: T) -> Self {
343 IPv4EndpointOption { buffer }
344 }
345
346 /// Create an IPv4EndpointOption from a buffer with length validation.
347 ///
348 /// # Parameters
349 /// * `buffer` - The buffer containing the 12-byte option
350 ///
351 /// # Returns
352 /// * `Ok(IPv4EndpointOption)` if buffer is at least 12 bytes
353 /// * `Err(Error)` if buffer is too short
354 pub fn new_checked(buffer: T) -> Result<Self> {
355 let option = Self::new_unchecked(buffer);
356 option.check_len()?;
357 Ok(option)
358 }
359
360 /// Validate that the buffer is at least 12 bytes long.
361 ///
362 /// # Returns
363 /// * `Ok(())` if buffer meets minimum length requirement
364 /// * `Err(Error)` if buffer is too short
365 pub fn check_len(&self) -> Result<()> {
366 if self.buffer.as_ref().len() < Self::LENGTH {
367 return Err(Error::BufferTooShort);
368 }
369 Ok(())
370 }
371
372 /// Get a view of the option header (first 4 bytes).
373 ///
374 /// # Returns
375 /// OptionHeader wrapper around the header bytes
376 pub fn header(&self) -> OptionHeader<&[u8]> {
377 OptionHeader::new_unchecked(&self.buffer.as_ref()[..4])
378 }
379
380 /// Get the IPv4 address (4 bytes at offset 4-7).
381 ///
382 /// # Returns
383 /// The IPv4 address as a 4-byte array in network byte order
384 pub fn ipv4_address(&self) -> [u8; 4] {
385 let bytes = &self.buffer.as_ref()[4..];
386 [bytes[0], bytes[1], bytes[2], bytes[3]]
387 }
388
389 /// Get the transport protocol (1 byte at offset 9).
390 ///
391 /// # Returns
392 /// Protocol value (0x06=TCP, 0x11=UDP)
393 pub fn transport_protocol(&self) -> u8 {
394 self.buffer.as_ref()[4 + field::ipv4_endpoint_option::TRANSPORT_PROTOCOL.start]
395 }
396
397 /// Validate the transport protocol field.
398 ///
399 /// # Returns
400 /// * `Ok(())` if protocol is TCP (0x06) or UDP (0x11)
401 /// * `Err(Error::InvalidProtocol)` if protocol is unknown
402 pub fn check_protocol(&self) -> Result<()> {
403 let proto = self.transport_protocol();
404 TransportProtocol::from_u8(proto)
405 .map(|_| ())
406 .ok_or(Error::InvalidProtocol(proto))
407 }
408
409 /// Get the port number (2 bytes at offset 10-11, network byte order).
410 ///
411 /// # Returns
412 /// The port number
413 pub fn port(&self) -> u16 {
414 NetworkEndian::read_u16(&self.buffer.as_ref()[4 + field::ipv4_endpoint_option::PORT.start..])
415 }
416}
417
418impl<T: AsRef<[u8]> + AsMut<[u8]>> IPv4EndpointOption<T> {
419 /// Set the IPv4 address (4 bytes at offset 4-7).
420 ///
421 /// # Parameters
422 /// * `addr` - The IPv4 address as a 4-byte array in network byte order
423 pub fn set_ipv4_address(&mut self, addr: [u8; 4]) {
424 self.buffer.as_mut()[4..8].copy_from_slice(&addr);
425 }
426
427 /// Set the transport protocol (1 byte at offset 9).
428 ///
429 /// # Parameters
430 /// * `proto` - Protocol value (0x06=TCP, 0x11=UDP)
431 pub fn set_transport_protocol(&mut self, proto: u8) {
432 self.buffer.as_mut()[4 + field::ipv4_endpoint_option::TRANSPORT_PROTOCOL.start] = proto;
433 }
434
435 /// Set the port number (2 bytes at offset 10-11, network byte order).
436 ///
437 /// # Parameters
438 /// * `port` - The port number
439 pub fn set_port(&mut self, port: u16) {
440 NetworkEndian::write_u16(&mut self.buffer.as_mut()[4 + field::ipv4_endpoint_option::PORT.start..], port);
441 }
442}
443
444/// Zero-copy wrapper around IPv6 Endpoint Option (24 bytes total: 4 header + 20 data).
445///
446/// IPv6 endpoint options convey IPv6 address, port, and transport protocol
447/// for service endpoints.
448///
449/// Wire format (24 bytes):
450/// ```text
451/// 0 1 2 3
452/// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
453/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
454/// | Length | Type |D| Reserved |
455/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
456/// | |
457/// | IPv6 Address (16 bytes) |
458/// | |
459/// | |
460/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
461/// | Reserved | Protocol | Port |
462/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
463/// ```
464#[derive(Debug, Clone, Copy)]
465pub struct IPv6EndpointOption<T: AsRef<[u8]>> {
466 buffer: T,
467}
468
469impl<T: AsRef<[u8]>> IPv6EndpointOption<T> {
470 /// IPv6 endpoint option wire format size in bytes (4 header + 20 data).
471 pub const LENGTH: usize = 24;
472
473 /// Create an IPv6EndpointOption without validation.
474 ///
475 /// # Parameters
476 /// * `buffer` - The buffer containing the 24-byte option
477 ///
478 /// # Safety
479 /// This does not validate buffer length. Use `new_checked` for validation.
480 pub fn new_unchecked(buffer: T) -> Self {
481 IPv6EndpointOption { buffer }
482 }
483
484 /// Create an IPv6EndpointOption from a buffer with length validation.
485 ///
486 /// # Parameters
487 /// * `buffer` - The buffer containing the 24-byte option
488 ///
489 /// # Returns
490 /// * `Ok(IPv6EndpointOption)` if buffer is at least 24 bytes
491 /// * `Err(Error)` if buffer is too short
492 pub fn new_checked(buffer: T) -> Result<Self> {
493 let option = Self::new_unchecked(buffer);
494 option.check_len()?;
495 Ok(option)
496 }
497
498 /// Validate that the buffer is at least 24 bytes long.
499 ///
500 /// # Returns
501 /// * `Ok(())` if buffer meets minimum length requirement
502 /// * `Err(Error)` if buffer is too short
503 pub fn check_len(&self) -> Result<()> {
504 if self.buffer.as_ref().len() < Self::LENGTH {
505 return Err(Error::BufferTooShort);
506 }
507 Ok(())
508 }
509
510 /// Get a view of the option header (first 4 bytes).
511 ///
512 /// # Returns
513 /// OptionHeader wrapper around the header bytes
514 pub fn header(&self) -> OptionHeader<&[u8]> {
515 OptionHeader::new_unchecked(&self.buffer.as_ref()[..4])
516 }
517
518 /// Get the IPv6 address (16 bytes at offset 4-19).
519 ///
520 /// # Returns
521 /// The IPv6 address as a 16-byte array in network byte order
522 pub fn ipv6_address(&self) -> [u8; 16] {
523 let bytes = &self.buffer.as_ref()[4..];
524 let mut addr = [0u8; 16];
525 addr.copy_from_slice(&bytes[0..16]);
526 addr
527 }
528
529 /// Get the transport protocol (1 byte at offset 21).
530 ///
531 /// # Returns
532 /// Protocol value (0x06=TCP, 0x11=UDP)
533 pub fn transport_protocol(&self) -> u8 {
534 self.buffer.as_ref()[4 + field::ipv6_endpoint_option::TRANSPORT_PROTOCOL.start]
535 }
536
537 /// Validate the transport protocol field.
538 ///
539 /// # Returns
540 /// * `Ok(())` if protocol is TCP (0x06) or UDP (0x11)
541 /// * `Err(Error::InvalidProtocol)` if protocol is unknown
542 pub fn check_protocol(&self) -> Result<()> {
543 let proto = self.transport_protocol();
544 TransportProtocol::from_u8(proto)
545 .map(|_| ())
546 .ok_or(Error::InvalidProtocol(proto))
547 }
548
549 /// Get the port number (2 bytes at offset 22-23, network byte order).
550 ///
551 /// # Returns
552 /// The port number
553 pub fn port(&self) -> u16 {
554 NetworkEndian::read_u16(&self.buffer.as_ref()[4 + field::ipv6_endpoint_option::PORT.start..])
555 }
556}
557
558impl<T: AsRef<[u8]> + AsMut<[u8]>> IPv6EndpointOption<T> {
559 /// Set the IPv6 address (16 bytes at offset 4-19).
560 ///
561 /// # Parameters
562 /// * `addr` - The IPv6 address as a 16-byte array in network byte order
563 pub fn set_ipv6_address(&mut self, addr: [u8; 16]) {
564 self.buffer.as_mut()[4..20].copy_from_slice(&addr);
565 }
566
567 /// Set the transport protocol (1 byte at offset 21).
568 ///
569 /// # Parameters
570 /// * `proto` - Protocol value (0x06=TCP, 0x11=UDP)
571 pub fn set_transport_protocol(&mut self, proto: u8) {
572 self.buffer.as_mut()[4 + field::ipv6_endpoint_option::TRANSPORT_PROTOCOL.start] = proto;
573 }
574
575 /// Set the port number (2 bytes at offset 22-23, network byte order).
576 ///
577 /// # Parameters
578 /// * `port` - The port number
579 pub fn set_port(&mut self, port: u16) {
580 NetworkEndian::write_u16(&mut self.buffer.as_mut()[4 + field::ipv6_endpoint_option::PORT.start..], port);
581 }
582}
583
584/// Zero-copy wrapper around Load Balancing Option (8 bytes total: 4 header + 4 data).
585///
586/// Load balancing options provide priority and weight values for server selection.
587///
588/// Wire format (8 bytes):
589/// ```text
590/// 0 1 2 3
591/// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
592/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
593/// | Length | Type |D| Reserved |
594/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
595/// | Priority | Weight |
596/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
597/// ```
598#[derive(Debug, Clone, Copy)]
599pub struct LoadBalancingOption<T: AsRef<[u8]>> {
600 buffer: T,
601}
602
603impl<T: AsRef<[u8]>> LoadBalancingOption<T> {
604 /// Load balancing option wire format size in bytes (4 header + 4 data).
605 pub const LENGTH: usize = 8;
606
607 /// Create a LoadBalancingOption without validation.
608 ///
609 /// # Parameters
610 /// * `buffer` - The buffer containing the 8-byte option
611 ///
612 /// # Safety
613 /// This does not validate buffer length. Use `new_checked` for validation.
614 pub fn new_unchecked(buffer: T) -> Self {
615 LoadBalancingOption { buffer }
616 }
617
618 /// Create a LoadBalancingOption from a buffer with length validation.
619 ///
620 /// # Parameters
621 /// * `buffer` - The buffer containing the 8-byte option
622 ///
623 /// # Returns
624 /// * `Ok(LoadBalancingOption)` if buffer is at least 8 bytes
625 /// * `Err(Error)` if buffer is too short
626 pub fn new_checked(buffer: T) -> Result<Self> {
627 let option = Self::new_unchecked(buffer);
628 option.check_len()?;
629 Ok(option)
630 }
631
632 /// Validate that the buffer is at least 8 bytes long.
633 ///
634 /// # Returns
635 /// * `Ok(())` if buffer meets minimum length requirement
636 /// * `Err(Error)` if buffer is too short
637 pub fn check_len(&self) -> Result<()> {
638 if self.buffer.as_ref().len() < Self::LENGTH {
639 return Err(Error::BufferTooShort);
640 }
641 Ok(())
642 }
643
644 /// Get a view of the option header (first 4 bytes).
645 ///
646 /// # Returns
647 /// OptionHeader wrapper around the header bytes
648 pub fn header(&self) -> OptionHeader<&[u8]> {
649 OptionHeader::new_unchecked(&self.buffer.as_ref()[..4])
650 }
651
652 /// Get the priority value (2 bytes at offset 4-5, network byte order).
653 ///
654 /// # Returns
655 /// Priority value (lower is higher priority)
656 pub fn priority(&self) -> u16 {
657 NetworkEndian::read_u16(&self.buffer.as_ref()[4 + field::load_balancing_option::PRIORITY.start..])
658 }
659
660 /// Get the weight value (2 bytes at offset 6-7, network byte order).
661 ///
662 /// # Returns
663 /// Weight value for load distribution
664 pub fn weight(&self) -> u16 {
665 NetworkEndian::read_u16(&self.buffer.as_ref()[4 + field::load_balancing_option::WEIGHT.start..])
666 }
667}
668
669impl<T: AsRef<[u8]> + AsMut<[u8]>> LoadBalancingOption<T> {
670 /// Set the priority value (2 bytes at offset 4-5, network byte order).
671 ///
672 /// # Parameters
673 /// * `priority` - Priority value (lower is higher priority)
674 pub fn set_priority(&mut self, priority: u16) {
675 NetworkEndian::write_u16(&mut self.buffer.as_mut()[4 + field::load_balancing_option::PRIORITY.start..], priority);
676 }
677
678 /// Set the weight value (2 bytes at offset 6-7, network byte order).
679 ///
680 /// # Parameters
681 /// * `weight` - Weight value for load distribution
682 pub fn set_weight(&mut self, weight: u16) {
683 NetworkEndian::write_u16(&mut self.buffer.as_mut()[4 + field::load_balancing_option::WEIGHT.start..], weight);
684 }
685}
686
687/// High-level representation of an IPv4 Endpoint Option.
688///
689/// This provides a builder-style API for constructing and parsing IPv4 endpoint options
690/// without manually managing byte arrays.
691#[derive(Debug, Clone, Copy, PartialEq, Eq)]
692pub struct IPv4EndpointOptionRepr {
693 /// IPv4 address (4 bytes)
694 pub ipv4_address: [u8; 4],
695 /// Transport protocol (TCP=0x06, UDP=0x11)
696 pub protocol: TransportProtocol,
697 /// Port number
698 pub port: u16,
699}
700
701impl IPv4EndpointOptionRepr {
702 /// Parse an IPv4EndpointOption into a high-level representation.
703 ///
704 /// # Parameters
705 /// * `option` - The IPv4EndpointOption to parse
706 ///
707 /// # Returns
708 /// IPv4EndpointOptionRepr with all fields populated
709 ///
710 /// # Errors
711 /// Returns Error::InvalidProtocol if protocol is not TCP or UDP
712 pub fn parse<T: AsRef<[u8]>>(option: &IPv4EndpointOption<T>) -> Result<Self> {
713 option.check_protocol()?;
714
715 let protocol = TransportProtocol::from_u8(option.transport_protocol())
716 .ok_or(Error::InvalidProtocol(option.transport_protocol()))?;
717
718 Ok(IPv4EndpointOptionRepr {
719 ipv4_address: option.ipv4_address(),
720 protocol,
721 port: option.port(),
722 })
723 }
724
725 /// Emit this representation into a buffer.
726 ///
727 /// # Parameters
728 /// * `buffer` - 12-byte buffer to write the option into
729 ///
730 /// # Returns
731 /// Number of bytes written (always 12)
732 pub fn emit(&self, buffer: &mut [u8]) -> usize {
733 let mut header = OptionHeader::new_unchecked(&mut buffer[..4]);
734 header.set_length(9);
735 header.set_option_type(OptionType::IPv4Endpoint.as_u8());
736
737 let mut option = IPv4EndpointOption::new_unchecked(buffer);
738 option.set_ipv4_address(self.ipv4_address);
739 option.set_transport_protocol(self.protocol.as_u8());
740 option.set_port(self.port);
741
742 Self::buffer_len()
743 }
744
745 /// Get the wire format size of this option (always 12 bytes: 4 header + 8 payload).
746 pub const fn buffer_len() -> usize {
747 12
748 }
749}
750
751/// High-level representation of an IPv6 Endpoint Option.
752///
753/// This provides a builder-style API for constructing and parsing IPv6 endpoint options
754/// without manually managing byte arrays.
755#[derive(Debug, Clone, Copy, PartialEq, Eq)]
756pub struct IPv6EndpointOptionRepr {
757 /// IPv6 address (16 bytes)
758 pub ipv6_address: [u8; 16],
759 /// Transport protocol (TCP=0x06, UDP=0x11)
760 pub protocol: TransportProtocol,
761 /// Port number
762 pub port: u16,
763}
764
765impl IPv6EndpointOptionRepr {
766 /// Parse an IPv6EndpointOption into a high-level representation.
767 ///
768 /// # Parameters
769 /// * `option` - The IPv6EndpointOption to parse
770 ///
771 /// # Returns
772 /// IPv6EndpointOptionRepr with all fields populated
773 ///
774 /// # Errors
775 /// Returns Error::InvalidProtocol if protocol is not TCP or UDP
776 pub fn parse<T: AsRef<[u8]>>(option: &IPv6EndpointOption<T>) -> Result<Self> {
777 option.check_protocol()?;
778
779 let protocol = TransportProtocol::from_u8(option.transport_protocol())
780 .ok_or(Error::InvalidProtocol(option.transport_protocol()))?;
781
782 Ok(IPv6EndpointOptionRepr {
783 ipv6_address: option.ipv6_address(),
784 protocol,
785 port: option.port(),
786 })
787 }
788
789 /// Emit this representation into a buffer.
790 ///
791 /// # Parameters
792 /// * `buffer` - 24-byte buffer to write the option into
793 ///
794 /// # Returns
795 /// Number of bytes written (always 24)
796 pub fn emit(&self, buffer: &mut [u8]) -> usize {
797 let mut header = OptionHeader::new_unchecked(&mut buffer[..4]);
798 header.set_length(21);
799 header.set_option_type(OptionType::IPv6Endpoint.as_u8());
800
801 let mut option = IPv6EndpointOption::new_unchecked(buffer);
802 option.set_ipv6_address(self.ipv6_address);
803 option.set_transport_protocol(self.protocol.as_u8());
804 option.set_port(self.port);
805
806 Self::buffer_len()
807 }
808
809 /// Get the wire format size of this option (always 24 bytes: 4 header + 20 payload).
810 pub const fn buffer_len() -> usize {
811 24
812 }
813}
814
815/// High-level representation of a Load Balancing Option.
816///
817/// This provides a builder-style API for constructing and parsing load balancing options
818/// without manually managing byte arrays.
819#[derive(Debug, Clone, Copy, PartialEq, Eq)]
820pub struct LoadBalancingOptionRepr {
821 /// Priority value (lower = higher priority)
822 pub priority: u16,
823 /// Weight for load distribution
824 pub weight: u16,
825}
826
827impl LoadBalancingOptionRepr {
828 /// Parse a LoadBalancingOption into a high-level representation.
829 ///
830 /// # Parameters
831 /// * `option` - The LoadBalancingOption to parse
832 ///
833 /// # Returns
834 /// LoadBalancingOptionRepr with all fields populated
835 pub fn parse<T: AsRef<[u8]>>(option: &LoadBalancingOption<T>) -> Self {
836 LoadBalancingOptionRepr {
837 priority: option.priority(),
838 weight: option.weight(),
839 }
840 }
841
842 /// Emit this representation into a buffer.
843 ///
844 /// # Parameters
845 /// * `buffer` - 9-byte buffer to write the option into
846 ///
847 /// # Returns
848 /// Number of bytes written (always 9)
849 pub fn emit(&self, buffer: &mut [u8]) -> usize {
850 let mut header = OptionHeader::new_unchecked(&mut buffer[..4]);
851 header.set_length(5);
852 header.set_option_type(OptionType::LoadBalancing.as_u8());
853
854 let mut option = LoadBalancingOption::new_unchecked(buffer);
855 option.set_priority(self.priority);
856 option.set_weight(self.weight);
857
858 Self::buffer_len()
859 }
860
861 /// Get the wire format size of this option (always 9 bytes: 4 header + 5 payload).
862 pub const fn buffer_len() -> usize {
863 9
864 }
865}
866
867#[cfg(test)]
868mod tests {
869 use super::*;
870
871 #[test]
872 fn test_option_header() {
873 let mut buffer = [0u8; 4];
874 let mut header = OptionHeader::new_unchecked(&mut buffer[..]);
875
876 header.set_length(8);
877 header.set_option_type(OptionType::Configuration.as_u8());
878 header.set_discardable_flag(DiscardableFlag::from_bool(true));
879
880 assert_eq!(header.length(), 8);
881 assert_eq!(header.option_type(), 0x01);
882 assert!(header.discardable_flag().is_discardable());
883 }
884
885 #[test]
886 fn test_ipv4_endpoint_option() {
887 let mut buffer = [0u8; 12];
888 let mut option = IPv4EndpointOption::new_unchecked(&mut buffer[..]);
889
890 option.set_ipv4_address([192, 168, 1, 1]);
891 option.set_transport_protocol(TransportProtocol::UDP.as_u8());
892 option.set_port(30490);
893
894 assert_eq!(option.ipv4_address(), [192, 168, 1, 1]);
895 assert_eq!(option.transport_protocol(), 0x11);
896 assert_eq!(option.port(), 30490);
897 }
898
899 #[test]
900 fn test_ipv6_endpoint_option() {
901 let mut buffer = [0u8; 24];
902 let mut option = IPv6EndpointOption::new_unchecked(&mut buffer[..]);
903
904 let addr = [0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
905 option.set_ipv6_address(addr);
906 option.set_transport_protocol(TransportProtocol::TCP.as_u8());
907 option.set_port(30490);
908
909 assert_eq!(option.ipv6_address(), addr);
910 assert_eq!(option.transport_protocol(), 0x06);
911 assert_eq!(option.port(), 30490);
912 }
913
914 #[test]
915 fn test_load_balancing_option() {
916 let mut buffer = [0u8; 8];
917 let mut option = LoadBalancingOption::new_unchecked(&mut buffer[..]);
918
919 option.set_priority(100);
920 option.set_weight(50);
921
922 assert_eq!(option.priority(), 100);
923 assert_eq!(option.weight(), 50);
924 }
925
926 #[test]
927 fn test_discardable_flag() {
928 let mut flag = DiscardableFlag::new();
929 assert!(!flag.is_discardable());
930 assert_eq!(flag.reserved(), 0x00);
931
932 flag.set_discardable(true);
933 assert!(flag.is_discardable());
934 assert_eq!(flag.as_u8(), 0x80);
935
936 let flag2 = DiscardableFlag::from_bool(true);
937 assert!(flag2.is_discardable());
938 }
939
940 #[test]
941 fn test_option_header_type_validation() {
942 // Valid option types
943 let mut buffer = [0u8; 4];
944 buffer[2] = 0x01; // Configuration
945 let header = OptionHeader::new_unchecked(&buffer[..]);
946 assert!(header.check_option_type().is_ok());
947
948 buffer[2] = 0x04; // IPv4Endpoint
949 let header = OptionHeader::new_unchecked(&buffer[..]);
950 assert!(header.check_option_type().is_ok());
951
952 buffer[2] = 0x24; // IPv4SdEndpoint
953 let header = OptionHeader::new_unchecked(&buffer[..]);
954 assert!(header.check_option_type().is_ok());
955
956 // Invalid option types
957 buffer[2] = 0xFF; // Unknown type
958 let header = OptionHeader::new_unchecked(&buffer[..]);
959 assert_eq!(header.check_option_type(), Err(Error::InvalidOptionType(0xFF)));
960
961 buffer[2] = 0x03; // Not a valid option type
962 let header = OptionHeader::new_unchecked(&buffer[..]);
963 assert_eq!(header.check_option_type(), Err(Error::InvalidOptionType(0x03)));
964
965 buffer[2] = 0x99; // Random invalid type
966 let header = OptionHeader::new_unchecked(&buffer[..]);
967 assert_eq!(header.check_option_type(), Err(Error::InvalidOptionType(0x99)));
968 }
969
970 #[test]
971 fn test_ipv4_endpoint_protocol_validation() {
972 // Valid protocols
973 let mut buffer = [0u8; 12];
974 buffer[9] = 0x06; // TCP
975 let option = IPv4EndpointOption::new_unchecked(&buffer[..]);
976 assert!(option.check_protocol().is_ok());
977
978 buffer[9] = 0x11; // UDP
979 let option = IPv4EndpointOption::new_unchecked(&buffer[..]);
980 assert!(option.check_protocol().is_ok());
981
982 // Invalid protocols
983 buffer[9] = 0x01; // ICMP (not supported)
984 let option = IPv4EndpointOption::new_unchecked(&buffer[..]);
985 assert_eq!(option.check_protocol(), Err(Error::InvalidProtocol(0x01)));
986
987 buffer[9] = 0xFF; // Unknown protocol
988 let option = IPv4EndpointOption::new_unchecked(&buffer[..]);
989 assert_eq!(option.check_protocol(), Err(Error::InvalidProtocol(0xFF)));
990
991 buffer[9] = 0x00; // Reserved
992 let option = IPv4EndpointOption::new_unchecked(&buffer[..]);
993 assert_eq!(option.check_protocol(), Err(Error::InvalidProtocol(0x00)));
994 }
995
996 #[test]
997 fn test_ipv6_endpoint_protocol_validation() {
998 // Valid protocols
999 let mut buffer = [0u8; 24];
1000 buffer[21] = 0x06; // TCP
1001 let option = IPv6EndpointOption::new_unchecked(&buffer[..]);
1002 assert!(option.check_protocol().is_ok());
1003
1004 buffer[21] = 0x11; // UDP
1005 let option = IPv6EndpointOption::new_unchecked(&buffer[..]);
1006 assert!(option.check_protocol().is_ok());
1007
1008 // Invalid protocols
1009 buffer[21] = 0x02; // IGMP (not supported)
1010 let option = IPv6EndpointOption::new_unchecked(&buffer[..]);
1011 assert_eq!(option.check_protocol(), Err(Error::InvalidProtocol(0x02)));
1012
1013 buffer[21] = 0xFF; // Unknown protocol
1014 let option = IPv6EndpointOption::new_unchecked(&buffer[..]);
1015 assert_eq!(option.check_protocol(), Err(Error::InvalidProtocol(0xFF)));
1016
1017 buffer[21] = 0x3A; // IPv6-ICMP (not supported in this context)
1018 let option = IPv6EndpointOption::new_unchecked(&buffer[..]);
1019 assert_eq!(option.check_protocol(), Err(Error::InvalidProtocol(0x3A)));
1020 }
1021}